<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="lib/servlet-api"/>
+ <classpathentry kind="src" path="lib/jetty"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * ThreadLocal Date formatters for HTTP style dates.
+ *
+ */
+public class DateGenerator
+{
+ private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+ static
+ {
+ __GMT.setID("GMT");
+ }
+
+ static final String[] DAYS =
+ { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+ static final String[] MONTHS =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
+
+
+ private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
+ {
+ @Override
+ protected DateGenerator initialValue()
+ {
+ return new DateGenerator();
+ }
+ };
+
+
+ public final static String __01Jan1970=DateGenerator.formatDate(0);
+
+ /**
+ * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+ */
+ public static String formatDate(long date)
+ {
+ return __dateGenerator.get().doFormatDate(date);
+ }
+
+ /**
+ * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+ */
+ public static void formatCookieDate(StringBuilder buf, long date)
+ {
+ __dateGenerator.get().doFormatCookieDate(buf,date);
+ }
+
+ /**
+ * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+ */
+ public static String formatCookieDate(long date)
+ {
+ StringBuilder buf = new StringBuilder(28);
+ formatCookieDate(buf, date);
+ return buf.toString();
+ }
+
+ private final StringBuilder buf = new StringBuilder(32);
+ private final GregorianCalendar gc = new GregorianCalendar(__GMT);
+
+ /**
+ * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
+ */
+ public String doFormatDate(long date)
+ {
+ buf.setLength(0);
+ gc.setTimeInMillis(date);
+
+ int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+ int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+ int month = gc.get(Calendar.MONTH);
+ int year = gc.get(Calendar.YEAR);
+ int century = year / 100;
+ year = year % 100;
+
+ int hours = gc.get(Calendar.HOUR_OF_DAY);
+ int minutes = gc.get(Calendar.MINUTE);
+ int seconds = gc.get(Calendar.SECOND);
+
+ buf.append(DAYS[day_of_week]);
+ buf.append(',');
+ buf.append(' ');
+ StringUtil.append2digits(buf, day_of_month);
+
+ buf.append(' ');
+ buf.append(MONTHS[month]);
+ buf.append(' ');
+ StringUtil.append2digits(buf, century);
+ StringUtil.append2digits(buf, year);
+
+ buf.append(' ');
+ StringUtil.append2digits(buf, hours);
+ buf.append(':');
+ StringUtil.append2digits(buf, minutes);
+ buf.append(':');
+ StringUtil.append2digits(buf, seconds);
+ buf.append(" GMT");
+ return buf.toString();
+ }
+
+ /**
+ * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
+ */
+ public void doFormatCookieDate(StringBuilder buf, long date)
+ {
+ gc.setTimeInMillis(date);
+
+ int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+ int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+ int month = gc.get(Calendar.MONTH);
+ int year = gc.get(Calendar.YEAR);
+ year = year % 10000;
+
+ int epoch = (int) ((date / 1000) % (60 * 60 * 24));
+ int seconds = epoch % 60;
+ epoch = epoch / 60;
+ int minutes = epoch % 60;
+ int hours = epoch / 60;
+
+ buf.append(DAYS[day_of_week]);
+ buf.append(',');
+ buf.append(' ');
+ StringUtil.append2digits(buf, day_of_month);
+
+ buf.append('-');
+ buf.append(MONTHS[month]);
+ buf.append('-');
+ StringUtil.append2digits(buf, year/100);
+ StringUtil.append2digits(buf, year%100);
+
+ buf.append(' ');
+ StringUtil.append2digits(buf, hours);
+ buf.append(':');
+ StringUtil.append2digits(buf, minutes);
+ buf.append(':');
+ StringUtil.append2digits(buf, seconds);
+ buf.append(" GMT");
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * ThreadLocal data parsers for HTTP style dates
+ *
+ */
+class DateParser
+{
+ private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+ static
+ {
+ __GMT.setID("GMT");
+ }
+
+ final static String __dateReceiveFmt[] =
+ {
+ "EEE, dd MMM yyyy HH:mm:ss zzz",
+ "EEE, dd-MMM-yy HH:mm:ss",
+ "EEE MMM dd HH:mm:ss yyyy",
+
+ "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz",
+ "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss",
+ "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz",
+ "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz",
+ "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",
+ "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz",
+ "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
+ };
+
+ public static long parseDate(String date)
+ {
+ return __dateParser.get().parse(date);
+ }
+
+ private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
+ {
+ @Override
+ protected DateParser initialValue()
+ {
+ return new DateParser();
+ }
+ };
+
+ final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
+
+ private long parse(final String dateVal)
+ {
+ for (int i = 0; i < _dateReceive.length; i++)
+ {
+ if (_dateReceive[i] == null)
+ {
+ _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
+ _dateReceive[i].setTimeZone(__GMT);
+ }
+
+ try
+ {
+ Date date = (Date) _dateReceive[i].parseObject(dateVal);
+ return date.getTime();
+ }
+ catch (java.lang.Exception e)
+ {
+ // LOG.ignore(e);
+ }
+ }
+
+ if (dateVal.endsWith(" GMT"))
+ {
+ final String val = dateVal.substring(0, dateVal.length() - 4);
+
+ for (SimpleDateFormat element : _dateReceive)
+ {
+ try
+ {
+ Date date = (Date) element.parseObject(val);
+ return date.getTime();
+ }
+ catch (java.lang.Exception e)
+ {
+ // LOG.ignore(e);
+ }
+ }
+ }
+ return -1;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** HttpContent.
+ *
+ *
+ */
+public interface HttpContent
+{
+ String getContentType();
+ String getLastModified();
+ ByteBuffer getIndirectBuffer();
+ ByteBuffer getDirectBuffer();
+ String getETag();
+ Resource getResource();
+ long getContentLength();
+ InputStream getInputStream() throws IOException;
+ ReadableByteChannel getReadableByteChannel() throws IOException;
+ void release();
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class ResourceAsHttpContent implements HttpContent
+ {
+ final Resource _resource;
+ final String _mimeType;
+ final int _maxBuffer;
+ final String _etag;
+
+ /* ------------------------------------------------------------ */
+ public ResourceAsHttpContent(final Resource resource, final String mimeType)
+ {
+ this(resource,mimeType,-1,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer)
+ {
+ this(resource,mimeType,maxBuffer,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ResourceAsHttpContent(final Resource resource, final String mimeType, boolean etag)
+ {
+ this(resource,mimeType,-1,etag);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ResourceAsHttpContent(final Resource resource, final String mimeType, int maxBuffer, boolean etag)
+ {
+ _resource=resource;
+ _mimeType=mimeType;
+ _maxBuffer=maxBuffer;
+ _etag=etag?resource.getWeakETag():null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getContentType()
+ {
+ return _mimeType;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getLastModified()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ByteBuffer getDirectBuffer()
+ {
+ if (_resource.length()<=0 || _maxBuffer<_resource.length())
+ return null;
+ try
+ {
+ return BufferUtil.toBuffer(_resource,true);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getETag()
+ {
+ return _etag;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ByteBuffer getIndirectBuffer()
+ {
+ if (_resource.length()<=0 || _maxBuffer<_resource.length())
+ return null;
+ try
+ {
+ return BufferUtil.toBuffer(_resource,false);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public long getContentLength()
+ {
+ return _resource.length();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public InputStream getInputStream() throws IOException
+ {
+ return _resource.getInputStream();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ReadableByteChannel getReadableByteChannel() throws IOException
+ {
+ return _resource.getReadableByteChannel();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Resource getResource()
+ {
+ return _resource;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void release()
+ {
+ _resource.close();
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.util.concurrent.TimeUnit;
+
+public class HttpCookie
+{
+ private final String _name;
+ private final String _value;
+ private final String _comment;
+ private final String _domain;
+ private final long _maxAge;
+ private final String _path;
+ private final boolean _secure;
+ private final int _version;
+ private final boolean _httpOnly;
+ private final long _expiration;
+
+ public HttpCookie(String name, String value)
+ {
+ this(name, value, -1);
+ }
+
+ public HttpCookie(String name, String value, String domain, String path)
+ {
+ this(name, value, domain, path, -1, false, false);
+ }
+
+ public HttpCookie(String name, String value, long maxAge)
+ {
+ this(name, value, null, null, maxAge, false, false);
+ }
+
+ public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure)
+ {
+ this(name, value, domain, path, maxAge, httpOnly, secure, null, 0);
+ }
+
+ public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version)
+ {
+ _name = name;
+ _value = value;
+ _domain = domain;
+ _path = path;
+ _maxAge = maxAge;
+ _httpOnly = httpOnly;
+ _secure = secure;
+ _comment = comment;
+ _version = version;
+ _expiration = maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(maxAge);
+ }
+
+ /**
+ * @return the cookie name
+ */
+ public String getName()
+ {
+ return _name;
+ }
+
+ /**
+ * @return the cookie value
+ */
+ public String getValue()
+ {
+ return _value;
+ }
+
+ /**
+ * @return the cookie comment
+ */
+ public String getComment()
+ {
+ return _comment;
+ }
+
+ /**
+ * @return the cookie domain
+ */
+ public String getDomain()
+ {
+ return _domain;
+ }
+
+ /**
+ * @return the cookie max age in seconds
+ */
+ public long getMaxAge()
+ {
+ return _maxAge;
+ }
+
+ /**
+ * @return the cookie path
+ */
+ public String getPath()
+ {
+ return _path;
+ }
+
+ /**
+ * @return whether the cookie is valid for secure domains
+ */
+ public boolean isSecure()
+ {
+ return _secure;
+ }
+
+ /**
+ * @return the cookie version
+ */
+ public int getVersion()
+ {
+ return _version;
+ }
+
+ /**
+ * @return whether the cookie is valid for the http protocol only
+ */
+ public boolean isHttpOnly()
+ {
+ return _httpOnly;
+ }
+
+ /**
+ * @param timeNanos the time to check for cookie expiration, in nanoseconds
+ * @return whether the cookie is expired by the given time
+ */
+ public boolean isExpired(long timeNanos)
+ {
+ return _expiration >= 0 && timeNanos >= _expiration;
+ }
+
+ /**
+ * @return a string representation of this cookie
+ */
+ public String asString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append(getName()).append("=").append(getValue());
+ if (getDomain() != null)
+ builder.append(";$Domain=").append(getDomain());
+ if (getPath() != null)
+ builder.append(";$Path=").append(getPath());
+ return builder.toString();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+
+/* ------------------------------------------------------------ */
+/** A HTTP Field
+ */
+public class HttpField
+{
+ private final HttpHeader _header;
+ private final String _name;
+ private final String _value;
+
+ public HttpField(HttpHeader header, String name, String value)
+ {
+ _header = header;
+ _name = name;
+ _value = value;
+ }
+
+ public HttpField(HttpHeader header, String value)
+ {
+ this(header,header.asString(),value);
+ }
+
+ public HttpField(HttpHeader header, HttpHeaderValue value)
+ {
+ this(header,header.asString(),value.asString());
+ }
+
+ public HttpField(String name, String value)
+ {
+ this(HttpHeader.CACHE.get(name),name,value);
+ }
+
+ public HttpHeader getHeader()
+ {
+ return _header;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public String getValue()
+ {
+ return _value;
+ }
+
+ @Override
+ public String toString()
+ {
+ String v=getValue();
+ return getName() + ": " + (v==null?"":v);
+ }
+
+ public boolean isSame(HttpField field)
+ {
+ if (field==null)
+ return false;
+ if (field==this)
+ return true;
+ if (_header!=null && _header==field.getHeader())
+ return true;
+ if (_name.equalsIgnoreCase(field.getName()))
+ return true;
+ return false;
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * HTTP Fields. A collection of HTTP header and or Trailer fields.
+ *
+ * <p>This class is not synchronized as it is expected that modifications will only be performed by a
+ * single thread.
+ *
+ * <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
+ *
+ */
+public class HttpFields implements Iterable<HttpField>
+{
+ private static final Logger LOG = Log.getLogger(HttpFields.class);
+ private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");
+ public final static String __separators = ", \t";
+
+ private final ArrayList<HttpField> _fields = new ArrayList<>(20);
+
+ /**
+ * Constructor.
+ */
+ public HttpFields()
+ {
+ }
+
+ /**
+ * Get Collection of header names.
+ */
+ public Collection<String> getFieldNamesCollection()
+ {
+ final Set<String> list = new HashSet<>(_fields.size());
+ for (HttpField f : _fields)
+ {
+ if (f!=null)
+ list.add(f.getName());
+ }
+ return list;
+ }
+
+ /**
+ * Get enumeration of header _names. Returns an enumeration of strings representing the header
+ * _names for this request.
+ */
+ public Enumeration<String> getFieldNames()
+ {
+ return Collections.enumeration(getFieldNamesCollection());
+ }
+
+ public int size()
+ {
+ return _fields.size();
+ }
+
+ /**
+ * Get a Field by index.
+ * @return A Field value or null if the Field value has not been set
+ *
+ */
+ public HttpField getField(int i)
+ {
+ return _fields.get(i);
+ }
+
+ @Override
+ public Iterator<HttpField> iterator()
+ {
+ return _fields.iterator();
+ }
+
+ public HttpField getField(HttpHeader header)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getHeader()==header)
+ return f;
+ }
+ return null;
+ }
+
+ public HttpField getField(String name)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getName().equalsIgnoreCase(name))
+ return f;
+ }
+ return null;
+ }
+
+ public boolean contains(HttpHeader header, String value)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getHeader()==header && contains(f,value))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean contains(String name, String value)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getName().equalsIgnoreCase(name) && contains(f,value))
+ return true;
+ }
+ return false;
+ }
+
+ private boolean contains(HttpField field,String value)
+ {
+ String v = field.getValue();
+ if (v==null)
+ return false;
+
+ if (value.equalsIgnoreCase(v))
+ return true;
+
+ String[] split = __splitter.split(v);
+ for (int i = 0; split!=null && i < split.length; i++)
+ {
+ if (value.equals(split[i]))
+ return true;
+ }
+
+ return false;
+ }
+
+ public boolean containsKey(String name)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getName().equalsIgnoreCase(name))
+ return true;
+ }
+ return false;
+ }
+
+ public String getStringField(HttpHeader header)
+ {
+ return getStringField(header.asString());
+ }
+
+ public String get(HttpHeader header)
+ {
+ return getStringField(header.asString());
+ }
+
+ public String get(String header)
+ {
+ return getStringField(header);
+ }
+
+ /**
+ * @return the value of a field, or null if not found. For multiple fields of the same name,
+ * only the first is returned.
+ * @param name the case-insensitive field name
+ */
+ public String getStringField(String name)
+ {
+ HttpField field = getField(name);
+ return field==null?null:field.getValue();
+ }
+
+ /**
+ * Get multi headers
+ *
+ * @return List the values
+ * @param name the case-insensitive field name
+ */
+ public List<String> getValuesList(String name)
+ {
+ final List<String> list = new ArrayList<>();
+ for (HttpField f : _fields)
+ if (f.getName().equalsIgnoreCase(name))
+ list.add(f.getValue());
+ return list;
+ }
+
+ /**
+ * Get multi headers
+ *
+ * @return Enumeration of the values
+ * @param name the case-insensitive field name
+ */
+ public Enumeration<String> getValues(final String name)
+ {
+ for (int i=0;i<_fields.size();i++)
+ {
+ final HttpField f = _fields.get(i);
+
+ if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
+ {
+ final int first=i;
+ return new Enumeration<String>()
+ {
+ HttpField field=f;
+ int i = first+1;
+
+ @Override
+ public boolean hasMoreElements()
+ {
+ if (field==null)
+ {
+ while (i<_fields.size())
+ {
+ field=_fields.get(i++);
+ if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
+ return true;
+ }
+ field=null;
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String nextElement() throws NoSuchElementException
+ {
+ if (hasMoreElements())
+ {
+ String value=field.getValue();
+ field=null;
+ return value;
+ }
+ throw new NoSuchElementException();
+ }
+
+ };
+ }
+ }
+
+ List<String> empty=Collections.emptyList();
+ return Collections.enumeration(empty);
+ }
+
+ /**
+ * Get multi field values with separator. The multiple values can be represented as separate
+ * headers of the same name, or by a single header using the separator(s), or a combination of
+ * both. Separators may be quoted.
+ *
+ * @param name the case-insensitive field name
+ * @param separators String of separators.
+ * @return Enumeration of the values, or null if no such header.
+ */
+ public Enumeration<String> getValues(String name, final String separators)
+ {
+ final Enumeration<String> e = getValues(name);
+ if (e == null)
+ return null;
+ return new Enumeration<String>()
+ {
+ QuotedStringTokenizer tok = null;
+
+ @Override
+ public boolean hasMoreElements()
+ {
+ if (tok != null && tok.hasMoreElements()) return true;
+ while (e.hasMoreElements())
+ {
+ String value = e.nextElement();
+ if (value!=null)
+ {
+ tok = new QuotedStringTokenizer(value, separators, false, false);
+ if (tok.hasMoreElements()) return true;
+ }
+ }
+ tok = null;
+ return false;
+ }
+
+ @Override
+ public String nextElement() throws NoSuchElementException
+ {
+ if (!hasMoreElements()) throw new NoSuchElementException();
+ String next = (String) tok.nextElement();
+ if (next != null) next = next.trim();
+ return next;
+ }
+ };
+ }
+
+ public void put(HttpField field)
+ {
+ boolean put=false;
+ for (int i=_fields.size();i-->0;)
+ {
+ HttpField f=_fields.get(i);
+ if (f.isSame(field))
+ {
+ if (put)
+ _fields.remove(i);
+ else
+ {
+ _fields.set(i,field);
+ put=true;
+ }
+ }
+ }
+ if (!put)
+ _fields.add(field);
+ }
+
+ /**
+ * Set a field.
+ *
+ * @param name the name of the field
+ * @param value the value of the field. If null the field is cleared.
+ */
+ public void put(String name, String value)
+ {
+ if (value == null)
+ remove(name);
+ else
+ put(new HttpField(name, value));
+ }
+
+ public void put(HttpHeader header, HttpHeaderValue value)
+ {
+ put(header,value.toString());
+ }
+
+ /**
+ * Set a field.
+ *
+ * @param header the header name of the field
+ * @param value the value of the field. If null the field is cleared.
+ */
+ public void put(HttpHeader header, String value)
+ {
+ if (value == null)
+ remove(header);
+ else
+ put(new HttpField(header, value));
+ }
+
+ /**
+ * Set a field.
+ *
+ * @param name the name of the field
+ * @param list the List value of the field. If null the field is cleared.
+ */
+ public void put(String name, List<String> list)
+ {
+ remove(name);
+ for (String v : list)
+ if (v!=null)
+ add(name,v);
+ }
+
+ /**
+ * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+ * headers of the same name.
+ *
+ * @param name the name of the field
+ * @param value the value of the field.
+ * @exception IllegalArgumentException If the name is a single valued field and already has a
+ * value.
+ */
+ public void add(String name, String value) throws IllegalArgumentException
+ {
+ if (value == null)
+ return;
+
+ HttpField field = new HttpField(name, value);
+ _fields.add(field);
+ }
+
+ public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
+ {
+ add(header,value.toString());
+ }
+
+ /**
+ * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+ * headers of the same name.
+ *
+ * @param header the header
+ * @param value the value of the field.
+ * @exception IllegalArgumentException
+ */
+ public void add(HttpHeader header, String value) throws IllegalArgumentException
+ {
+ if (value == null) throw new IllegalArgumentException("null value");
+
+ HttpField field = new HttpField(header, value);
+ _fields.add(field);
+ }
+
+ /**
+ * Remove a field.
+ *
+ * @param name the field to remove
+ */
+ public HttpField remove(HttpHeader name)
+ {
+ for (int i=_fields.size();i-->0;)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getHeader()==name)
+ return _fields.remove(i);
+ }
+ return null;
+ }
+
+ /**
+ * Remove a field.
+ *
+ * @param name the field to remove
+ */
+ public HttpField remove(String name)
+ {
+ for (int i=_fields.size();i-->0;)
+ {
+ HttpField f=_fields.get(i);
+ if (f.getName().equalsIgnoreCase(name))
+ return _fields.remove(i);
+ }
+ return null;
+ }
+
+ /**
+ * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
+ * case of the field name is ignored.
+ *
+ * @param name the case-insensitive field name
+ * @exception NumberFormatException If bad long found
+ */
+ public long getLongField(String name) throws NumberFormatException
+ {
+ HttpField field = getField(name);
+ return field==null?-1L:StringUtil.toLong(field.getValue());
+ }
+
+ /**
+ * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
+ * of the field name is ignored.
+ *
+ * @param name the case-insensitive field name
+ */
+ public long getDateField(String name)
+ {
+ HttpField field = getField(name);
+ if (field == null)
+ return -1;
+
+ String val = valueParameters(field.getValue(), null);
+ if (val == null)
+ return -1;
+
+ final long date = DateParser.parseDate(val);
+ if (date==-1)
+ throw new IllegalArgumentException("Cannot convert date: " + val);
+ return date;
+ }
+
+
+ /**
+ * Sets the value of an long field.
+ *
+ * @param name the field name
+ * @param value the field long value
+ */
+ public void putLongField(HttpHeader name, long value)
+ {
+ String v = Long.toString(value);
+ put(name, v);
+ }
+
+ /**
+ * Sets the value of an long field.
+ *
+ * @param name the field name
+ * @param value the field long value
+ */
+ public void putLongField(String name, long value)
+ {
+ String v = Long.toString(value);
+ put(name, v);
+ }
+
+
+ /**
+ * Sets the value of a date field.
+ *
+ * @param name the field name
+ * @param date the field date value
+ */
+ public void putDateField(HttpHeader name, long date)
+ {
+ String d=DateGenerator.formatDate(date);
+ put(name, d);
+ }
+
+ /**
+ * Sets the value of a date field.
+ *
+ * @param name the field name
+ * @param date the field date value
+ */
+ public void putDateField(String name, long date)
+ {
+ String d=DateGenerator.formatDate(date);
+ put(name, d);
+ }
+
+ /**
+ * Sets the value of a date field.
+ *
+ * @param name the field name
+ * @param date the field date value
+ */
+ public void addDateField(String name, long date)
+ {
+ String d=DateGenerator.formatDate(date);
+ add(name,d);
+ }
+
+ @Override
+ public String
+ toString()
+ {
+ try
+ {
+ StringBuilder buffer = new StringBuilder();
+ for (HttpField field : _fields)
+ {
+ if (field != null)
+ {
+ String tmp = field.getName();
+ if (tmp != null) buffer.append(tmp);
+ buffer.append(": ");
+ tmp = field.getValue();
+ if (tmp != null) buffer.append(tmp);
+ buffer.append("\r\n");
+ }
+ }
+ buffer.append("\r\n");
+ return buffer.toString();
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ return e.toString();
+ }
+ }
+
+ /**
+ * Clear the header.
+ */
+ public void clear()
+ {
+ _fields.clear();
+ }
+
+ public void add(HttpField field)
+ {
+ _fields.add(field);
+ }
+
+
+
+ /**
+ * Add fields from another HttpFields instance. Single valued fields are replaced, while all
+ * others are added.
+ *
+ * @param fields the fields to add
+ */
+ public void add(HttpFields fields)
+ {
+ if (fields == null) return;
+
+ Enumeration<String> e = fields.getFieldNames();
+ while (e.hasMoreElements())
+ {
+ String name = e.nextElement();
+ Enumeration<String> values = fields.getValues(name);
+ while (values.hasMoreElements())
+ add(name, values.nextElement());
+ }
+ }
+
+ /**
+ * Get field value parameters. Some field values can have parameters. This method separates the
+ * value from the parameters and optionally populates a map with the parameters. For example:
+ *
+ * <PRE>
+ *
+ * FieldName : Value ; param1=val1 ; param2=val2
+ *
+ * </PRE>
+ *
+ * @param value The Field value, possibly with parameteres.
+ * @param parameters A map to populate with the parameters, or null
+ * @return The value.
+ */
+ public static String valueParameters(String value, Map<String,String> parameters)
+ {
+ if (value == null) return null;
+
+ int i = value.indexOf(';');
+ if (i < 0) return value;
+ if (parameters == null) return value.substring(0, i).trim();
+
+ StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
+ while (tok1.hasMoreTokens())
+ {
+ String token = tok1.nextToken();
+ StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
+ if (tok2.hasMoreTokens())
+ {
+ String paramName = tok2.nextToken();
+ String paramVal = null;
+ if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
+ parameters.put(paramName, paramVal);
+ }
+ }
+
+ return value.substring(0, i).trim();
+ }
+
+ private static final Float __one = new Float("1.0");
+ private static final Float __zero = new Float("0.0");
+ private static final Trie<Float> __qualities = new ArrayTernaryTrie<>();
+ static
+ {
+ __qualities.put("*", __one);
+ __qualities.put("1.0", __one);
+ __qualities.put("1", __one);
+ __qualities.put("0.9", new Float("0.9"));
+ __qualities.put("0.8", new Float("0.8"));
+ __qualities.put("0.7", new Float("0.7"));
+ __qualities.put("0.66", new Float("0.66"));
+ __qualities.put("0.6", new Float("0.6"));
+ __qualities.put("0.5", new Float("0.5"));
+ __qualities.put("0.4", new Float("0.4"));
+ __qualities.put("0.33", new Float("0.33"));
+ __qualities.put("0.3", new Float("0.3"));
+ __qualities.put("0.2", new Float("0.2"));
+ __qualities.put("0.1", new Float("0.1"));
+ __qualities.put("0", __zero);
+ __qualities.put("0.0", __zero);
+ }
+
+ public static Float getQuality(String value)
+ {
+ if (value == null) return __zero;
+
+ int qe = value.indexOf(";");
+ if (qe++ < 0 || qe == value.length()) return __one;
+
+ if (value.charAt(qe++) == 'q')
+ {
+ qe++;
+ Float q = __qualities.get(value, qe, value.length() - qe);
+ if (q != null)
+ return q;
+ }
+
+ Map<String,String> params = new HashMap<>(4);
+ valueParameters(value, params);
+ String qs = params.get("q");
+ if (qs==null)
+ qs="*";
+ Float q = __qualities.get(qs);
+ if (q == null)
+ {
+ try
+ {
+ q = new Float(qs);
+ }
+ catch (Exception e)
+ {
+ q = __one;
+ }
+ }
+ return q;
+ }
+
+ /**
+ * List values in quality order.
+ *
+ * @param e Enumeration of values with quality parameters
+ * @return values in quality order.
+ */
+ public static List<String> qualityList(Enumeration<String> e)
+ {
+ if (e == null || !e.hasMoreElements())
+ return Collections.emptyList();
+
+ Object list = null;
+ Object qual = null;
+
+ // Assume list will be well ordered and just add nonzero
+ while (e.hasMoreElements())
+ {
+ String v = e.nextElement();
+ Float q = getQuality(v);
+
+ if (q >= 0.001)
+ {
+ list = LazyList.add(list, v);
+ qual = LazyList.add(qual, q);
+ }
+ }
+
+ List<String> vl = LazyList.getList(list, false);
+ if (vl.size() < 2)
+ return vl;
+
+ List<Float> ql = LazyList.getList(qual, false);
+
+ // sort list with swaps
+ Float last = __zero;
+ for (int i = vl.size(); i-- > 0;)
+ {
+ Float q = ql.get(i);
+ if (last.compareTo(q) > 0)
+ {
+ String tmp = vl.get(i);
+ vl.set(i, vl.get(i + 1));
+ vl.set(i + 1, tmp);
+ ql.set(i, ql.get(i + 1));
+ ql.set(i + 1, q);
+ last = __zero;
+ i = vl.size();
+ continue;
+ }
+ last = q;
+ }
+ ql.clear();
+ return vl;
+ }
+
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * HttpGenerator. Builds HTTP Messages.
+ *
+ * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
+ * then the generator will strictly pass on the exact strings received from methods and header
+ * fields. Otherwise a fast case insensitive string lookup is used that may alter the
+ * case and white space of some methods/headers
+ * </p>
+ */
+public class HttpGenerator
+{
+ private final static Logger LOG = Log.getLogger(HttpGenerator.class);
+
+ public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");
+
+ private final static byte[] __colon_space = new byte[] {':',' '};
+ private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
+ public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false);
+ public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false);
+ public final static ResponseInfo RESPONSE_500_INFO =
+ new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0,HttpStatus.INTERNAL_SERVER_ERROR_500,null,false);
+
+ // states
+ public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
+ public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
+
+ // other statics
+ public static final int CHUNK_SIZE = 12;
+
+ private State _state = State.START;
+ private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+
+ private long _contentPrepared = 0;
+ private boolean _noContent = false;
+ private Boolean _persistent = null;
+
+ private final int _send;
+ private final static int SEND_SERVER = 0x01;
+ private final static int SEND_XPOWEREDBY = 0x02;
+
+
+ /* ------------------------------------------------------------------------------- */
+ public static void setJettyVersion(String serverVersion)
+ {
+ SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
+ SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
+ SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
+ serverVersion + "\015\012");
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ // data
+ private boolean _needCRLF = false;
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpGenerator()
+ {
+ this(false,false);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
+ {
+ _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void reset()
+ {
+ _state = State.START;
+ _endOfContent = EndOfContent.UNKNOWN_CONTENT;
+ _noContent=false;
+ _persistent = null;
+ _contentPrepared = 0;
+ _needCRLF = false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Deprecated
+ public boolean getSendServerVersion ()
+ {
+ return (_send&SEND_SERVER)!=0;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Deprecated
+ public void setSendServerVersion (boolean sendServerVersion)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public State getState()
+ {
+ return _state;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isState(State state)
+ {
+ return _state == state;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isIdle()
+ {
+ return _state == State.START;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isEnd()
+ {
+ return _state == State.END;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCommitted()
+ {
+ return _state.ordinal() >= State.COMMITTED.ordinal();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isChunking()
+ {
+ return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setPersistent(boolean persistent)
+ {
+ _persistent=persistent;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if known to be persistent
+ */
+ public boolean isPersistent()
+ {
+ return Boolean.TRUE.equals(_persistent);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isWritten()
+ {
+ return _contentPrepared>0;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentPrepared()
+ {
+ return _contentPrepared;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void abort()
+ {
+ _persistent=false;
+ _state=State.END;
+ _endOfContent=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Result generateRequest(RequestInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+ {
+ switch(_state)
+ {
+ case START:
+ {
+ if (info==null)
+ return Result.NEED_INFO;
+
+ // Do we need a request header
+ if (header==null)
+ return Result.NEED_HEADER;
+
+ // If we have not been told our persistence, set the default
+ if (_persistent==null)
+ _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+ // prepare the header
+ int pos=BufferUtil.flipToFill(header);
+ try
+ {
+ // generate ResponseLine
+ generateRequestLine(info,header);
+
+ if (info.getHttpVersion()==HttpVersion.HTTP_0_9)
+ _noContent=true;
+ else
+ generateHeaders(info,header,content,last);
+
+ boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+
+ if (expect100)
+ {
+ _state = State.COMMITTED;
+ }
+ else
+ {
+ // handle the content.
+ int len = BufferUtil.length(content);
+ if (len>0)
+ {
+ _contentPrepared+=len;
+ if (isChunking())
+ prepareChunk(header,len);
+ }
+ _state = last?State.COMPLETING:State.COMMITTED;
+ }
+
+ return Result.FLUSH;
+ }
+ catch(Exception e)
+ {
+ String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+ throw new IOException(message,e);
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(header,pos);
+ }
+ }
+
+ case COMMITTED:
+ {
+ int len = BufferUtil.length(content);
+
+ if (len>0)
+ {
+ // Do we need a chunk buffer?
+ if (isChunking())
+ {
+ // Do we need a chunk buffer?
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,len);
+ BufferUtil.flipToFlush(chunk,0);
+ }
+ _contentPrepared+=len;
+ }
+
+ if (last)
+ {
+ _state=State.COMPLETING;
+ return len>0?Result.FLUSH:Result.CONTINUE;
+ }
+
+ return Result.FLUSH;
+ }
+
+ case COMPLETING:
+ {
+ if (BufferUtil.hasContent(content))
+ {
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+
+ if (isChunking())
+ {
+ // Do we need a chunk buffer?
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,0);
+ BufferUtil.flipToFlush(chunk,0);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ return Result.FLUSH;
+ }
+
+ _state=State.END;
+ return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+ }
+
+ case END:
+ if (BufferUtil.hasContent(content))
+ {
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+ return Result.DONE;
+
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Result generateResponse(ResponseInfo info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
+ {
+ switch(_state)
+ {
+ case START:
+ {
+ if (info==null)
+ return Result.NEED_INFO;
+
+ // Handle 0.9
+ if (info.getHttpVersion() == HttpVersion.HTTP_0_9)
+ {
+ _persistent = false;
+ _endOfContent=EndOfContent.EOF_CONTENT;
+ if (BufferUtil.hasContent(content))
+ _contentPrepared+=content.remaining();
+ _state = last?State.COMPLETING:State.COMMITTED;
+ return Result.FLUSH;
+ }
+
+ // Do we need a response header
+ if (header==null)
+ return Result.NEED_HEADER;
+
+ // If we have not been told our persistence, set the default
+ if (_persistent==null)
+ _persistent=(info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
+
+ // prepare the header
+ int pos=BufferUtil.flipToFill(header);
+ try
+ {
+ // generate ResponseLine
+ generateResponseLine(info,header);
+
+ // Handle 1xx and no content responses
+ int status=info.getStatus();
+ if (status>=100 && status<200 )
+ {
+ _noContent=true;
+
+ if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
+ {
+ header.put(HttpTokens.CRLF);
+ _state=State.COMPLETING_1XX;
+ return Result.FLUSH;
+ }
+ }
+ else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
+ {
+ _noContent=true;
+ }
+
+ generateHeaders(info,header,content,last);
+
+ // handle the content.
+ int len = BufferUtil.length(content);
+ if (len>0)
+ {
+ _contentPrepared+=len;
+ if (isChunking() && !info.isHead())
+ prepareChunk(header,len);
+ }
+ _state = last?State.COMPLETING:State.COMMITTED;
+ }
+ catch(Exception e)
+ {
+ String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
+ throw new IOException(message,e);
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(header,pos);
+ }
+
+ return Result.FLUSH;
+ }
+
+ case COMMITTED:
+ {
+ int len = BufferUtil.length(content);
+
+ // handle the content.
+ if (len>0)
+ {
+ if (isChunking())
+ {
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,len);
+ BufferUtil.flipToFlush(chunk,0);
+ }
+ _contentPrepared+=len;
+ }
+
+ if (last)
+ {
+ _state=State.COMPLETING;
+ return len>0?Result.FLUSH:Result.CONTINUE;
+ }
+ return len>0?Result.FLUSH:Result.DONE;
+
+ }
+
+ case COMPLETING_1XX:
+ {
+ reset();
+ return Result.DONE;
+ }
+
+ case COMPLETING:
+ {
+ if (BufferUtil.hasContent(content))
+ {
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+
+ if (isChunking())
+ {
+ // Do we need a chunk buffer?
+ if (chunk==null)
+ return Result.NEED_CHUNK;
+
+ // Write the last chunk
+ BufferUtil.clearToFill(chunk);
+ prepareChunk(chunk,0);
+ BufferUtil.flipToFlush(chunk,0);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ return Result.FLUSH;
+ }
+
+ _state=State.END;
+
+ return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
+ }
+
+ case END:
+ if (BufferUtil.hasContent(content))
+ {
+ LOG.debug("discarding content in COMPLETING");
+ BufferUtil.clear(content);
+ }
+ return Result.DONE;
+
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void prepareChunk(ByteBuffer chunk, int remaining)
+ {
+ // if we need CRLF add this to header
+ if (_needCRLF)
+ BufferUtil.putCRLF(chunk);
+
+ // Add the chunk size to the header
+ if (remaining>0)
+ {
+ BufferUtil.putHexInt(chunk, remaining);
+ BufferUtil.putCRLF(chunk);
+ _needCRLF=true;
+ }
+ else
+ {
+ chunk.put(LAST_CHUNK);
+ _needCRLF=false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void generateRequestLine(RequestInfo request,ByteBuffer header)
+ {
+ header.put(StringUtil.getBytes(request.getMethod()));
+ header.put((byte)' ');
+ header.put(StringUtil.getBytes(request.getUri()));
+ switch(request.getHttpVersion())
+ {
+ case HTTP_1_0:
+ case HTTP_1_1:
+ header.put((byte)' ');
+ header.put(request.getHttpVersion().toBytes());
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ header.put(HttpTokens.CRLF);
+ }
+
+ /* ------------------------------------------------------------ */
+ private void generateResponseLine(ResponseInfo response, ByteBuffer header)
+ {
+ // Look for prepared response line
+ int status=response.getStatus();
+ PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
+ String reason=response.getReason();
+ if (preprepared!=null)
+ {
+ if (reason==null)
+ header.put(preprepared._responseLine);
+ else
+ {
+ header.put(preprepared._schemeCode);
+ header.put(getReasonBytes(reason));
+ header.put(HttpTokens.CRLF);
+ }
+ }
+ else // generate response line
+ {
+ header.put(HTTP_1_1_SPACE);
+ header.put((byte) ('0' + status / 100));
+ header.put((byte) ('0' + (status % 100) / 10));
+ header.put((byte) ('0' + (status % 10)));
+ header.put((byte) ' ');
+ if (reason==null)
+ {
+ header.put((byte) ('0' + status / 100));
+ header.put((byte) ('0' + (status % 100) / 10));
+ header.put((byte) ('0' + (status % 10)));
+ }
+ else
+ header.put(getReasonBytes(reason));
+ header.put(HttpTokens.CRLF);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private byte[] getReasonBytes(String reason)
+ {
+ if (reason.length()>1024)
+ reason=reason.substring(0,1024);
+ byte[] _bytes = StringUtil.getBytes(reason);
+
+ for (int i=_bytes.length;i-->0;)
+ if (_bytes[i]=='\r' || _bytes[i]=='\n')
+ _bytes[i]='?';
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void generateHeaders(Info _info,ByteBuffer header,ByteBuffer content,boolean last)
+ {
+ final RequestInfo request=(_info instanceof RequestInfo)?(RequestInfo)_info:null;
+ final ResponseInfo response=(_info instanceof ResponseInfo)?(ResponseInfo)_info:null;
+
+ // default field values
+ int send=_send;
+ HttpField transfer_encoding=null;
+ boolean keep_alive=false;
+ boolean close=false;
+ boolean content_type=false;
+ StringBuilder connection = null;
+
+ // Generate fields
+ if (_info.getHttpFields() != null)
+ {
+ for (HttpField field : _info.getHttpFields())
+ {
+ HttpHeader h = field.getHeader();
+
+ switch (h==null?HttpHeader.UNKNOWN:h)
+ {
+ case CONTENT_LENGTH:
+ // handle specially below
+ if (_info.getContentLength()>=0)
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ break;
+
+ case CONTENT_TYPE:
+ {
+ if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
+ _endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
+
+ // write the field to the header
+ content_type=true;
+ putTo(field,header);
+ break;
+ }
+
+ case TRANSFER_ENCODING:
+ {
+ if (_info.getHttpVersion() == HttpVersion.HTTP_1_1)
+ transfer_encoding = field;
+ // Do NOT add yet!
+ break;
+ }
+
+ case CONNECTION:
+ {
+ if (request!=null)
+ putTo(field,header);
+
+ // Lookup and/or split connection value field
+ HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
+ String[] split = null;
+
+ if (values[0]==null)
+ {
+ split = field.getValue().split("\\s*,\\s*");
+ if (split.length>0)
+ {
+ values=new HttpHeaderValue[split.length];
+ for (int i=0;i<split.length;i++)
+ values[i]=HttpHeaderValue.CACHE.get(split[i]);
+ }
+ }
+
+ // Handle connection values
+ for (int i=0;i<values.length;i++)
+ {
+ HttpHeaderValue value=values[i];
+ switch (value==null?HttpHeaderValue.UNKNOWN:value)
+ {
+ case UPGRADE:
+ {
+ // special case for websocket connection ordering
+ header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
+ header.put(CRLF);
+ break;
+ }
+
+ case CLOSE:
+ {
+ close=true;
+ if (response!=null)
+ {
+ _persistent=false;
+ if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+ _endOfContent=EndOfContent.EOF_CONTENT;
+ }
+ break;
+ }
+
+ case KEEP_ALIVE:
+ {
+ if (_info.getHttpVersion() == HttpVersion.HTTP_1_0)
+ {
+ keep_alive = true;
+ if (response!=null)
+ _persistent=true;
+ }
+ break;
+ }
+
+ default:
+ {
+ if (connection==null)
+ connection=new StringBuilder();
+ else
+ connection.append(',');
+ connection.append(split==null?field.getValue():split[i]);
+ }
+ }
+ }
+
+ // Do NOT add yet!
+ break;
+ }
+
+ case SERVER:
+ {
+ send=send&~SEND_SERVER;
+ putTo(field,header);
+ break;
+ }
+
+ default:
+ putTo(field,header);
+ }
+ }
+ }
+
+
+ // Calculate how to end _content and connection, _content length and transfer encoding
+ // settings.
+ // From RFC 2616 4.4:
+ // 1. No body for 1xx, 204, 304 & HEAD response
+ // 2. Force _content-length?
+ // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
+ // 4. Content-Length
+ // 5. multipart/byteranges
+ // 6. close
+ int status=response!=null?response.getStatus():-1;
+ switch (_endOfContent)
+ {
+ case UNKNOWN_CONTENT:
+ // It may be that we have no _content, or perhaps _content just has not been
+ // written yet?
+
+ // Response known not to have a body
+ if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304))
+ _endOfContent=EndOfContent.NO_CONTENT;
+ else if (_info.getContentLength()>0)
+ {
+ // we have been given a content length
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ long content_length = _info.getContentLength();
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
+ {
+ // known length but not actually set.
+ header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+ BufferUtil.putDecLong(header, content_length);
+ header.put(HttpTokens.CRLF);
+ }
+ }
+ else if (last)
+ {
+ // we have seen all the _content there is, so we can be content-length limited.
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ long content_length = _contentPrepared+BufferUtil.length(content);
+
+ // Do we need to tell the headers about it
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
+ {
+ header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+ BufferUtil.putDecLong(header, content_length);
+ header.put(HttpTokens.CRLF);
+ }
+ }
+ else
+ {
+ // No idea, so we must assume that a body is coming.
+ _endOfContent = EndOfContent.CHUNKED_CONTENT;
+ // HTTP 1.0 does not understand chunked content, so we must use EOF content.
+ // For a request with HTTP 1.0 & Connection: keep-alive
+ // we *must* close the connection, otherwise the client
+ // has no way to detect the end of the content.
+ if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
+ _endOfContent = EndOfContent.EOF_CONTENT;
+ }
+ break;
+
+ case CONTENT_LENGTH:
+ long content_length = _info.getContentLength();
+ if ((response!=null || content_length>0 || content_type ) && !_noContent)
+ {
+ // known length but not actually set.
+ header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
+ BufferUtil.putDecLong(header, content_length);
+ header.put(HttpTokens.CRLF);
+ }
+ break;
+
+ case NO_CONTENT:
+ if (response!=null && status >= 200 && status != 204 && status != 304)
+ header.put(CONTENT_LENGTH_0);
+ break;
+
+ case EOF_CONTENT:
+ _persistent = request!=null;
+ break;
+
+ case CHUNKED_CONTENT:
+ break;
+
+ default:
+ break;
+ }
+
+ // Add transfer_encoding if needed
+ if (isChunking())
+ {
+ // try to use user supplied encoding as it may have other values.
+ if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
+ {
+ String c = transfer_encoding.getValue();
+ if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
+ putTo(transfer_encoding,header);
+ else
+ throw new IllegalArgumentException("BAD TE");
+ }
+ else
+ header.put(TRANSFER_ENCODING_CHUNKED);
+ }
+
+ // Handle connection if need be
+ if (_endOfContent==EndOfContent.EOF_CONTENT)
+ {
+ keep_alive=false;
+ _persistent=false;
+ }
+
+ // If this is a response, work out persistence
+ if (response!=null)
+ {
+ if (!isPersistent() && (close || _info.getHttpVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
+ {
+ if (connection==null)
+ header.put(CONNECTION_CLOSE);
+ else
+ {
+ header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
+ header.put((byte)',');
+ header.put(StringUtil.getBytes(connection.toString()));
+ header.put(CRLF);
+ }
+ }
+ else if (keep_alive)
+ {
+ if (connection==null)
+ header.put(CONNECTION_KEEP_ALIVE);
+ else
+ {
+ header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
+ header.put((byte)',');
+ header.put(StringUtil.getBytes(connection.toString()));
+ header.put(CRLF);
+ }
+ }
+ else if (connection!=null)
+ {
+ header.put(HttpHeader.CONNECTION.getBytesColonSpace());
+ header.put(StringUtil.getBytes(connection.toString()));
+ header.put(CRLF);
+ }
+ }
+
+ if (status>199)
+ header.put(SEND[send]);
+
+ // end the header.
+ header.put(HttpTokens.CRLF);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public static byte[] getReasonBuffer(int code)
+ {
+ PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
+ if (status!=null)
+ return status._reason;
+ return null;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return String.format("%s{s=%s}",
+ getClass().getSimpleName(),
+ _state);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ // common _content
+ private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
+ private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
+ private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
+ private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
+ private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
+ private static final byte[] CRLF = StringUtil.getBytes("\015\012");
+ private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
+ private static final byte[][] SEND = new byte[][]{
+ new byte[0],
+ StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
+ StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
+ StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
+ };
+
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ // Build cache of response lines for status
+ private static class PreparedResponse
+ {
+ byte[] _reason;
+ byte[] _schemeCode;
+ byte[] _responseLine;
+ }
+ private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
+ static
+ {
+ int versionLength=HttpVersion.HTTP_1_1.toString().length();
+
+ for (int i=0;i<__preprepared.length;i++)
+ {
+ HttpStatus.Code code = HttpStatus.getCode(i);
+ if (code==null)
+ continue;
+ String reason=code.getMessage();
+ byte[] line=new byte[versionLength+5+reason.length()+2];
+ HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
+ line[versionLength+0]=' ';
+ line[versionLength+1]=(byte)('0'+i/100);
+ line[versionLength+2]=(byte)('0'+(i%100)/10);
+ line[versionLength+3]=(byte)('0'+(i%10));
+ line[versionLength+4]=' ';
+ for (int j=0;j<reason.length();j++)
+ line[versionLength+5+j]=(byte)reason.charAt(j);
+ line[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
+ line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
+
+ __preprepared[i] = new PreparedResponse();
+ __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
+ __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
+ __preprepared[i]._responseLine=line;
+ }
+ }
+
+ public static class Info
+ {
+ final HttpVersion _httpVersion;
+ final HttpFields _httpFields;
+ final long _contentLength;
+
+ private Info(HttpVersion httpVersion, HttpFields httpFields, long contentLength)
+ {
+ _httpVersion = httpVersion;
+ _httpFields = httpFields;
+ _contentLength = contentLength;
+ }
+
+ public HttpVersion getHttpVersion()
+ {
+ return _httpVersion;
+ }
+ public HttpFields getHttpFields()
+ {
+ return _httpFields;
+ }
+ public long getContentLength()
+ {
+ return _contentLength;
+ }
+ }
+
+ public static class RequestInfo extends Info
+ {
+ private final String _method;
+ private final String _uri;
+
+ public RequestInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, String method, String uri)
+ {
+ super(httpVersion,httpFields,contentLength);
+ _method = method;
+ _uri = uri;
+ }
+
+ public String getMethod()
+ {
+ return _method;
+ }
+
+ public String getUri()
+ {
+ return _uri;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("RequestInfo{%s %s %s,%d}",_method,_uri,_httpVersion,_contentLength);
+ }
+ }
+
+ public static class ResponseInfo extends Info
+ {
+ private final int _status;
+ private final String _reason;
+ private final boolean _head;
+
+ public ResponseInfo(HttpVersion httpVersion, HttpFields httpFields, long contentLength, int status, String reason, boolean head)
+ {
+ super(httpVersion,httpFields,contentLength);
+ _status = status;
+ _reason = reason;
+ _head = head;
+ }
+
+ public boolean isInformational()
+ {
+ return _status>=100 && _status<200;
+ }
+
+ public int getStatus()
+ {
+ return _status;
+ }
+
+ public String getReason()
+ {
+ return _reason;
+ }
+
+ public boolean isHead()
+ {
+ return _head;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head);
+ }
+ }
+
+ private static void putSanitisedName(String s,ByteBuffer buffer)
+ {
+ int l=s.length();
+ for (int i=0;i<l;i++)
+ {
+ char c=s.charAt(i);
+
+ if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
+ buffer.put((byte)'?');
+ else
+ buffer.put((byte)(0xff&c));
+ }
+ }
+
+ private static void putSanitisedValue(String s,ByteBuffer buffer)
+ {
+ int l=s.length();
+ for (int i=0;i<l;i++)
+ {
+ char c=s.charAt(i);
+
+ if (c<0 || c>0xff || c=='\r' || c=='\n')
+ buffer.put((byte)'?');
+ else
+ buffer.put((byte)(0xff&c));
+ }
+ }
+
+ public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
+ {
+ if (field instanceof CachedHttpField)
+ {
+ ((CachedHttpField)field).putTo(bufferInFillMode);
+ }
+ else
+ {
+ HttpHeader header=field.getHeader();
+ if (header!=null)
+ {
+ bufferInFillMode.put(header.getBytesColonSpace());
+ putSanitisedValue(field.getValue(),bufferInFillMode);
+ }
+ else
+ {
+ putSanitisedName(field.getName(),bufferInFillMode);
+ bufferInFillMode.put(__colon_space);
+ putSanitisedValue(field.getValue(),bufferInFillMode);
+ }
+
+ BufferUtil.putCRLF(bufferInFillMode);
+ }
+ }
+
+ public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode)
+ {
+ for (HttpField field : fields)
+ {
+ if (field != null)
+ putTo(field,bufferInFillMode);
+ }
+ BufferUtil.putCRLF(bufferInFillMode);
+ }
+
+ public static class CachedHttpField extends HttpField
+ {
+ private final byte[] _bytes;
+ public CachedHttpField(HttpHeader header,String value)
+ {
+ super(header,value);
+ int cbl=header.getBytesColonSpace().length;
+ _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
+ System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
+ _bytes[_bytes.length-2]=(byte)'\r';
+ _bytes[_bytes.length-1]=(byte)'\n';
+ }
+
+ public void putTo(ByteBuffer bufferInFillMode)
+ {
+ bufferInFillMode.put(_bytes);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+public enum HttpHeader
+{
+ /* ------------------------------------------------------------ */
+ /** General Fields.
+ */
+ CONNECTION("Connection"),
+ CACHE_CONTROL("Cache-Control"),
+ DATE("Date"),
+ PRAGMA("Pragma"),
+ PROXY_CONNECTION ("Proxy-Connection"),
+ TRAILER("Trailer"),
+ TRANSFER_ENCODING("Transfer-Encoding"),
+ UPGRADE("Upgrade"),
+ VIA("Via"),
+ WARNING("Warning"),
+ NEGOTIATE("Negotiate"),
+
+ /* ------------------------------------------------------------ */
+ /** Entity Fields.
+ */
+ ALLOW("Allow"),
+ CONTENT_ENCODING("Content-Encoding"),
+ CONTENT_LANGUAGE("Content-Language"),
+ CONTENT_LENGTH("Content-Length"),
+ CONTENT_LOCATION("Content-Location"),
+ CONTENT_MD5("Content-MD5"),
+ CONTENT_RANGE("Content-Range"),
+ CONTENT_TYPE("Content-Type"),
+ EXPIRES("Expires"),
+ LAST_MODIFIED("Last-Modified"),
+
+ /* ------------------------------------------------------------ */
+ /** Request Fields.
+ */
+ ACCEPT("Accept"),
+ ACCEPT_CHARSET("Accept-Charset"),
+ ACCEPT_ENCODING("Accept-Encoding"),
+ ACCEPT_LANGUAGE("Accept-Language"),
+ AUTHORIZATION("Authorization"),
+ EXPECT("Expect"),
+ FORWARDED("Forwarded"),
+ FROM("From"),
+ HOST("Host"),
+ IF_MATCH("If-Match"),
+ IF_MODIFIED_SINCE("If-Modified-Since"),
+ IF_NONE_MATCH("If-None-Match"),
+ IF_RANGE("If-Range"),
+ IF_UNMODIFIED_SINCE("If-Unmodified-Since"),
+ KEEP_ALIVE("Keep-Alive"),
+ MAX_FORWARDS("Max-Forwards"),
+ PROXY_AUTHORIZATION("Proxy-Authorization"),
+ RANGE("Range"),
+ REQUEST_RANGE("Request-Range"),
+ REFERER("Referer"),
+ TE("TE"),
+ USER_AGENT("User-Agent"),
+ X_FORWARDED_FOR("X-Forwarded-For"),
+ X_FORWARDED_PROTO("X-Forwarded-Proto"),
+ X_FORWARDED_SERVER("X-Forwarded-Server"),
+ X_FORWARDED_HOST("X-Forwarded-Host"),
+
+ /* ------------------------------------------------------------ */
+ /** Response Fields.
+ */
+ ACCEPT_RANGES("Accept-Ranges"),
+ AGE("Age"),
+ ETAG("ETag"),
+ LOCATION("Location"),
+ PROXY_AUTHENTICATE("Proxy-Authenticate"),
+ RETRY_AFTER("Retry-After"),
+ SERVER("Server"),
+ SERVLET_ENGINE("Servlet-Engine"),
+ VARY("Vary"),
+ WWW_AUTHENTICATE("WWW-Authenticate"),
+
+ /* ------------------------------------------------------------ */
+ /** Other Fields.
+ */
+ COOKIE("Cookie"),
+ SET_COOKIE("Set-Cookie"),
+ SET_COOKIE2("Set-Cookie2"),
+ MIME_VERSION("MIME-Version"),
+ IDENTITY("identity"),
+
+ X_POWERED_BY("X-Powered-By"),
+
+ UNKNOWN("::UNKNOWN::");
+
+
+ /* ------------------------------------------------------------ */
+ public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(512);
+ static
+ {
+ for (HttpHeader header : HttpHeader.values())
+ if (header!=UNKNOWN)
+ CACHE.put(header.toString(),header);
+ }
+
+ private final String _string;
+ private final byte[] _bytes;
+ private final byte[] _bytesColonSpace;
+ private final ByteBuffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ HttpHeader(String s)
+ {
+ _string=s;
+ _bytes=StringUtil.getBytes(s);
+ _bytesColonSpace=StringUtil.getBytes(s+": ");
+ _buffer=ByteBuffer.wrap(_bytes);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer toBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBytes()
+ {
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBytesColonSpace()
+ {
+ return _bytesColonSpace;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+}
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/**
+ *
+ */
+public enum HttpHeaderValue
+{
+ CLOSE("close"),
+ CHUNKED("chunked"),
+ GZIP("gzip"),
+ IDENTITY("identity"),
+ KEEP_ALIVE("keep-alive"),
+ CONTINUE("100-continue"),
+ PROCESSING("102-processing"),
+ TE("TE"),
+ BYTES("bytes"),
+ NO_CACHE("no-cache"),
+ UPGRADE("Upgrade"),
+ UNKNOWN("::UNKNOWN::");
+
+ /* ------------------------------------------------------------ */
+ public final static Trie<HttpHeaderValue> CACHE= new ArrayTrie<HttpHeaderValue>();
+ static
+ {
+ for (HttpHeaderValue value : HttpHeaderValue.values())
+ if (value!=UNKNOWN)
+ CACHE.put(value.toString(),value);
+ }
+
+ private final String _string;
+ private final ByteBuffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ HttpHeaderValue(String s)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer toBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ private static EnumSet<HttpHeader> __known =
+ EnumSet.of(HttpHeader.CONNECTION,
+ HttpHeader.TRANSFER_ENCODING,
+ HttpHeader.CONTENT_ENCODING);
+
+ /* ------------------------------------------------------------ */
+ public static boolean hasKnownValues(HttpHeader header)
+ {
+ if (header==null)
+ return false;
+ return __known.contains(header);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpMethod
+{
+ GET,
+ POST,
+ HEAD,
+ PUT,
+ OPTIONS,
+ DELETE,
+ TRACE,
+ CONNECT,
+ MOVE,
+ PROXY;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a method name and trailing space in a byte array.
+ * @param bytes Array containing ISO-8859-1 characters
+ * @param position The first valid index
+ * @param limit The first non valid index
+ * @return A HttpMethod if a match or null if no easy match.
+ */
+ public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit)
+ {
+ int length=limit-position;
+ if (length<4)
+ return null;
+ switch(bytes[position])
+ {
+ case 'G':
+ if (bytes[position+1]=='E' && bytes[position+2]=='T' && bytes[position+3]==' ')
+ return GET;
+ break;
+ case 'P':
+ if (bytes[position+1]=='O' && bytes[position+2]=='S' && bytes[position+3]=='T' && length>=5 && bytes[position+4]==' ')
+ return POST;
+ if (bytes[position+1]=='R' && bytes[position+2]=='O' && bytes[position+3]=='X' && length>=6 && bytes[position+4]=='Y' && bytes[position+5]==' ')
+ return PROXY;
+ if (bytes[position+1]=='U' && bytes[position+2]=='T' && bytes[position+3]==' ')
+ return PUT;
+ break;
+ case 'H':
+ if (bytes[position+1]=='E' && bytes[position+2]=='A' && bytes[position+3]=='D' && length>=5 && bytes[position+4]==' ')
+ return HEAD;
+ break;
+ case 'O':
+ if (bytes[position+1]=='O' && bytes[position+2]=='T' && bytes[position+3]=='I' && length>=8 &&
+ bytes[position+4]=='O' && bytes[position+5]=='N' && bytes[position+6]=='S' && bytes[position+7]==' ' )
+ return OPTIONS;
+ break;
+ case 'D':
+ if (bytes[position+1]=='E' && bytes[position+2]=='L' && bytes[position+3]=='E' && length>=7 &&
+ bytes[position+4]=='T' && bytes[position+5]=='E' && bytes[position+6]==' ' )
+ return DELETE;
+ break;
+ case 'T':
+ if (bytes[position+1]=='R' && bytes[position+2]=='A' && bytes[position+3]=='C' && length>=6 &&
+ bytes[position+4]=='E' && bytes[position+5]==' ' )
+ return TRACE;
+ break;
+ case 'C':
+ if (bytes[position+1]=='O' && bytes[position+2]=='N' && bytes[position+3]=='N' && length>=8 &&
+ bytes[position+4]=='E' && bytes[position+5]=='C' && bytes[position+6]=='T' && bytes[position+7]==' ' )
+ return CONNECT;
+ break;
+ case 'M':
+ if (bytes[position+1]=='O' && bytes[position+2]=='V' && bytes[position+3]=='E' && bytes[position+4]==' ')
+ return MOVE;
+ break;
+
+ default:
+ break;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a method name and trailing space in a byte array.
+ * @param buffer buffer containing ISO-8859-1 characters
+ * @return A HttpMethod if a match or null if no easy match.
+ */
+ public static HttpMethod lookAheadGet(ByteBuffer buffer)
+ {
+ if (buffer.hasArray())
+ return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+
+ // TODO use cache and check for space
+ // return CACHE.getBest(buffer,0,buffer.remaining());
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public final static Trie<HttpMethod> CACHE= new ArrayTrie<>();
+ static
+ {
+ for (HttpMethod method : HttpMethod.values())
+ CACHE.put(method.toString(),method);
+ }
+
+ /* ------------------------------------------------------------ */
+ private final ByteBuffer _buffer;
+ private final byte[] _bytes;
+
+ /* ------------------------------------------------------------ */
+ HttpMethod()
+ {
+ _bytes=StringUtil.getBytes(toString());
+ _buffer=ByteBuffer.wrap(_bytes);
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBytes()
+ {
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return toString().equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer asBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return toString();
+ }
+
+ /**
+ * Converts the given String parameter to an HttpMethod
+ * @param method the String to get the equivalent HttpMethod from
+ * @return the HttpMethod or null if the parameter method is unknown
+ */
+ public static HttpMethod fromString(String method)
+ {
+ return CACHE.get(method);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A Parser for HTTP 0.9, 1.0 and 1.1
+ * <p>
+ * The is parser parses HTTP client and server messages from buffers
+ * passed in the {@link #parseNext(ByteBuffer)} method. The parsed
+ * elements of the HTTP message are passed as event calls to the
+ * {@link HttpHandler} instance the parser is constructed with.
+ * If the passed handler is a {@link RequestHandler} then server side
+ * parsing is performed and if it is a {@link ResponseHandler}, then
+ * client side parsing is done.
+ * </p>
+ * <p>
+ * The contract of the {@link HttpHandler} API is that if a call returns
+ * true then the call to {@link #parseNext(ByteBuffer)} will return as
+ * soon as possible also with a true response. Typically this indicates
+ * that the parsing has reached a stage where the caller should process
+ * the events accumulated by the handler. It is the preferred calling
+ * style that handling such as calling a servlet to process a request,
+ * should be done after a true return from {@link #parseNext(ByteBuffer)}
+ * rather than from within the scope of a call like
+ * {@link RequestHandler#messageComplete()}
+ * </p>
+ * <p>
+ * For performance, the parse is heavily dependent on the
+ * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
+ * single pass for both the structure ( : and CRLF ) and semantic (which
+ * header and value) of a header. Specifically the static {@link HttpHeader#CACHE}
+ * is used to lookup common combinations of headers and values
+ * (eg. "Connection: close"), or just header names (eg. "Connection:" ).
+ * For headers who's value is not known statically (eg. Host, COOKIE) then a
+ * per parser dynamic Trie of {@link HttpFields} from previous parsed messages
+ * is used to help the parsing of subsequent messages.
+ * </p>
+ * <p>
+ * If the system property "org.eclipse.jetty.http.HttpParser.STRICT" is set to true,
+ * then the parser will strictly pass on the exact strings received for methods and header
+ * fields. Otherwise a fast case insensitive string lookup is used that may alter the
+ * case of the method and/or headers
+ * </p>
+ */
+public class HttpParser
+{
+ public static final Logger LOG = Log.getLogger(HttpParser.class);
+ public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT");
+ public final static int INITIAL_URI_LENGTH=256;
+
+ /**
+ * Cache of common {@link HttpField}s including: <UL>
+ * <LI>Common static combinations such as:<UL>
+ * <li>Connection: close
+ * <li>Accept-Encoding: gzip
+ * <li>Content-Length: 0
+ * </ul>
+ * <li>Combinations of Content-Type header for common mime types by common charsets
+ * <li>Most common headers with null values so that a lookup will at least
+ * determine the header name even if the name:value combination is not cached
+ * </ul>
+ */
+ public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
+
+ // States
+ public enum State
+ {
+ START,
+ METHOD,
+ RESPONSE_VERSION,
+ SPACE1,
+ STATUS,
+ URI,
+ SPACE2,
+ REQUEST_VERSION,
+ REASON,
+ PROXY,
+ HEADER,
+ HEADER_IN_NAME,
+ HEADER_VALUE,
+ HEADER_IN_VALUE,
+ CONTENT,
+ EOF_CONTENT,
+ CHUNKED_CONTENT,
+ CHUNK_SIZE,
+ CHUNK_PARAMS,
+ CHUNK,
+ END,
+ CLOSED
+ }
+
+ private final boolean DEBUG=LOG.isDebugEnabled(); // Cache debug to help branch prediction
+ private final HttpHandler<ByteBuffer> _handler;
+ private final RequestHandler<ByteBuffer> _requestHandler;
+ private final ResponseHandler<ByteBuffer> _responseHandler;
+ private final int _maxHeaderBytes;
+ private final boolean _strict;
+ private HttpField _field;
+ private HttpHeader _header;
+ private String _headerString;
+ private HttpHeaderValue _value;
+ private String _valueString;
+ private int _responseStatus;
+ private int _headerBytes;
+ private boolean _host;
+
+ /* ------------------------------------------------------------------------------- */
+ private volatile State _state=State.START;
+ private volatile boolean _eof;
+ private volatile boolean _closed;
+ private HttpMethod _method;
+ private String _methodString;
+ private HttpVersion _version;
+ private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune?
+ private EndOfContent _endOfContent;
+ private long _contentLength;
+ private long _contentPosition;
+ private int _chunkLength;
+ private int _chunkPosition;
+ private boolean _headResponse;
+ private boolean _cr;
+ private ByteBuffer _contentChunk;
+ private Trie<HttpField> _connectionFields;
+
+ private int _length;
+ private final StringBuilder _string=new StringBuilder();
+
+ static
+ {
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
+ CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
+ CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
+ CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
+
+ // Add common Content types as fields
+ for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/json","application/x-www-form-urlencoded"})
+ {
+ HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type);
+ CACHE.put(field);
+
+ for (String charset : new String[]{"UTF-8","ISO-8859-1"})
+ {
+ CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
+ CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
+ }
+ }
+
+ // Add headers with null values so HttpParser can avoid looking up name again for unknown values
+ for (HttpHeader h:HttpHeader.values())
+ if (!CACHE.put(new HttpField(h,(String)null)))
+ throw new IllegalStateException("CACHE FULL");
+ // Add some more common headers
+ CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
+ CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
+ CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler<ByteBuffer> handler)
+ {
+ this(handler,-1,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler<ByteBuffer> handler)
+ {
+ this(handler,-1,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+ {
+ _handler=handler;
+ _requestHandler=handler;
+ _responseHandler=null;
+ _maxHeaderBytes=maxHeaderBytes;
+ _strict=strict;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+ {
+ _handler=handler;
+ _requestHandler=null;
+ _responseHandler=handler;
+ _maxHeaderBytes=maxHeaderBytes;
+ _strict=strict;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public long getContentLength()
+ {
+ return _contentLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentRead()
+ {
+ return _contentPosition;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set if a HEAD response is expected
+ * @param head
+ */
+ public void setHeadResponse(boolean head)
+ {
+ _headResponse=head;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected void setResponseStatus(int status)
+ {
+ _responseStatus=status;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public State getState()
+ {
+ return _state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean inContentState()
+ {
+ return _state.ordinal()>=State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean inHeaderState()
+ {
+ return _state.ordinal() < State.CONTENT.ordinal();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean isChunking()
+ {
+ return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isStart()
+ {
+ return isState(State.START);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isClosed()
+ {
+ return isState(State.CLOSED);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isIdle()
+ {
+ return isState(State.START)||isState(State.END)||isState(State.CLOSED);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isComplete()
+ {
+ return isState(State.END)||isState(State.CLOSED);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean isState(State state)
+ {
+ return _state == state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private static class BadMessage extends Error
+ {
+ private static final long serialVersionUID = 1L;
+ private final int _code;
+ private final String _message;
+
+ BadMessage()
+ {
+ this(400,null);
+ }
+
+ BadMessage(int code)
+ {
+ this(code,null);
+ }
+
+ BadMessage(String message)
+ {
+ this(400,message);
+ }
+
+ BadMessage(int code,String message)
+ {
+ _code=code;
+ _message=message;
+ }
+
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private byte next(ByteBuffer buffer)
+ {
+ byte ch = buffer.get();
+
+ if (_cr)
+ {
+ if (ch!=HttpTokens.LINE_FEED)
+ throw new BadMessage("Bad EOL");
+ _cr=false;
+ return ch;
+ }
+
+ if (ch>=0 && ch<HttpTokens.SPACE)
+ {
+ if (ch==HttpTokens.CARRIAGE_RETURN)
+ {
+ if (buffer.hasRemaining())
+ {
+ if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
+ _headerBytes++;
+ ch=buffer.get();
+ if (ch!=HttpTokens.LINE_FEED)
+ throw new BadMessage("Bad EOL");
+ }
+ else
+ {
+ _cr=true;
+ // Can return 0 here to indicate the need for more characters,
+ // because a real 0 in the buffer would cause a BadMessage below
+ return 0;
+ }
+ }
+ // Only LF or TAB acceptable special characters
+ else if (!(ch==HttpTokens.LINE_FEED || ch==HttpTokens.TAB))
+ throw new BadMessage("Illegal character");
+ }
+
+ return ch;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* Quick lookahead for the start state looking for a request method or a HTTP version,
+ * otherwise skip white space until something else to parse.
+ */
+ private boolean quickStart(ByteBuffer buffer)
+ {
+ if (_requestHandler!=null)
+ {
+ _method = HttpMethod.lookAheadGet(buffer);
+ if (_method!=null)
+ {
+ _methodString = _method.asString();
+ buffer.position(buffer.position()+_methodString.length()+1);
+
+ setState(State.SPACE1);
+ return false;
+ }
+ }
+ else if (_responseHandler!=null)
+ {
+ _version = HttpVersion.lookAheadGet(buffer);
+ if (_version!=null)
+ {
+ buffer.position(buffer.position()+_version.asString().length()+1);
+ setState(State.SPACE1);
+ return false;
+ }
+ }
+
+ // Quick start look
+ while (_state==State.START && buffer.hasRemaining())
+ {
+ int ch=next(buffer);
+
+ if (ch > HttpTokens.SPACE)
+ {
+ _string.setLength(0);
+ _string.append((char)ch);
+ setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
+ return false;
+ }
+ else if (ch==0)
+ break;
+ else if (ch<0)
+ throw new BadMessage();
+
+ // count this white space as a header byte to avoid DOS
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("padding is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400);
+ }
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private void setString(String s)
+ {
+ _string.setLength(0);
+ _string.append(s);
+ _length=s.length();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private String takeString()
+ {
+ _string.setLength(_length);
+ String s =_string.toString();
+ _string.setLength(0);
+ _length=-1;
+ return s;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* Parse a request or response line
+ */
+ private boolean parseLine(ByteBuffer buffer)
+ {
+ boolean handle=false;
+
+ // Process headers
+ while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !handle)
+ {
+ // process each character
+ byte ch=next(buffer);
+ if (ch==0)
+ break;
+
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ if (_state==State.URI)
+ {
+ LOG.warn("URI is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ }
+ else
+ {
+ if (_requestHandler!=null)
+ LOG.warn("request is too large >"+_maxHeaderBytes);
+ else
+ LOG.warn("response is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ }
+ }
+
+ switch (_state)
+ {
+ case METHOD:
+ if (ch == HttpTokens.SPACE)
+ {
+ _length=_string.length();
+ _methodString=takeString();
+ HttpMethod method=HttpMethod.CACHE.get(_methodString);
+ if (method!=null && !_strict)
+ _methodString=method.asString();
+ setState(State.SPACE1);
+ }
+ else if (ch < HttpTokens.SPACE)
+ throw new BadMessage(ch<0?"Illegal character":"No URI");
+ else
+ _string.append((char)ch);
+ break;
+
+ case RESPONSE_VERSION:
+ if (ch == HttpTokens.SPACE)
+ {
+ _length=_string.length();
+ String version=takeString();
+ _version=HttpVersion.CACHE.get(version);
+ if (_version==null)
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+ setState(State.SPACE1);
+ }
+ else if (ch < HttpTokens.SPACE)
+ throw new BadMessage(ch<0?"Illegal character":"No Status");
+ else
+ _string.append((char)ch);
+ break;
+
+ case SPACE1:
+ if (ch > HttpTokens.SPACE || ch<0)
+ {
+ if (_responseHandler!=null)
+ {
+ setState(State.STATUS);
+ setResponseStatus(ch-'0');
+ }
+ else
+ {
+ _uri.clear();
+ setState(State.URI);
+ // quick scan for space or EoBuffer
+ if (buffer.hasArray())
+ {
+ byte[] array=buffer.array();
+ int p=buffer.arrayOffset()+buffer.position();
+ int l=buffer.arrayOffset()+buffer.limit();
+ int i=p;
+ while (i<l && array[i]>HttpTokens.SPACE)
+ i++;
+
+ int len=i-p;
+ _headerBytes+=len;
+
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("URI is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ }
+ if (_uri.remaining()<=len)
+ {
+ ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()+2*len);
+ _uri.flip();
+ uri.put(_uri);
+ _uri=uri;
+ }
+ _uri.put(array,p-1,len+1);
+ buffer.position(i-buffer.arrayOffset());
+ }
+ else
+ _uri.put(ch);
+ }
+ }
+ else if (ch < HttpTokens.SPACE)
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
+ }
+ break;
+
+ case STATUS:
+ if (ch == HttpTokens.SPACE)
+ {
+ setState(State.SPACE2);
+ }
+ else if (ch>='0' && ch<='9')
+ {
+ _responseStatus=_responseStatus*10+(ch-'0');
+ }
+ else if (ch < HttpTokens.SPACE && ch>=0)
+ {
+ handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+ setState(State.HEADER);
+ }
+ else
+ {
+ throw new BadMessage();
+ }
+ break;
+
+ case URI:
+ if (ch == HttpTokens.SPACE)
+ {
+ setState(State.SPACE2);
+ }
+ else if (ch < HttpTokens.SPACE && ch>=0)
+ {
+ // HTTP/0.9
+ _uri.flip();
+ handle=_requestHandler.startRequest(_method,_methodString,_uri,null)||handle;
+ setState(State.END);
+ BufferUtil.clear(buffer);
+ handle=_handler.headerComplete()||handle;
+ handle=_handler.messageComplete()||handle;
+ }
+ else
+ {
+ if (!_uri.hasRemaining())
+ {
+ ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()*2);
+ _uri.flip();
+ uri.put(_uri);
+ _uri=uri;
+ }
+ _uri.put(ch);
+ }
+ break;
+
+ case SPACE2:
+ if (ch > HttpTokens.SPACE)
+ {
+ _string.setLength(0);
+ _string.append((char)ch);
+ if (_responseHandler!=null)
+ {
+ _length=1;
+ setState(State.REASON);
+ }
+ else
+ {
+ setState(State.REQUEST_VERSION);
+
+ // try quick look ahead for HTTP Version
+ HttpVersion version;
+ if (buffer.position()>0 && buffer.hasArray())
+ version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+ else
+ version=HttpVersion.CACHE.getBest(buffer,0,buffer.remaining());
+ if (version==null)
+ {
+ if (_method==HttpMethod.PROXY)
+ {
+ if (!(_requestHandler instanceof ProxyHandler))
+ throw new BadMessage();
+
+ _uri.flip();
+ String protocol=BufferUtil.toString(_uri);
+ // This is the proxy protocol, so we can assume entire first line is in buffer else 400
+ buffer.position(buffer.position()-1);
+ String sAddr = getProxyField(buffer);
+ String dAddr = getProxyField(buffer);
+ int sPort = BufferUtil.takeInt(buffer);
+ next(buffer);
+ int dPort = BufferUtil.takeInt(buffer);
+ next(buffer);
+ _state=State.START;
+ ((ProxyHandler)_requestHandler).proxied(protocol,sAddr,dAddr,sPort,dPort);
+ return false;
+ }
+ }
+ else
+ {
+ int pos = buffer.position()+version.asString().length()-1;
+ if (pos<buffer.limit())
+ {
+ byte n=buffer.get(pos);
+ if (n==HttpTokens.CARRIAGE_RETURN)
+ {
+ _cr=true;
+ _version=version;
+ _string.setLength(0);
+ buffer.position(pos+1);
+ }
+ else if (n==HttpTokens.LINE_FEED)
+ {
+ _version=version;
+ _string.setLength(0);
+ buffer.position(pos);
+ }
+ }
+ }
+ }
+ }
+ else if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_responseHandler!=null)
+ {
+ handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+ setState(State.HEADER);
+ }
+ else
+ {
+ // HTTP/0.9
+ _uri.flip();
+ handle=_requestHandler.startRequest(_method,_methodString,_uri, null)||handle;
+ setState(State.END);
+ BufferUtil.clear(buffer);
+ handle=_handler.headerComplete()||handle;
+ handle=_handler.messageComplete()||handle;
+ }
+ }
+ else if (ch<0)
+ throw new BadMessage();
+ break;
+
+ case REQUEST_VERSION:
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_version==null)
+ {
+ _length=_string.length();
+ _version=HttpVersion.CACHE.get(takeString());
+ }
+ if (_version==null)
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+
+ // Should we try to cache header fields?
+ if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+ {
+ int header_cache = _handler.getHeaderCacheSize();
+ _connectionFields=new ArrayTernaryTrie<>(header_cache);
+ }
+
+ setState(State.HEADER);
+ _uri.flip();
+ handle=_requestHandler.startRequest(_method,_methodString,_uri, _version)||handle;
+ continue;
+ }
+ else if (ch>=HttpTokens.SPACE)
+ _string.append((char)ch);
+ else
+ throw new BadMessage();
+
+ break;
+
+ case REASON:
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ String reason=takeString();
+
+ setState(State.HEADER);
+ handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
+ continue;
+ }
+ else if (ch>=HttpTokens.SPACE)
+ {
+ _string.append((char)ch);
+ if (ch!=' '&&ch!='\t')
+ _length=_string.length();
+ }
+ else
+ throw new BadMessage();
+ break;
+
+ default:
+ throw new IllegalStateException(_state.toString());
+
+ }
+ }
+
+ return handle;
+ }
+
+ private boolean handleKnownHeaders(ByteBuffer buffer)
+ {
+ boolean add_to_connection_trie=false;
+ switch (_header)
+ {
+ case CONTENT_LENGTH:
+ if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
+ {
+ try
+ {
+ _contentLength=Long.parseLong(_valueString);
+ }
+ catch(NumberFormatException e)
+ {
+ LOG.ignore(e);
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
+ }
+ if (_contentLength <= 0)
+ _endOfContent=EndOfContent.NO_CONTENT;
+ else
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ }
+ break;
+
+ case TRANSFER_ENCODING:
+ if (_value==HttpHeaderValue.CHUNKED)
+ _endOfContent=EndOfContent.CHUNKED_CONTENT;
+ else
+ {
+ if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
+ _endOfContent=EndOfContent.CHUNKED_CONTENT;
+ else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking");
+ }
+ }
+ break;
+
+ case HOST:
+ add_to_connection_trie=_connectionFields!=null && _field==null;
+ _host=true;
+ String host=_valueString;
+ int port=0;
+ if (host==null || host.length()==0)
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+ }
+
+ int len=host.length();
+ loop: for (int i = len; i-- > 0;)
+ {
+ char c2 = (char)(0xff & host.charAt(i));
+ switch (c2)
+ {
+ case ']':
+ break loop;
+
+ case ':':
+ try
+ {
+ len=i;
+ port = StringUtil.toInt(host.substring(i+1));
+ }
+ catch (NumberFormatException e)
+ {
+ if (DEBUG)
+ LOG.debug(e);
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+ }
+ break loop;
+ }
+ }
+ if (host.charAt(0)=='[')
+ {
+ if (host.charAt(len-1)!=']')
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
+ }
+ host = host.substring(1,len-1);
+ }
+ else if (len!=host.length())
+ host = host.substring(0,len);
+
+ if (_requestHandler!=null)
+ _requestHandler.parsedHostHeader(host,port);
+
+ break;
+
+ case CONNECTION:
+ // Don't cache if not persistent
+ if (_valueString!=null && _valueString.contains("close"))
+ {
+ _closed=true;
+ _connectionFields=null;
+ }
+ break;
+
+ case AUTHORIZATION:
+ case ACCEPT:
+ case ACCEPT_CHARSET:
+ case ACCEPT_ENCODING:
+ case ACCEPT_LANGUAGE:
+ case COOKIE:
+ case CACHE_CONTROL:
+ case USER_AGENT:
+ add_to_connection_trie=_connectionFields!=null && _field==null;
+ break;
+
+ default: break;
+ }
+
+ if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
+ {
+ _field=new HttpField(_header,_valueString);
+ _connectionFields.put(_field);
+ }
+
+ return false;
+ }
+
+
+ /* ------------------------------------------------------------------------------- */
+ /*
+ * Parse the message headers and return true if the handler has signaled for a return
+ */
+ protected boolean parseHeaders(ByteBuffer buffer)
+ {
+ boolean handle=false;
+
+ // Process headers
+ while (_state.ordinal()<State.CONTENT.ordinal() && buffer.hasRemaining() && !handle)
+ {
+ // process each character
+ byte ch=next(buffer);
+ if (ch==0)
+ break;
+
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("Header is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ }
+
+ switch (_state)
+ {
+ case HEADER:
+ switch(ch)
+ {
+ case HttpTokens.COLON:
+ case HttpTokens.SPACE:
+ case HttpTokens.TAB:
+ {
+ // header value without name - continuation?
+ if (_valueString==null)
+ {
+ _string.setLength(0);
+ _length=0;
+ }
+ else
+ {
+ setString(_valueString);
+ _string.append(' ');
+ _length++;
+ _valueString=null;
+ }
+ setState(State.HEADER_VALUE);
+ break;
+ }
+
+ default:
+ {
+ // handler last header if any. Delayed to here just in case there was a continuation line (above)
+ if (_headerString!=null || _valueString!=null)
+ {
+ // Handle known headers
+ if (_header!=null && handleKnownHeaders(buffer))
+ {
+ _headerString=_valueString=null;
+ _header=null;
+ _value=null;
+ _field=null;
+ return true;
+ }
+ handle=_handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString))||handle;
+ }
+ _headerString=_valueString=null;
+ _header=null;
+ _value=null;
+ _field=null;
+
+ // now handle the ch
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ _contentPosition=0;
+
+ // End of headers!
+
+ // Was there a required host header?
+ if (!_host && _version!=HttpVersion.HTTP_1_0 && _requestHandler!=null)
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"No Host");
+ }
+
+ // is it a response that cannot have a body?
+ if (_responseHandler !=null && // response
+ (_responseStatus == 304 || // not-modified response
+ _responseStatus == 204 || // no-content response
+ _responseStatus < 200)) // 1xx response
+ _endOfContent=EndOfContent.NO_CONTENT; // ignore any other headers set
+
+ // else if we don't know framing
+ else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+ {
+ if (_responseStatus == 0 // request
+ || _responseStatus == 304 // not-modified response
+ || _responseStatus == 204 // no-content response
+ || _responseStatus < 200) // 1xx response
+ _endOfContent=EndOfContent.NO_CONTENT;
+ else
+ _endOfContent=EndOfContent.EOF_CONTENT;
+ }
+
+ // How is the message ended?
+ switch (_endOfContent)
+ {
+ case EOF_CONTENT:
+ setState(State.EOF_CONTENT);
+ handle=_handler.headerComplete()||handle;
+ break;
+
+ case CHUNKED_CONTENT:
+ setState(State.CHUNKED_CONTENT);
+ handle=_handler.headerComplete()||handle;
+ break;
+
+ case NO_CONTENT:
+ handle=_handler.headerComplete()||handle;
+ setState(State.END);
+ handle=_handler.messageComplete()||handle;
+ break;
+
+ default:
+ setState(State.CONTENT);
+ handle=_handler.headerComplete()||handle;
+ break;
+ }
+ }
+ else if (ch<=HttpTokens.SPACE)
+ throw new BadMessage();
+ else
+ {
+ if (buffer.hasRemaining())
+ {
+ // Try a look ahead for the known header name and value.
+ HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
+ if (field==null)
+ field=CACHE.getBest(buffer,-1,buffer.remaining());
+
+ if (field!=null)
+ {
+ final String n;
+ final String v;
+
+ if (_strict)
+ {
+ // Have to get the fields exactly from the buffer to match case
+ String fn=field.getName();
+ String fv=field.getValue();
+ n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII);
+ if (fv==null)
+ v=null;
+ else
+ {
+ v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1);
+ field=new HttpField(field.getHeader(),n,v);
+ }
+ }
+ else
+ {
+ n=field.getName();
+ v=field.getValue();
+ }
+
+ _header=field.getHeader();
+ _headerString=n;
+
+ if (v==null)
+ {
+ // Header only
+ setState(State.HEADER_VALUE);
+ _string.setLength(0);
+ _length=0;
+ buffer.position(buffer.position()+n.length()+1);
+ break;
+ }
+ else
+ {
+ // Header and value
+ int pos=buffer.position()+n.length()+v.length()+1;
+ byte b=buffer.get(pos);
+
+ if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
+ {
+ _field=field;
+ _valueString=v;
+ setState(State.HEADER_IN_VALUE);
+
+ if (b==HttpTokens.CARRIAGE_RETURN)
+ {
+ _cr=true;
+ buffer.position(pos+1);
+ }
+ else
+ buffer.position(pos);
+ break;
+ }
+ else
+ {
+ setState(State.HEADER_IN_VALUE);
+ setString(v);
+ buffer.position(pos);
+ break;
+ }
+ }
+ }
+ }
+
+ // New header
+ setState(State.HEADER_IN_NAME);
+ _string.setLength(0);
+ _string.append((char)ch);
+ _length=1;
+ }
+ }
+ }
+ break;
+
+ case HEADER_IN_NAME:
+ if (ch==HttpTokens.COLON || ch==HttpTokens.LINE_FEED)
+ {
+ if (_headerString==null)
+ {
+ _headerString=takeString();
+ _header=HttpHeader.CACHE.get(_headerString);
+ }
+ _length=-1;
+
+ setState(ch==HttpTokens.LINE_FEED?State.HEADER:State.HEADER_VALUE);
+ break;
+ }
+
+ if (ch>=HttpTokens.SPACE || ch==HttpTokens.TAB)
+ {
+ if (_header!=null)
+ {
+ setString(_header.asString());
+ _header=null;
+ _headerString=null;
+ }
+
+ _string.append((char)ch);
+ if (ch>HttpTokens.SPACE)
+ _length=_string.length();
+ break;
+ }
+
+ throw new BadMessage("Illegal character");
+
+ case HEADER_VALUE:
+ if (ch>HttpTokens.SPACE || ch<0)
+ {
+ _string.append((char)(0xff&ch));
+ _length=_string.length();
+ setState(State.HEADER_IN_VALUE);
+ break;
+ }
+
+ if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
+ break;
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ if (_length > 0)
+ {
+ _value=null;
+ _valueString=(_valueString==null)?takeString():(_valueString+" "+takeString());
+ }
+ setState(State.HEADER);
+ break;
+ }
+
+ throw new BadMessage("Illegal character");
+
+ case HEADER_IN_VALUE:
+ if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
+ {
+ if (_valueString!=null)
+ {
+ setString(_valueString);
+ _valueString=null;
+ _field=null;
+ }
+ _string.append((char)(0xff&ch));
+ if (ch>HttpTokens.SPACE || ch<0)
+ _length=_string.length();
+ break;
+ }
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ if (_length > 0)
+ {
+ _value=null;
+ _valueString=takeString();
+ _length=-1;
+ }
+ setState(State.HEADER);
+ break;
+ }
+ throw new BadMessage("Illegal character");
+
+ default:
+ throw new IllegalStateException(_state.toString());
+
+ }
+ }
+
+ return handle;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * Parse until next Event.
+ * @return True if an {@link RequestHandler} method was called and it returned true;
+ */
+ public boolean parseNext(ByteBuffer buffer)
+ {
+ if (DEBUG)
+ LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
+ try
+ {
+ // Start a request/response
+ if (_state==State.START)
+ {
+ _version=null;
+ _method=null;
+ _methodString=null;
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ _header=null;
+ if (quickStart(buffer))
+ return true;
+ }
+
+ // Request/response line
+ if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
+ {
+ if (parseLine(buffer))
+ return true;
+ }
+
+ // parse headers
+ if (_state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
+ {
+ if (parseHeaders(buffer))
+ return true;
+ }
+
+ // parse content
+ if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
+ {
+ // Handle HEAD response
+ if (_responseStatus>0 && _headResponse)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ {
+ if (parseContent(buffer))
+ return true;
+ }
+ }
+
+ // handle end states
+ if (_state==State.END)
+ {
+ // eat white space
+ while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
+ buffer.get();
+ }
+ else if (_state==State.CLOSED)
+ {
+ if (BufferUtil.hasContent(buffer))
+ {
+ // Just ignore data when closed
+ _headerBytes+=buffer.remaining();
+ BufferUtil.clear(buffer);
+ if (_headerBytes>_maxHeaderBytes)
+ {
+ // Don't want to waste time reading data of a closed request
+ throw new IllegalStateException("too much data after closed");
+ }
+ }
+ }
+
+ // Handle EOF
+ if (_eof && !buffer.hasRemaining())
+ {
+ switch(_state)
+ {
+ case CLOSED:
+ break;
+
+ case START:
+ setState(State.CLOSED);
+ _handler.earlyEOF();
+ break;
+
+ case END:
+ setState(State.CLOSED);
+ break;
+
+ case EOF_CONTENT:
+ setState(State.CLOSED);
+ return _handler.messageComplete();
+
+ case CONTENT:
+ case CHUNKED_CONTENT:
+ case CHUNK_SIZE:
+ case CHUNK_PARAMS:
+ case CHUNK:
+ setState(State.CLOSED);
+ _handler.earlyEOF();
+ break;
+
+ default:
+ if (DEBUG)
+ LOG.debug("{} EOF in {}",this,_state);
+ setState(State.CLOSED);
+ _handler.badMessage(400,null);
+ break;
+ }
+ }
+
+ return false;
+ }
+ catch(BadMessage e)
+ {
+ BufferUtil.clear(buffer);
+
+ LOG.warn("badMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
+ if (DEBUG)
+ LOG.debug(e);
+ setState(State.CLOSED);
+ _handler.badMessage(e._code, e._message);
+ return false;
+ }
+ catch(Exception e)
+ {
+ BufferUtil.clear(buffer);
+
+ LOG.warn("badMessage: "+e.toString()+" for "+_handler);
+ if (DEBUG)
+ LOG.debug(e);
+
+ if (_state.ordinal()<=State.END.ordinal())
+ {
+ setState(State.CLOSED);
+ _handler.badMessage(400,null);
+ }
+ else
+ {
+ _handler.earlyEOF();
+ setState(State.CLOSED);
+ }
+
+ return false;
+ }
+ }
+
+ protected boolean parseContent(ByteBuffer buffer)
+ {
+ int remaining=buffer.remaining();
+ if (remaining==0 && _state==State.CONTENT)
+ {
+ long content=_contentLength - _contentPosition;
+ if (content == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ }
+
+ // Handle _content
+ byte ch;
+ while (_state.ordinal() < State.END.ordinal() && remaining>0)
+ {
+ switch (_state)
+ {
+ case EOF_CONTENT:
+ _contentChunk=buffer.asReadOnlyBuffer();
+ _contentPosition += remaining;
+ buffer.position(buffer.position()+remaining);
+ if (_handler.content(_contentChunk))
+ return true;
+ break;
+
+ case CONTENT:
+ {
+ long content=_contentLength - _contentPosition;
+ if (content == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
+
+ // limit content by expected size
+ if (remaining > content)
+ {
+ // We can cast remaining to an int as we know that it is smaller than
+ // or equal to length which is already an int.
+ _contentChunk.limit(_contentChunk.position()+(int)content);
+ }
+
+ _contentPosition += _contentChunk.remaining();
+ buffer.position(buffer.position()+_contentChunk.remaining());
+
+ if (_handler.content(_contentChunk))
+ return true;
+
+ if(_contentPosition == _contentLength)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ }
+ break;
+ }
+
+ case CHUNKED_CONTENT:
+ {
+ ch=next(buffer);
+ if (ch>HttpTokens.SPACE)
+ {
+ _chunkLength=TypeUtil.convertHexDigit(ch);
+ _chunkPosition=0;
+ setState(State.CHUNK_SIZE);
+ }
+
+ break;
+ }
+
+ case CHUNK_SIZE:
+ {
+ ch=next(buffer);
+ if (ch==0)
+ break;
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ setState(State.CHUNK);
+ }
+ else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
+ setState(State.CHUNK_PARAMS);
+ else
+ _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
+ break;
+ }
+
+ case CHUNK_PARAMS:
+ {
+ ch=next(buffer);
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ setState(State.CHUNK);
+ }
+ break;
+ }
+
+ case CHUNK:
+ {
+ int chunk=_chunkLength - _chunkPosition;
+ if (chunk == 0)
+ {
+ setState(State.CHUNKED_CONTENT);
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
+
+ if (remaining > chunk)
+ _contentChunk.limit(_contentChunk.position()+chunk);
+ chunk=_contentChunk.remaining();
+
+ _contentPosition += chunk;
+ _chunkPosition += chunk;
+ buffer.position(buffer.position()+chunk);
+ if (_handler.content(_contentChunk))
+ return true;
+ }
+ break;
+ }
+
+ case CLOSED:
+ {
+ BufferUtil.clear(buffer);
+ return false;
+ }
+
+ default:
+ break;
+
+ }
+
+ remaining=buffer.remaining();
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean isAtEOF()
+
+ {
+ return _eof;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void atEOF()
+
+ {
+ if (DEBUG)
+ LOG.debug("atEOF {}", this);
+ _eof=true;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void close()
+ {
+ if (DEBUG)
+ LOG.debug("close {}", this);
+ setState(State.CLOSED);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void reset()
+ {
+ if (DEBUG)
+ LOG.debug("reset {}", this);
+ // reset state
+ if (_state==State.CLOSED)
+ return;
+ if (_closed)
+ {
+ setState(State.CLOSED);
+ return;
+ }
+
+ setState(State.START);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ _contentLength=-1;
+ _contentPosition=0;
+ _responseStatus=0;
+ _contentChunk=null;
+ _headerBytes=0;
+ _host=false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected void setState(State state)
+ {
+ if (DEBUG)
+ LOG.debug("{} --> {}",_state,state);
+ _state=state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return String.format("%s{s=%s,%d of %d}",
+ getClass().getSimpleName(),
+ _state,
+ _contentPosition,
+ _contentLength);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* Event Handler interface
+ * These methods return true if the caller should process the events
+ * so far received (eg return from parseNext and call HttpChannel.handle).
+ * If multiple callbacks are called in sequence (eg
+ * headerComplete then messageComplete) from the same point in the parsing
+ * then it is sufficient for the caller to process the events only once.
+ */
+ public interface HttpHandler<T>
+ {
+ public boolean content(T item);
+
+ public boolean headerComplete();
+
+ public boolean messageComplete();
+
+ /**
+ * This is the method called by parser when a HTTP Header name and value is found
+ * @param field The field parsed
+ * @return True if the parser should return to its caller
+ */
+ public boolean parsedHeader(HttpField field);
+
+ /* ------------------------------------------------------------ */
+ /** Called to signal that an EOF was received unexpectedly
+ * during the parsing of a HTTP message
+ */
+ public void earlyEOF();
+
+ /* ------------------------------------------------------------ */
+ /** Called to signal that a bad HTTP message has been received.
+ * @param status The bad status to send
+ * @param reason The textual reason for badness
+ */
+ public void badMessage(int status, String reason);
+
+ /* ------------------------------------------------------------ */
+ /** @return the size in bytes of the per parser header cache
+ */
+ public int getHeaderCacheSize();
+ }
+
+ public interface ProxyHandler
+ {
+ void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort);
+ }
+
+ public interface RequestHandler<T> extends HttpHandler<T>
+ {
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ * @param method The method as enum if of a known type
+ * @param methodString The method as a string
+ * @param uri The raw bytes of the URI. These are copied into a ByteBuffer that will not be changed until this parser is reset and reused.
+ * @param version
+ * @return true if handling parsing should return.
+ */
+ public abstract boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version);
+
+ /**
+ * This is the method called by the parser after it has parsed the host header (and checked it's format). This is
+ * called after the {@link HttpHandler#parsedHeader(HttpField)} methods and before
+ * HttpHandler#headerComplete();
+ */
+ public abstract boolean parsedHostHeader(String host,int port);
+ }
+
+ public interface ResponseHandler<T> extends HttpHandler<T>
+ {
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ */
+ public abstract boolean startResponse(HttpVersion version, int status, String reason);
+ }
+
+ public Trie<HttpField> getFieldCache()
+ {
+ return _connectionFields;
+ }
+
+ private String getProxyField(ByteBuffer buffer)
+ {
+ _string.setLength(0);
+ _length=0;
+
+ while (buffer.hasRemaining())
+ {
+ // process each character
+ byte ch=next(buffer);
+ if (ch<=' ')
+ return _string.toString();
+ _string.append((char)ch);
+ }
+ throw new BadMessage();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Trie;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ */
+public enum HttpScheme
+{
+ HTTP("http"),
+ HTTPS("https"),
+ WS("ws"),
+ WSS("wss");
+
+ /* ------------------------------------------------------------ */
+ public final static Trie<HttpScheme> CACHE= new ArrayTrie<HttpScheme>();
+ static
+ {
+ for (HttpScheme version : HttpScheme.values())
+ CACHE.put(version.asString(),version);
+ }
+
+ private final String _string;
+ private final ByteBuffer _buffer;
+
+ /* ------------------------------------------------------------ */
+ HttpScheme(String s)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer asByteBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+/**
+ * <p>
+ * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see
+ * table below)
+ * </p>
+ *
+ * <table border="1" cellpadding="5">
+ * <tr>
+ * <th>Enum</th>
+ * <th>Code</th>
+ * <th>Message</th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a></th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a></th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc2518">RFC 2518 - WEBDAV</a></th>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Informational - 1xx</code></strong></td>
+ * <td colspan="5">{@link #isInformational(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #CONTINUE_100}</td>
+ * <td>100</td>
+ * <td>Continue</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.1">Sec. 10.1.1</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SWITCHING_PROTOCOLS_101}</td>
+ * <td>101</td>
+ * <td>Switching Protocols</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.2">Sec. 10.1.2</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PROCESSING_102}</td>
+ * <td>102</td>
+ * <td>Processing</td>
+ * <td> </td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.1">Sec. 10.1</a></td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Success - 2xx</code></strong></td>
+ * <td colspan="5">{@link #isSuccess(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #OK_200}</td>
+ * <td>200</td>
+ * <td>OK</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.1">Sec. 10.2.1</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #CREATED_201}</td>
+ * <td>201</td>
+ * <td>Created</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.2">Sec. 10.2.2</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #ACCEPTED_202}</td>
+ * <td>202</td>
+ * <td>Accepted</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.3">Sec. 10.2.3</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NON_AUTHORITATIVE_INFORMATION_203}</td>
+ * <td>203</td>
+ * <td>Non Authoritative Information</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.4">Sec. 10.2.4</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NO_CONTENT_204}</td>
+ * <td>204</td>
+ * <td>No Content</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.5">Sec. 10.2.5</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #RESET_CONTENT_205}</td>
+ * <td>205</td>
+ * <td>Reset Content</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.6">Sec. 10.2.6</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PARTIAL_CONTENT_206}</td>
+ * <td>206</td>
+ * <td>Partial Content</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">Sec. 10.2.7</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MULTI_STATUS_207}</td>
+ * <td>207</td>
+ * <td>Multi-Status</td>
+ * <td> </td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.2">Sec. 10.2</a></td>
+ * </tr>
+ * <tr>
+ * <td> </td>
+ * <td><strike>207</strike></td>
+ * <td><strike>Partial Update OK</strike></td>
+ * <td> </td>
+ * <td>
+ * <a href=
+ * "http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-rev-01.txt"
+ * >draft/01</a></td>
+ * <td> </td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Redirection - 3xx</code></strong></td>
+ * <td colspan="5">{@link #isRedirection(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #MULTIPLE_CHOICES_300}</td>
+ * <td>300</td>
+ * <td>Multiple Choices</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.1">Sec. 10.3.1</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MOVED_PERMANENTLY_301}</td>
+ * <td>301</td>
+ * <td>Moved Permanently</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.2">Sec. 10.3.2</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MOVED_TEMPORARILY_302}</td>
+ * <td>302</td>
+ * <td>Moved Temporarily</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>(now "<code>302 Found</code>")</td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FOUND_302}</td>
+ * <td>302</td>
+ * <td>Found</td>
+ * <td>(was "<code>302 Moved Temporarily</code>")</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.3">Sec. 10.3.3</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SEE_OTHER_303}</td>
+ * <td>303</td>
+ * <td>See Other</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.4">Sec. 10.3.4</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_MODIFIED_304}</td>
+ * <td>304</td>
+ * <td>Not Modified</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.5">Sec. 10.3.5</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #USE_PROXY_305}</td>
+ * <td>305</td>
+ * <td>Use Proxy</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.6">Sec. 10.3.6</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td> </td>
+ * <td>306</td>
+ * <td><em>(Unused)</em></td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.7">Sec. 10.3.7</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #TEMPORARY_REDIRECT_307}</td>
+ * <td>307</td>
+ * <td>Temporary Redirect</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.8">Sec. 10.3.8</a></td>
+ * <td> </td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Client Error - 4xx</code></strong></td>
+ * <td colspan="5">{@link #isClientError(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #BAD_REQUEST_400}</td>
+ * <td>400</td>
+ * <td>Bad Request</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.1">Sec. 10.4.1</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNAUTHORIZED_401}</td>
+ * <td>401</td>
+ * <td>Unauthorized</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.2">Sec. 10.4.2</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PAYMENT_REQUIRED_402}</td>
+ * <td>402</td>
+ * <td>Payment Required</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.3">Sec. 10.4.3</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FORBIDDEN_403}</td>
+ * <td>403</td>
+ * <td>Forbidden</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.4">Sec. 10.4.4</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_FOUND_404}</td>
+ * <td>404</td>
+ * <td>Not Found</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.5">Sec. 10.4.5</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #METHOD_NOT_ALLOWED_405}</td>
+ * <td>405</td>
+ * <td>Method Not Allowed</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.6">Sec. 10.4.6</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_ACCEPTABLE_406}</td>
+ * <td>406</td>
+ * <td>Not Acceptable</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.7">Sec. 10.4.7</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PROXY_AUTHENTICATION_REQUIRED_407}</td>
+ * <td>407</td>
+ * <td>Proxy Authentication Required</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.8">Sec. 10.4.8</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_TIMEOUT_408}</td>
+ * <td>408</td>
+ * <td>Request Timeout</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.9">Sec. 10.4.9</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #CONFLICT_409}</td>
+ * <td>409</td>
+ * <td>Conflict</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.10">Sec. 10.4.10</a>
+ * </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #GONE_410}</td>
+ * <td>410</td>
+ * <td>Gone</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">Sec. 10.4.11</a>
+ * </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #LENGTH_REQUIRED_411}</td>
+ * <td>411</td>
+ * <td>Length Required</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.12">Sec. 10.4.12</a>
+ * </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PRECONDITION_FAILED_412}</td>
+ * <td>412</td>
+ * <td>Precondition Failed</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">Sec. 10.4.13</a>
+ * </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_ENTITY_TOO_LARGE_413}</td>
+ * <td>413</td>
+ * <td>Request Entity Too Large</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">Sec. 10.4.14</a>
+ * </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_URI_TOO_LONG_414}</td>
+ * <td>414</td>
+ * <td>Request-URI Too Long</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">Sec. 10.4.15</a>
+ * </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNSUPPORTED_MEDIA_TYPE_415}</td>
+ * <td>415</td>
+ * <td>Unsupported Media Type</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.16">Sec. 10.4.16</a>
+ * </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUESTED_RANGE_NOT_SATISFIABLE_416}</td>
+ * <td>416</td>
+ * <td>Requested Range Not Satisfiable</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.17">Sec. 10.4.17</a>
+ * </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXPECTATION_FAILED_417}</td>
+ * <td>417</td>
+ * <td>Expectation Failed</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.18">Sec. 10.4.18</a>
+ * </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td> </td>
+ * <td><strike>418</strike></td>
+ * <td><strike>Reauthentication Required</strike></td>
+ * <td> </td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.19"
+ * >draft/01</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td> </td>
+ * <td><strike>418</strike></td>
+ * <td><strike>Unprocessable Entity</strike></td>
+ * <td> </td>
+ * <td> </td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.3"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td> </td>
+ * <td><strike>419</strike></td>
+ * <td><strike>Proxy Reauthentication Required</stike></td>
+ * <td> </td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.20"
+ * >draft/01</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td> </td>
+ * <td><strike>419</strike></td>
+ * <td><strike>Insufficient Space on Resource</stike></td>
+ * <td> </td>
+ * <td> </td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.4"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td> </td>
+ * <td><strike>420</strike></td>
+ * <td><strike>Method Failure</strike></td>
+ * <td> </td>
+ * <td> </td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.5"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td> </td>
+ * <td>421</td>
+ * <td><em>(Unused)</em></td>
+ * <td> </td>
+ * <td> </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNPROCESSABLE_ENTITY_422}</td>
+ * <td>422</td>
+ * <td>Unprocessable Entity</td>
+ * <td> </td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.3">Sec. 10.3</a></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #LOCKED_423}</td>
+ * <td>423</td>
+ * <td>Locked</td>
+ * <td> </td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.4">Sec. 10.4</a></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FAILED_DEPENDENCY_424}</td>
+ * <td>424</td>
+ * <td>Failed Dependency</td>
+ * <td> </td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.5">Sec. 10.5</a></td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Server Error - 5xx</code></strong></td>
+ * <td colspan="5">{@link #isServerError(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #INTERNAL_SERVER_ERROR_500}</td>
+ * <td>500</td>
+ * <td>Internal Server Error</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.1">Sec. 10.5.1</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_IMPLEMENTED_501}</td>
+ * <td>501</td>
+ * <td>Not Implemented</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.2">Sec. 10.5.2</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #BAD_GATEWAY_502}</td>
+ * <td>502</td>
+ * <td>Bad Gateway</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.3">Sec. 10.5.3</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SERVICE_UNAVAILABLE_503}</td>
+ * <td>503</td>
+ * <td>Service Unavailable</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.4">Sec. 10.5.4</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #GATEWAY_TIMEOUT_504}</td>
+ * <td>504</td>
+ * <td>Gateway Timeout</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.5">Sec. 10.5.5</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #HTTP_VERSION_NOT_SUPPORTED_505}</td>
+ * <td>505</td>
+ * <td>HTTP Version Not Supported</td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.6">Sec. 10.5.6</a></td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td> </td>
+ * <td>506</td>
+ * <td><em>(Unused)</em></td>
+ * <td> </td>
+ * <td> </td>
+ * <td> </td>
+ * </tr>
+ * <tr>
+ * <td>{@link #INSUFFICIENT_STORAGE_507}</td>
+ * <td>507</td>
+ * <td>Insufficient Storage</td>
+ * <td> </td>
+ * <td> </td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.6">Sec. 10.6</a></td>
+ * </tr>
+ *
+ * </table>
+ *
+ * @version $Id$
+ */
+public class HttpStatus
+{
+ public final static int NOT_SET_000 = 0;
+ public final static int CONTINUE_100 = 100;
+ public final static int SWITCHING_PROTOCOLS_101 = 101;
+ public final static int PROCESSING_102 = 102;
+
+ public final static int OK_200 = 200;
+ public final static int CREATED_201 = 201;
+ public final static int ACCEPTED_202 = 202;
+ public final static int NON_AUTHORITATIVE_INFORMATION_203 = 203;
+ public final static int NO_CONTENT_204 = 204;
+ public final static int RESET_CONTENT_205 = 205;
+ public final static int PARTIAL_CONTENT_206 = 206;
+ public final static int MULTI_STATUS_207 = 207;
+
+ public final static int MULTIPLE_CHOICES_300 = 300;
+ public final static int MOVED_PERMANENTLY_301 = 301;
+ public final static int MOVED_TEMPORARILY_302 = 302;
+ public final static int FOUND_302 = 302;
+ public final static int SEE_OTHER_303 = 303;
+ public final static int NOT_MODIFIED_304 = 304;
+ public final static int USE_PROXY_305 = 305;
+ public final static int TEMPORARY_REDIRECT_307 = 307;
+
+ public final static int BAD_REQUEST_400 = 400;
+ public final static int UNAUTHORIZED_401 = 401;
+ public final static int PAYMENT_REQUIRED_402 = 402;
+ public final static int FORBIDDEN_403 = 403;
+ public final static int NOT_FOUND_404 = 404;
+ public final static int METHOD_NOT_ALLOWED_405 = 405;
+ public final static int NOT_ACCEPTABLE_406 = 406;
+ public final static int PROXY_AUTHENTICATION_REQUIRED_407 = 407;
+ public final static int REQUEST_TIMEOUT_408 = 408;
+ public final static int CONFLICT_409 = 409;
+ public final static int GONE_410 = 410;
+ public final static int LENGTH_REQUIRED_411 = 411;
+ public final static int PRECONDITION_FAILED_412 = 412;
+ public final static int REQUEST_ENTITY_TOO_LARGE_413 = 413;
+ public final static int REQUEST_URI_TOO_LONG_414 = 414;
+ public final static int UNSUPPORTED_MEDIA_TYPE_415 = 415;
+ public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416;
+ public final static int EXPECTATION_FAILED_417 = 417;
+ public final static int UNPROCESSABLE_ENTITY_422 = 422;
+ public final static int LOCKED_423 = 423;
+ public final static int FAILED_DEPENDENCY_424 = 424;
+
+ public final static int INTERNAL_SERVER_ERROR_500 = 500;
+ public final static int NOT_IMPLEMENTED_501 = 501;
+ public final static int BAD_GATEWAY_502 = 502;
+ public final static int SERVICE_UNAVAILABLE_503 = 503;
+ public final static int GATEWAY_TIMEOUT_504 = 504;
+ public final static int HTTP_VERSION_NOT_SUPPORTED_505 = 505;
+ public final static int INSUFFICIENT_STORAGE_507 = 507;
+
+ public static final int MAX_CODE = 507;
+
+
+ private static final Code[] codeMap = new Code[MAX_CODE+1];
+
+ static
+ {
+ for (Code code : Code.values())
+ {
+ codeMap[code._code] = code;
+ }
+ }
+
+
+ public enum Code
+ {
+ /*
+ * --------------------------------------------------------------------
+ * Informational messages in 1xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** <code>100 Continue</code> */
+ CONTINUE(CONTINUE_100, "Continue"),
+ /** <code>101 Switching Protocols</code> */
+ SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"),
+ /** <code>102 Processing</code> */
+ PROCESSING(PROCESSING_102, "Processing"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Success messages in 2xx series. As defined by ... RFC 1945 - HTTP/1.0
+ * RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** <code>200 OK</code> */
+ OK(OK_200, "OK"),
+ /** <code>201 Created</code> */
+ CREATED(CREATED_201, "Created"),
+ /** <code>202 Accepted</code> */
+ ACCEPTED(ACCEPTED_202, "Accepted"),
+ /** <code>203 Non Authoritative Information</code> */
+ NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"),
+ /** <code>204 No Content</code> */
+ NO_CONTENT(NO_CONTENT_204, "No Content"),
+ /** <code>205 Reset Content</code> */
+ RESET_CONTENT(RESET_CONTENT_205, "Reset Content"),
+ /** <code>206 Partial Content</code> */
+ PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"),
+ /** <code>207 Multi-Status</code> */
+ MULTI_STATUS(MULTI_STATUS_207, "Multi-Status"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Redirection messages in 3xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 2616 - HTTP/1.1
+ */
+
+ /** <code>300 Mutliple Choices</code> */
+ MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"),
+ /** <code>301 Moved Permanently</code> */
+ MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"),
+ /** <code>302 Moved Temporarily</code> */
+ MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"),
+ /** <code>302 Found</code> */
+ FOUND(FOUND_302, "Found"),
+ /** <code>303 See Other</code> */
+ SEE_OTHER(SEE_OTHER_303, "See Other"),
+ /** <code>304 Not Modified</code> */
+ NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"),
+ /** <code>305 Use Proxy</code> */
+ USE_PROXY(USE_PROXY_305, "Use Proxy"),
+ /** <code>307 Temporary Redirect</code> */
+ TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Client Error messages in 4xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** <code>400 Bad Request</code> */
+ BAD_REQUEST(BAD_REQUEST_400, "Bad Request"),
+ /** <code>401 Unauthorized</code> */
+ UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"),
+ /** <code>402 Payment Required</code> */
+ PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"),
+ /** <code>403 Forbidden</code> */
+ FORBIDDEN(FORBIDDEN_403, "Forbidden"),
+ /** <code>404 Not Found</code> */
+ NOT_FOUND(NOT_FOUND_404, "Not Found"),
+ /** <code>405 Method Not Allowed</code> */
+ METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"),
+ /** <code>406 Not Acceptable</code> */
+ NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"),
+ /** <code>407 Proxy Authentication Required</code> */
+ PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"),
+ /** <code>408 Request Timeout</code> */
+ REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"),
+ /** <code>409 Conflict</code> */
+ CONFLICT(CONFLICT_409, "Conflict"),
+ /** <code>410 Gone</code> */
+ GONE(GONE_410, "Gone"),
+ /** <code>411 Length Required</code> */
+ LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"),
+ /** <code>412 Precondition Failed</code> */
+ PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"),
+ /** <code>413 Request Entity Too Large</code> */
+ REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"),
+ /** <code>414 Request-URI Too Long</code> */
+ REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"),
+ /** <code>415 Unsupported Media Type</code> */
+ UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"),
+ /** <code>416 Requested Range Not Satisfiable</code> */
+ REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"),
+ /** <code>417 Expectation Failed</code> */
+ EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"),
+ /** <code>422 Unprocessable Entity</code> */
+ UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"),
+ /** <code>423 Locked</code> */
+ LOCKED(LOCKED_423, "Locked"),
+ /** <code>424 Failed Dependency</code> */
+ FAILED_DEPENDENCY(FAILED_DEPENDENCY_424, "Failed Dependency"),
+
+ /*
+ * --------------------------------------------------------------------
+ * Server Error messages in 5xx series. As defined by ... RFC 1945 -
+ * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+ */
+
+ /** <code>500 Server Error</code> */
+ INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"),
+ /** <code>501 Not Implemented</code> */
+ NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"),
+ /** <code>502 Bad Gateway</code> */
+ BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"),
+ /** <code>503 Service Unavailable</code> */
+ SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"),
+ /** <code>504 Gateway Timeout</code> */
+ GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"),
+ /** <code>505 HTTP Version Not Supported</code> */
+ HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"),
+ /** <code>507 Insufficient Storage</code> */
+ INSUFFICIENT_STORAGE(INSUFFICIENT_STORAGE_507, "Insufficient Storage");
+
+ private final int _code;
+ private final String _message;
+
+ private Code(int code, String message)
+ {
+ this._code = code;
+ _message=message;
+ }
+
+ public int getCode()
+ {
+ return _code;
+ }
+
+ public String getMessage()
+ {
+ return _message;
+ }
+
+
+ public boolean equals(int code)
+ {
+ return (this._code == code);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("[%03d %s]",this._code,this.getMessage());
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Informational</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+ * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+ * HTTP/1.1</a>.
+ *
+ * @return true if within range of codes that belongs to
+ * <code>Informational</code> messages.
+ */
+ public boolean isInformational()
+ {
+ return HttpStatus.isInformational(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Success</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+ * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+ * HTTP/1.1</a>.
+ *
+ * @return true if within range of codes that belongs to
+ * <code>Success</code> messages.
+ */
+ public boolean isSuccess()
+ {
+ return HttpStatus.isSuccess(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Redirection</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+ * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+ * HTTP/1.1</a>.
+ *
+ * @return true if within range of codes that belongs to
+ * <code>Redirection</code> messages.
+ */
+ public boolean isRedirection()
+ {
+ return HttpStatus.isRedirection(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Client Error</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+ * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+ * HTTP/1.1</a>.
+ *
+ * @return true if within range of codes that belongs to
+ * <code>Client Error</code> messages.
+ */
+ public boolean isClientError()
+ {
+ return HttpStatus.isClientError(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Server Error</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+ * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+ * HTTP/1.1</a>.
+ *
+ * @return true if within range of codes that belongs to
+ * <code>Server Error</code> messages.
+ */
+ public boolean isServerError()
+ {
+ return HttpStatus.isServerError(this._code);
+ }
+ }
+
+
+ /**
+ * Get the HttpStatusCode for a specific code
+ *
+ * @param code
+ * the code to lookup.
+ * @return the {@link HttpStatus} if found, or null if not found.
+ */
+ public static Code getCode(int code)
+ {
+ if (code <= MAX_CODE)
+ {
+ return codeMap[code];
+ }
+ return null;
+ }
+
+ /**
+ * Get the status message for a specific code.
+ *
+ * @param code
+ * the code to look up
+ * @return the specific message, or the code number itself if code
+ * does not match known list.
+ */
+ public static String getMessage(int code)
+ {
+ Code codeEnum = getCode(code);
+ if (codeEnum != null)
+ {
+ return codeEnum.getMessage();
+ }
+ else
+ {
+ return Integer.toString(code);
+ }
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Informational</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+ * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * <code>Informational</code> messages.
+ */
+ public static boolean isInformational(int code)
+ {
+ return ((100 <= code) && (code <= 199));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Success</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+ * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * <code>Success</code> messages.
+ */
+ public static boolean isSuccess(int code)
+ {
+ return ((200 <= code) && (code <= 299));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Redirection</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+ * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * <code>Redirection</code> messages.
+ */
+ public static boolean isRedirection(int code)
+ {
+ return ((300 <= code) && (code <= 399));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Client Error</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+ * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * <code>Client Error</code> messages.
+ */
+ public static boolean isClientError(int code)
+ {
+ return ((400 <= code) && (code <= 499));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * <code>Server Error</code> message category as defined in the <a
+ * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+ * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * <code>Server Error</code> messages.
+ */
+ public static boolean isServerError(int code)
+ {
+ return ((500 <= code) && (code <= 599));
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpGenerator.RequestInfo;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+
+public class HttpTester
+{
+ private HttpTester()
+ {
+ }
+
+ public static Request newRequest()
+ {
+ return new Request();
+ }
+
+ public static Request parseRequest(String request)
+ {
+ Request r=new Request();
+ HttpParser parser =new HttpParser(r);
+ parser.parseNext(BufferUtil.toBuffer(request));
+ return r;
+ }
+
+ public static Request parseRequest(ByteBuffer request)
+ {
+ Request r=new Request();
+ HttpParser parser =new HttpParser(r);
+ parser.parseNext(request);
+ return r;
+ }
+
+ public static Response parseResponse(String response)
+ {
+ Response r=new Response();
+ HttpParser parser =new HttpParser(r);
+ parser.parseNext(BufferUtil.toBuffer(response));
+ return r;
+ }
+
+ public static Response parseResponse(ByteBuffer response)
+ {
+ Response r=new Response();
+ HttpParser parser =new HttpParser(r);
+ parser.parseNext(response);
+ return r;
+ }
+
+
+ public abstract static class Message extends HttpFields implements HttpParser.HttpHandler<ByteBuffer>
+ {
+ ByteArrayOutputStream _content;
+ HttpVersion _version=HttpVersion.HTTP_1_0;
+
+ public HttpVersion getVersion()
+ {
+ return _version;
+ }
+
+ public void setVersion(String version)
+ {
+ setVersion(HttpVersion.CACHE.get(version));
+ }
+
+ public void setVersion(HttpVersion version)
+ {
+ _version=version;
+ }
+
+ public void setContent(byte[] bytes)
+ {
+ try
+ {
+ _content=new ByteArrayOutputStream();
+ _content.write(bytes);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setContent(String content)
+ {
+ try
+ {
+ _content=new ByteArrayOutputStream();
+ _content.write(StringUtil.getBytes(content));
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setContent(ByteBuffer content)
+ {
+ try
+ {
+ _content=new ByteArrayOutputStream();
+ _content.write(BufferUtil.toArray(content));
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ @Override
+ public boolean parsedHeader(HttpField field)
+ {
+ put(field.getName(),field.getValue());
+ return false;
+ }
+
+ @Override
+ public boolean messageComplete()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean headerComplete()
+ {
+ _content=new ByteArrayOutputStream();
+ return false;
+ }
+
+ @Override
+ public void earlyEOF()
+ {
+ }
+
+ @Override
+ public boolean content(ByteBuffer ref)
+ {
+ try
+ {
+ _content.write(BufferUtil.toArray(ref));
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ return false;
+ }
+
+ @Override
+ public void badMessage(int status, String reason)
+ {
+ throw new RuntimeException(reason);
+ }
+
+ public ByteBuffer generate()
+ {
+ try
+ {
+ HttpGenerator generator = new HttpGenerator();
+ HttpGenerator.Info info = getInfo();
+ // System.err.println(info.getClass());
+ // System.err.println(info);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ByteBuffer header=null;
+ ByteBuffer chunk=null;
+ ByteBuffer content=_content==null?null:ByteBuffer.wrap(_content.toByteArray());
+
+
+ loop: while(!generator.isEnd())
+ {
+ HttpGenerator.Result result = info instanceof RequestInfo
+ ?generator.generateRequest((RequestInfo)info,header,chunk,content,true)
+ :generator.generateResponse((ResponseInfo)info,header,chunk,content,true);
+ switch(result)
+ {
+ case NEED_HEADER:
+ header=BufferUtil.allocate(8192);
+ continue;
+
+ case NEED_CHUNK:
+ chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
+ continue;
+
+ case NEED_INFO:
+ throw new IllegalStateException();
+
+ case FLUSH:
+ if (BufferUtil.hasContent(header))
+ {
+ out.write(BufferUtil.toArray(header));
+ BufferUtil.clear(header);
+ }
+ if (BufferUtil.hasContent(chunk))
+ {
+ out.write(BufferUtil.toArray(chunk));
+ BufferUtil.clear(chunk);
+ }
+ if (BufferUtil.hasContent(content))
+ {
+ out.write(BufferUtil.toArray(content));
+ BufferUtil.clear(content);
+ }
+ break;
+
+ case SHUTDOWN_OUT:
+ break loop;
+ }
+ }
+
+ return ByteBuffer.wrap(out.toByteArray());
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ }
+ abstract public HttpGenerator.Info getInfo();
+
+ @Override
+ public int getHeaderCacheSize()
+ {
+ return 0;
+ }
+
+ }
+
+ public static class Request extends Message implements HttpParser.RequestHandler<ByteBuffer>
+ {
+ private String _method;
+ private String _uri;
+
+ @Override
+ public boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version)
+ {
+ _method=methodString;
+ _uri=BufferUtil.toUTF8String(uri);
+ _version=version;
+ return false;
+ }
+
+ public String getMethod()
+ {
+ return _method;
+ }
+
+ public String getUri()
+ {
+ return _uri;
+ }
+
+ public void setMethod(String method)
+ {
+ _method=method;
+ }
+
+ public void setURI(String uri)
+ {
+ _uri=uri;
+ }
+
+ @Override
+ public HttpGenerator.RequestInfo getInfo()
+ {
+ return new HttpGenerator.RequestInfo(_version,this,_content==null?0:_content.size(),_method,_uri);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s %s %s\n%s\n",_method,_uri,_version,super.toString());
+ }
+
+ public void setHeader(String name, String value)
+ {
+ put(name,value);
+ }
+
+ @Override
+ public boolean parsedHostHeader(String host,int port)
+ {
+ return false;
+ }
+ }
+
+ public static class Response extends Message implements HttpParser.ResponseHandler<ByteBuffer>
+ {
+ private int _status;
+ private String _reason;
+
+ @Override
+ public boolean startResponse(HttpVersion version, int status, String reason)
+ {
+ _version=version;
+ _status=status;
+ _reason=reason;
+ return false;
+ }
+
+ public int getStatus()
+ {
+ return _status;
+ }
+
+ public String getReason()
+ {
+ return _reason;
+ }
+
+ public byte[] getContentBytes()
+ {
+ if (_content==null)
+ return null;
+ return _content.toByteArray();
+ }
+
+ public String getContent()
+ {
+ if (_content==null)
+ return null;
+ byte[] bytes=_content.toByteArray();
+
+ String content_type=get(HttpHeader.CONTENT_TYPE);
+ String encoding=MimeTypes.getCharsetFromContentType(content_type);
+ Charset charset=encoding==null?StandardCharsets.UTF_8:Charset.forName(encoding);
+
+ return new String(bytes,charset);
+ }
+
+ @Override
+ public HttpGenerator.ResponseInfo getInfo()
+ {
+ return new HttpGenerator.ResponseInfo(_version,this,_content==null?-1:_content.size(),_status,_reason,false);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s %s %s\n%s\n",_version,_status,_reason,super.toString());
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+/**
+ * HTTP constants
+ */
+public interface HttpTokens
+{
+ // Terminal symbols.
+ static final byte COLON= (byte)':';
+ static final byte TAB= 0x09;
+ static final byte LINE_FEED= 0x0A;
+ static final byte CARRIAGE_RETURN= 0x0D;
+ static final byte SPACE= 0x20;
+ static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
+ static final byte SEMI_COLON= (byte)';';
+
+ public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT,SELF_DEFINING_CONTENT }
+
+}
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+
+
+/* ------------------------------------------------------------ */
+/** Http URI.
+ * Parse a HTTP URI from a string or byte array. Given a URI
+ * <code>http://user@host:port/path/info;param?query#fragment</code>
+ * this class will split it into the following undecoded optional elements:<ul>
+ * <li>{@link #getScheme()} - http:</li>
+ * <li>{@link #getAuthority()} - //name@host:port</li>
+ * <li>{@link #getHost()} - host</li>
+ * <li>{@link #getPort()} - port</li>
+ * <li>{@link #getPath()} - /path/info</li>
+ * <li>{@link #getParam()} - param</li>
+ * <li>{@link #getQuery()} - query</li>
+ * <li>{@link #getFragment()} - fragment</li>
+ * </ul>
+ *
+ */
+public class HttpURI
+{
+ private static final byte[] __empty={};
+ private final static int
+ START=0,
+ AUTH_OR_PATH=1,
+ SCHEME_OR_PATH=2,
+ AUTH=4,
+ IPV6=5,
+ PORT=6,
+ PATH=7,
+ PARAM=8,
+ QUERY=9,
+ ASTERISK=10;
+
+ final Charset _charset;
+ boolean _partial=false;
+ byte[] _raw=__empty;
+ String _rawString;
+ int _scheme;
+ int _authority;
+ int _host;
+ int _port;
+ int _portValue;
+ int _path;
+ int _param;
+ int _query;
+ int _fragment;
+ int _end;
+ boolean _encoded=false;
+
+ public HttpURI()
+ {
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public HttpURI(Charset charset)
+ {
+ _charset = charset;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param parsePartialAuth If True, parse auth without prior scheme, else treat all URIs starting with / as paths
+ */
+ public HttpURI(boolean parsePartialAuth)
+ {
+ _partial=parsePartialAuth;
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public HttpURI(String raw)
+ {
+ _rawString=raw;
+ byte[] b = raw.getBytes(StandardCharsets.UTF_8);
+ parse(b,0,b.length);
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public HttpURI(byte[] raw,int offset, int length)
+ {
+ parse2(raw,offset,length);
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public HttpURI(URI uri)
+ {
+ parse(uri.toASCIIString());
+ _charset = URIUtil.__CHARSET;
+ }
+
+ public void parse(String raw)
+ {
+ byte[] b = StringUtil.getUtf8Bytes(raw);
+ parse2(b,0,b.length);
+ _rawString=raw;
+ }
+
+ public void parseConnect(String raw)
+ {
+ byte[] b = StringUtil.getBytes(raw);
+ parseConnect(b,0,b.length);
+ _rawString=raw;
+ }
+
+ public void parse(byte[] raw,int offset, int length)
+ {
+ _rawString=null;
+ parse2(raw,offset,length);
+ }
+
+
+ public void parseConnect(byte[] raw,int offset, int length)
+ {
+ _rawString=null;
+ _encoded=false;
+ _raw=raw;
+ int i=offset;
+ int e=offset+length;
+ int state=AUTH;
+ _end=offset+length;
+ _scheme=offset;
+ _authority=offset;
+ _host=offset;
+ _port=_end;
+ _portValue=-1;
+ _path=_end;
+ _param=_end;
+ _query=_end;
+ _fragment=_end;
+
+ loop: while (i<e)
+ {
+ char c=(char)(0xff&_raw[i]);
+ int s=i++;
+
+ switch (state)
+ {
+ case AUTH:
+ {
+ switch (c)
+ {
+ case ':':
+ {
+ _port = s;
+ break loop;
+ }
+ case '[':
+ {
+ state = IPV6;
+ break;
+ }
+ }
+ continue;
+ }
+
+ case IPV6:
+ {
+ switch (c)
+ {
+ case '/':
+ {
+ throw new IllegalArgumentException("No closing ']' for " + new String(_raw,offset,length,_charset));
+ }
+ case ']':
+ {
+ state = AUTH;
+ break;
+ }
+ }
+
+ continue;
+ }
+ }
+ }
+
+ if (_port<_path)
+ _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+ else
+ throw new IllegalArgumentException("No port");
+ _path=offset;
+ }
+
+
+ private void parse2(byte[] raw,int offset, int length)
+ {
+ _encoded=false;
+ _raw=raw;
+ int i=offset;
+ int e=offset+length;
+ int state=START;
+ int m=offset;
+ _end=offset+length;
+ _scheme=offset;
+ _authority=offset;
+ _host=offset;
+ _port=offset;
+ _portValue=-1;
+ _path=offset;
+ _param=_end;
+ _query=_end;
+ _fragment=_end;
+ while (i<e)
+ {
+ char c=(char)(0xff&_raw[i]);
+ int s=i++;
+
+ state: switch (state)
+ {
+ case START:
+ {
+ m=s;
+ switch(c)
+ {
+ case '/':
+ state=AUTH_OR_PATH;
+ break;
+ case ';':
+ _param=s;
+ state=PARAM;
+ break;
+ case '?':
+ _param=s;
+ _query=s;
+ state=QUERY;
+ break;
+ case '#':
+ _param=s;
+ _query=s;
+ _fragment=s;
+ break;
+ case '*':
+ _path=s;
+ state=ASTERISK;
+ break;
+
+ default:
+ state=SCHEME_OR_PATH;
+ }
+
+ continue;
+ }
+
+ case AUTH_OR_PATH:
+ {
+ if ((_partial||_scheme!=_authority) && c=='/')
+ {
+ _host=i;
+ _port=_end;
+ _path=_end;
+ state=AUTH;
+ }
+ else if (c==';' || c=='?' || c=='#')
+ {
+ i--;
+ state=PATH;
+ }
+ else
+ {
+ _host=m;
+ _port=m;
+ state=PATH;
+ }
+ continue;
+ }
+
+ case SCHEME_OR_PATH:
+ {
+ // short cut for http and https
+ if (length>6 && c=='t')
+ {
+ if (_raw[offset+3]==':')
+ {
+ s=offset+3;
+ i=offset+4;
+ c=':';
+ }
+ else if (_raw[offset+4]==':')
+ {
+ s=offset+4;
+ i=offset+5;
+ c=':';
+ }
+ else if (_raw[offset+5]==':')
+ {
+ s=offset+5;
+ i=offset+6;
+ c=':';
+ }
+ }
+
+ switch (c)
+ {
+ case ':':
+ {
+ m = i++;
+ _authority = m;
+ _path = m;
+ c = (char)(0xff & _raw[i]);
+ if (c == '/')
+ state = AUTH_OR_PATH;
+ else
+ {
+ _host = m;
+ _port = m;
+ state = PATH;
+ }
+ break;
+ }
+
+ case '/':
+ {
+ state = PATH;
+ break;
+ }
+
+ case ';':
+ {
+ _param = s;
+ state = PARAM;
+ break;
+ }
+
+ case '?':
+ {
+ _param = s;
+ _query = s;
+ state = QUERY;
+ break;
+ }
+
+ case '#':
+ {
+ _param = s;
+ _query = s;
+ _fragment = s;
+ break;
+ }
+ }
+ continue;
+ }
+
+ case AUTH:
+ {
+ switch (c)
+ {
+
+ case '/':
+ {
+ m = s;
+ _path = m;
+ _port = _path;
+ state = PATH;
+ break;
+ }
+ case '@':
+ {
+ _host = i;
+ break;
+ }
+ case ':':
+ {
+ _port = s;
+ state = PORT;
+ break;
+ }
+ case '[':
+ {
+ state = IPV6;
+ break;
+ }
+ }
+ continue;
+ }
+
+ case IPV6:
+ {
+ switch (c)
+ {
+ case '/':
+ {
+ throw new IllegalArgumentException("No closing ']' for " + new String(_raw,offset,length,_charset));
+ }
+ case ']':
+ {
+ state = AUTH;
+ break;
+ }
+ }
+
+ continue;
+ }
+
+ case PORT:
+ {
+ if (c=='/')
+ {
+ m=s;
+ _path=m;
+ if (_port<=_authority)
+ _port=_path;
+ state=PATH;
+ }
+ continue;
+ }
+
+ case PATH:
+ {
+ switch (c)
+ {
+ case ';':
+ {
+ _param = s;
+ state = PARAM;
+ break;
+ }
+ case '?':
+ {
+ _param = s;
+ _query = s;
+ state = QUERY;
+ break;
+ }
+ case '#':
+ {
+ _param = s;
+ _query = s;
+ _fragment = s;
+ break state;
+ }
+ case '%':
+ {
+ _encoded=true;
+ }
+ }
+ continue;
+ }
+
+ case PARAM:
+ {
+ switch (c)
+ {
+ case '?':
+ {
+ _query = s;
+ state = QUERY;
+ break;
+ }
+ case '#':
+ {
+ _query = s;
+ _fragment = s;
+ break state;
+ }
+ }
+ continue;
+ }
+
+ case QUERY:
+ {
+ if (c=='#')
+ {
+ _fragment=s;
+ break state;
+ }
+ continue;
+ }
+
+ case ASTERISK:
+ {
+ throw new IllegalArgumentException("only '*'");
+ }
+ }
+ }
+
+ if (_port<_path)
+ _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+ }
+
+ public String getScheme()
+ {
+ if (_scheme==_authority)
+ return null;
+ int l=_authority-_scheme;
+ if (l==5 &&
+ _raw[_scheme]=='h' &&
+ _raw[_scheme+1]=='t' &&
+ _raw[_scheme+2]=='t' &&
+ _raw[_scheme+3]=='p' )
+ return HttpScheme.HTTP.asString();
+ if (l==6 &&
+ _raw[_scheme]=='h' &&
+ _raw[_scheme+1]=='t' &&
+ _raw[_scheme+2]=='t' &&
+ _raw[_scheme+3]=='p' &&
+ _raw[_scheme+4]=='s' )
+ return HttpScheme.HTTPS.asString();
+
+ return new String(_raw,_scheme,_authority-_scheme-1,_charset);
+ }
+
+ public String getAuthority()
+ {
+ if (_authority==_path)
+ return null;
+ return new String(_raw,_authority,_path-_authority,_charset);
+ }
+
+ public String getHost()
+ {
+ if (_host==_port)
+ return null;
+ if (_raw[_host]=='[')
+ return new String(_raw,_host+1,_port-_host-2,_charset);
+ return new String(_raw,_host,_port-_host,_charset);
+ }
+
+ public int getPort()
+ {
+ return _portValue;
+ }
+
+ public String getPath()
+ {
+ if (_path==_param)
+ return null;
+ return new String(_raw,_path,_param-_path,_charset);
+ }
+
+ public String getDecodedPath()
+ {
+ if (_path==_param)
+ return null;
+
+ Utf8StringBuilder utf8b=null;
+
+ for (int i=_path;i<_param;i++)
+ {
+ byte b = _raw[i];
+
+ if (b=='%')
+ {
+ if (utf8b==null)
+ {
+ utf8b=new Utf8StringBuilder();
+ utf8b.append(_raw,_path,i-_path);
+ }
+
+ if ((i+2)>=_param)
+ throw new IllegalArgumentException("Bad % encoding: "+this);
+ if (_raw[i+1]=='u')
+ {
+ if ((i+5)>=_param)
+ throw new IllegalArgumentException("Bad %u encoding: "+this);
+ try
+ {
+ String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+ utf8b.getStringBuilder().append(unicode);
+ i+=5;
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ else
+ {
+ b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+ utf8b.append(b);
+ i+=2;
+ }
+ continue;
+ }
+ else if (utf8b!=null)
+ {
+ utf8b.append(b);
+ }
+ }
+
+ if (utf8b==null)
+ return StringUtil.toUTF8String(_raw, _path, _param-_path);
+ return utf8b.toString();
+ }
+
+ public String getDecodedPath(String encoding)
+ {
+ return getDecodedPath(Charset.forName(encoding));
+ }
+
+ public String getDecodedPath(Charset encoding)
+ {
+ if (_path==_param)
+ return null;
+
+ int length = _param-_path;
+ byte[] bytes=null;
+ int n=0;
+
+ for (int i=_path;i<_param;i++)
+ {
+ byte b = _raw[i];
+
+ if (b=='%')
+ {
+ if (bytes==null)
+ {
+ bytes=new byte[length];
+ System.arraycopy(_raw,_path,bytes,0,n);
+ }
+
+ if ((i+2)>=_param)
+ throw new IllegalArgumentException("Bad % encoding: "+this);
+ if (_raw[i+1]=='u')
+ {
+ if ((i+5)>=_param)
+ throw new IllegalArgumentException("Bad %u encoding: "+this);
+
+ try
+ {
+ String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+ byte[] encoded = unicode.getBytes(encoding);
+ System.arraycopy(encoded,0,bytes,n,encoded.length);
+ n+=encoded.length;
+ i+=5;
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ else
+ {
+ b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+ bytes[n++]=b;
+ i+=2;
+ }
+ continue;
+ }
+ else if (bytes==null)
+ {
+ n++;
+ continue;
+ }
+
+ bytes[n++]=b;
+ }
+
+
+ if (bytes==null)
+ return new String(_raw,_path,_param-_path,encoding);
+
+ return new String(bytes,0,n,encoding);
+ }
+
+ public String getPathAndParam()
+ {
+ if (_path==_query)
+ return null;
+ return new String(_raw,_path,_query-_path,_charset);
+ }
+
+ public String getCompletePath()
+ {
+ if (_path==_end)
+ return null;
+ return new String(_raw,_path,_end-_path,_charset);
+ }
+
+ public String getParam()
+ {
+ if (_param==_query)
+ return null;
+ return new String(_raw,_param+1,_query-_param-1,_charset);
+ }
+
+ public String getQuery()
+ {
+ if (_query==_fragment)
+ return null;
+ return new String(_raw,_query+1,_fragment-_query-1,_charset);
+ }
+
+ public String getQuery(String encoding)
+ {
+ if (_query==_fragment)
+ return null;
+ return StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding);
+ }
+
+ public boolean hasQuery()
+ {
+ return (_fragment>_query);
+ }
+
+ public String getFragment()
+ {
+ if (_fragment==_end)
+ return null;
+ return new String(_raw,_fragment+1,_end-_fragment-1,_charset);
+ }
+
+ public void decodeQueryTo(MultiMap<String> parameters)
+ {
+ if (_query==_fragment)
+ return;
+ if (_charset.equals(StandardCharsets.UTF_8))
+ UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+ else
+ UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,_charset),parameters,_charset,-1);
+ }
+
+ public void decodeQueryTo(MultiMap<String> parameters, String encoding) throws UnsupportedEncodingException
+ {
+ if (_query==_fragment)
+ return;
+
+ if (encoding==null || StringUtil.isUTF8(encoding))
+ UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+ else
+ UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
+ }
+
+ public void decodeQueryTo(MultiMap<String> parameters, Charset encoding) throws UnsupportedEncodingException
+ {
+ if (_query==_fragment)
+ return;
+
+ if (encoding==null || StandardCharsets.UTF_8.equals(encoding))
+ UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+ else
+ UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding,-1);
+ }
+
+ public void clear()
+ {
+ _scheme=_authority=_host=_port=_path=_param=_query=_fragment=_end=0;
+ _raw=__empty;
+ _rawString="";
+ _encoded=false;
+ }
+
+ @Override
+ public String toString()
+ {
+ if (_rawString==null)
+ _rawString=new String(_raw,_scheme,_end-_scheme,_charset);
+ return _rawString;
+ }
+
+ public void writeTo(Utf8StringBuilder buf)
+ {
+ buf.append(_raw,_scheme,_end-_scheme);
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+
+
+/* ------------------------------------------------------------------------------- */
+public enum HttpVersion
+{
+ HTTP_0_9("HTTP/0.9",9),
+ HTTP_1_0("HTTP/1.0",10),
+ HTTP_1_1("HTTP/1.1",11),
+ HTTP_2_0("HTTP/2.0",20);
+
+ /* ------------------------------------------------------------ */
+ public final static Trie<HttpVersion> CACHE= new ArrayTrie<HttpVersion>();
+ static
+ {
+ for (HttpVersion version : HttpVersion.values())
+ CACHE.put(version.toString(),version);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a Http Version and whitespace in a byte array.
+ * @param bytes Array containing ISO-8859-1 characters
+ * @param position The first valid index
+ * @param limit The first non valid index
+ * @return A HttpMethod if a match or null if no easy match.
+ */
+ public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit)
+ {
+ int length=limit-position;
+ if (length<9)
+ return null;
+
+ if (bytes[position+4]=='/' && bytes[position+6]=='.' && Character.isWhitespace((char)bytes[position+8]) &&
+ ((bytes[position]=='H' && bytes[position+1]=='T' && bytes[position+2]=='T' && bytes[position+3]=='P') ||
+ (bytes[position]=='h' && bytes[position+1]=='t' && bytes[position+2]=='t' && bytes[position+3]=='p')))
+ {
+ switch(bytes[position+5])
+ {
+ case '1':
+ switch(bytes[position+7])
+ {
+ case '0':
+ return HTTP_1_0;
+ case '1':
+ return HTTP_1_1;
+ }
+ break;
+ case '2':
+ switch(bytes[position+7])
+ {
+ case '0':
+ return HTTP_2_0;
+ }
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Optimised lookup to find a HTTP Version and trailing white space in a byte array.
+ * @param buffer buffer containing ISO-8859-1 characters
+ * @return A HttpVersion if a match or null if no easy match.
+ */
+ public static HttpVersion lookAheadGet(ByteBuffer buffer)
+ {
+ if (buffer.hasArray())
+ return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
+ return null;
+ }
+
+
+ private final String _string;
+ private final byte[] _bytes;
+ private final ByteBuffer _buffer;
+ private final int _version;
+
+ /* ------------------------------------------------------------ */
+ HttpVersion(String s,int version)
+ {
+ _string=s;
+ _bytes=StringUtil.getBytes(s);
+ _buffer=ByteBuffer.wrap(_bytes);
+ _version=version;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] toBytes()
+ {
+ return _bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer toBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getVersion()
+ {
+ return _version;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+ /**
+ * Case insensitive fromString() conversion
+ * @param version the String to convert to enum constant
+ * @return the enum constant or null if version unknown
+ */
+ public static HttpVersion fromString(String version)
+ {
+ return CACHE.get(version);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static HttpVersion fromVersion(int version)
+ {
+ switch(version)
+ {
+ case 9: return HttpVersion.HTTP_0_9;
+ case 10: return HttpVersion.HTTP_1_0;
+ case 11: return HttpVersion.HTTP_1_1;
+ default: throw new IllegalArgumentException();
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ *
+ */
+public class MimeTypes
+{
+ public enum Type
+ {
+ FORM_ENCODED("application/x-www-form-urlencoded"),
+ MESSAGE_HTTP("message/http"),
+ MULTIPART_BYTERANGES("multipart/byteranges"),
+
+ TEXT_HTML("text/html"),
+ TEXT_PLAIN("text/plain"),
+ TEXT_XML("text/xml"),
+ TEXT_JSON("text/json",StandardCharsets.UTF_8),
+ APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
+
+ TEXT_HTML_8859_1("text/html; charset=ISO-8859-1",TEXT_HTML),
+ TEXT_HTML_UTF_8("text/html; charset=UTF-8",TEXT_HTML),
+
+ TEXT_PLAIN_8859_1("text/plain; charset=ISO-8859-1",TEXT_PLAIN),
+ TEXT_PLAIN_UTF_8("text/plain; charset=UTF-8",TEXT_PLAIN),
+
+ TEXT_XML_8859_1("text/xml; charset=ISO-8859-1",TEXT_XML),
+ TEXT_XML_UTF_8("text/xml; charset=UTF-8",TEXT_XML),
+
+ TEXT_JSON_8859_1("text/json; charset=ISO-8859-1",TEXT_JSON),
+ TEXT_JSON_UTF_8("text/json; charset=UTF-8",TEXT_JSON),
+
+ APPLICATION_JSON_8859_1("text/json; charset=ISO-8859-1",APPLICATION_JSON),
+ APPLICATION_JSON_UTF_8("text/json; charset=UTF-8",APPLICATION_JSON);
+
+
+ /* ------------------------------------------------------------ */
+ private final String _string;
+ private final Type _base;
+ private final ByteBuffer _buffer;
+ private final Charset _charset;
+ private final boolean _assumedCharset;
+ private final HttpField _field;
+
+ /* ------------------------------------------------------------ */
+ Type(String s)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ _base=this;
+ _charset=null;
+ _assumedCharset=false;
+ _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+ }
+
+ /* ------------------------------------------------------------ */
+ Type(String s,Type base)
+ {
+ _string=s;
+ _buffer=BufferUtil.toBuffer(s);
+ _base=base;
+ int i=s.indexOf("; charset=");
+ _charset=Charset.forName(s.substring(i+10));
+ _assumedCharset=false;
+ _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+ }
+
+ /* ------------------------------------------------------------ */
+ Type(String s,Charset cs)
+ {
+ _string=s;
+ _base=this;
+ _buffer=BufferUtil.toBuffer(s);
+ _charset=cs;
+ _assumedCharset=true;
+ _field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteBuffer asBuffer()
+ {
+ return _buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ public Charset getCharset()
+ {
+ return _charset;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean is(String s)
+ {
+ return _string.equalsIgnoreCase(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String asString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _string;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCharsetAssumed()
+ {
+ return _assumedCharset;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpField getContentTypeField()
+ {
+ return _field;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Type getBaseType()
+ {
+ return _base;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private static final Logger LOG = Log.getLogger(MimeTypes.class);
+ public final static Trie<MimeTypes.Type> CACHE= new ArrayTrie<>(512);
+ private final static Trie<ByteBuffer> TYPES= new ArrayTrie<ByteBuffer>(512);
+ private final static Map<String,String> __dftMimeMap = new HashMap<String,String>();
+ private final static Map<String,String> __encodings = new HashMap<String,String>();
+
+ static
+ {
+ for (MimeTypes.Type type : MimeTypes.Type.values())
+ {
+ CACHE.put(type.toString(),type);
+ TYPES.put(type.toString(),type.asBuffer());
+
+ int charset=type.toString().indexOf(";charset=");
+ if (charset>0)
+ {
+ CACHE.put(type.toString().replace(";charset=","; charset="),type);
+ TYPES.put(type.toString().replace(";charset=","; charset="),type.asBuffer());
+ }
+ }
+
+ try
+ {
+ ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
+ Enumeration<String> i = mime.getKeys();
+ while(i.hasMoreElements())
+ {
+ String ext = i.nextElement();
+ String m = mime.getString(ext);
+ __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
+ }
+ }
+ catch(MissingResourceException e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+
+ try
+ {
+ ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
+ Enumeration<String> i = encoding.getKeys();
+ while(i.hasMoreElements())
+ {
+ String type = i.nextElement();
+ __encodings.put(type,encoding.getString(type));
+ }
+ }
+ catch(MissingResourceException e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private final Map<String,String> _mimeMap=new HashMap<String,String>();
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ */
+ public MimeTypes()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized Map<String,String> getMimeMap()
+ {
+ return _mimeMap;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param mimeMap A Map of file extension to mime-type.
+ */
+ public void setMimeMap(Map<String,String> mimeMap)
+ {
+ _mimeMap.clear();
+ if (mimeMap!=null)
+ {
+ for (Entry<String, String> ext : mimeMap.entrySet())
+ _mimeMap.put(StringUtil.asciiToLowerCase(ext.getKey()),normalizeMimeType(ext.getValue()));
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the MIME type by filename extension.
+ * @param filename A file name
+ * @return MIME type matching the longest dot extension of the
+ * file name.
+ */
+ public String getMimeByExtension(String filename)
+ {
+ String type=null;
+
+ if (filename!=null)
+ {
+ int i=-1;
+ while(type==null)
+ {
+ i=filename.indexOf(".",i+1);
+
+ if (i<0 || i>=filename.length())
+ break;
+
+ String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
+ if (_mimeMap!=null)
+ type=_mimeMap.get(ext);
+ if (type==null)
+ type=__dftMimeMap.get(ext);
+ }
+ }
+
+ if (type==null)
+ {
+ if (_mimeMap!=null)
+ type=_mimeMap.get("*");
+ if (type==null)
+ type=__dftMimeMap.get("*");
+ }
+
+ return type;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set a mime mapping
+ * @param extension
+ * @param type
+ */
+ public void addMimeMapping(String extension,String type)
+ {
+ _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
+ }
+
+ /* ------------------------------------------------------------ */
+ public static Set<String> getKnownMimeTypes()
+ {
+ return new HashSet<>(__dftMimeMap.values());
+ }
+
+ /* ------------------------------------------------------------ */
+ private static String normalizeMimeType(String type)
+ {
+ MimeTypes.Type t =CACHE.get(type);
+ if (t!=null)
+ return t.asString();
+
+ return StringUtil.asciiToLowerCase(type);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String getCharsetFromContentType(String value)
+ {
+ if (value==null)
+ return null;
+ int end=value.length();
+ int state=0;
+ int start=0;
+ boolean quote=false;
+ int i=0;
+ for (;i<end;i++)
+ {
+ char b = value.charAt(i);
+
+ if (quote && state!=10)
+ {
+ if ('"'==b)
+ quote=false;
+ continue;
+ }
+
+ switch(state)
+ {
+ case 0:
+ if ('"'==b)
+ {
+ quote=true;
+ break;
+ }
+ if (';'==b)
+ state=1;
+ break;
+
+ case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
+ case 2: if ('h'==b) state=3; else state=0;break;
+ case 3: if ('a'==b) state=4; else state=0;break;
+ case 4: if ('r'==b) state=5; else state=0;break;
+ case 5: if ('s'==b) state=6; else state=0;break;
+ case 6: if ('e'==b) state=7; else state=0;break;
+ case 7: if ('t'==b) state=8; else state=0;break;
+
+ case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
+
+ case 9:
+ if (' '==b)
+ break;
+ if ('"'==b)
+ {
+ quote=true;
+ start=i+1;
+ state=10;
+ break;
+ }
+ start=i;
+ state=10;
+ break;
+
+ case 10:
+ if (!quote && (';'==b || ' '==b )||
+ (quote && '"'==b ))
+ return StringUtil.normalizeCharset(value,start,i-start);
+ }
+ }
+
+ if (state==10)
+ return StringUtil.normalizeCharset(value,start,i-start);
+
+ return null;
+ }
+
+ public static String inferCharsetFromContentType(String value)
+ {
+ return __encodings.get(value);
+ }
+
+ public static String getContentTypeWithoutCharset(String value)
+ {
+ int end=value.length();
+ int state=0;
+ int start=0;
+ boolean quote=false;
+ int i=0;
+ StringBuilder builder=null;
+ for (;i<end;i++)
+ {
+ char b = value.charAt(i);
+
+ if ('"'==b)
+ {
+ if (quote)
+ {
+ quote=false;
+ }
+ else
+ {
+ quote=true;
+ }
+
+ switch(state)
+ {
+ case 11:
+ builder.append(b);break;
+ case 10:
+ break;
+ case 9:
+ builder=new StringBuilder();
+ builder.append(value,0,start+1);
+ state=10;
+ break;
+ default:
+ start=i;
+ state=0;
+ }
+ continue;
+ }
+
+ if (quote)
+ {
+ if (builder!=null && state!=10)
+ builder.append(b);
+ continue;
+ }
+
+ switch(state)
+ {
+ case 0:
+ if (';'==b)
+ state=1;
+ else if (' '!=b)
+ start=i;
+ break;
+
+ case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
+ case 2: if ('h'==b) state=3; else state=0;break;
+ case 3: if ('a'==b) state=4; else state=0;break;
+ case 4: if ('r'==b) state=5; else state=0;break;
+ case 5: if ('s'==b) state=6; else state=0;break;
+ case 6: if ('e'==b) state=7; else state=0;break;
+ case 7: if ('t'==b) state=8; else state=0;break;
+ case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
+
+ case 9:
+ if (' '==b)
+ break;
+ builder=new StringBuilder();
+ builder.append(value,0,start+1);
+ state=10;
+ break;
+
+ case 10:
+ if (';'==b)
+ {
+ builder.append(b);
+ state=11;
+ }
+ break;
+ case 11:
+ if (' '!=b)
+ builder.append(b);
+ }
+ }
+ if (builder==null)
+ return value;
+ return builder.toString();
+
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.http;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.URIUtil;
+
+/* ------------------------------------------------------------ */
+/** URI path map to Object.
+ * This mapping implements the path specification recommended
+ * in the 2.2 Servlet API.
+ *
+ * Path specifications can be of the following forms:<PRE>
+ * /foo/bar - an exact path specification.
+ * /foo/* - a prefix path specification (must end '/*').
+ * *.ext - a suffix path specification.
+ * / - the default path specification.
+ * "" - the / path specification
+ * </PRE>
+ * Matching is performed in the following order <NL>
+ * <LI>Exact match.
+ * <LI>Longest prefix match.
+ * <LI>Longest suffix match.
+ * <LI>default.
+ * </NL>
+ * Multiple path specifications can be mapped by providing a list of
+ * specifications. By default this class uses characters ":," as path
+ * separators, unless configured differently by calling the static
+ * method @see PathMap#setPathSpecSeparators(String)
+ * <P>
+ * Special characters within paths such as '?� and ';' are not treated specially
+ * as it is assumed they would have been either encoded in the original URL or
+ * stripped from the path.
+ * <P>
+ * This class is not synchronized. If concurrent modifications are
+ * possible then it should be synchronized at a higher level.
+ *
+ *
+ */
+public class PathMap<O> extends HashMap<String,O>
+{
+ /* ------------------------------------------------------------ */
+ private static String __pathSpecSeparators = ":,";
+
+ /* ------------------------------------------------------------ */
+ /** Set the path spec separator.
+ * Multiple path specification may be included in a single string
+ * if they are separated by the characters set in this string.
+ * By default this class uses ":," characters as path separators.
+ * @param s separators
+ */
+ public static void setPathSpecSeparators(String s)
+ {
+ __pathSpecSeparators=s;
+ }
+
+ /* --------------------------------------------------------------- */
+ Trie<MappedEntry<O>> _prefixMap=new ArrayTernaryTrie<>(false);
+ Trie<MappedEntry<O>> _suffixMap=new ArrayTernaryTrie<>(false);
+ final Map<String,MappedEntry<O>> _exactMap=new HashMap<>();
+
+ List<MappedEntry<O>> _defaultSingletonList=null;
+ MappedEntry<O> _prefixDefault=null;
+ MappedEntry<O> _default=null;
+ boolean _nodefault=false;
+
+ /* --------------------------------------------------------------- */
+ public PathMap()
+ {
+ this(11);
+ }
+
+ /* --------------------------------------------------------------- */
+ public PathMap(boolean noDefault)
+ {
+ this(11, noDefault);
+ }
+
+ /* --------------------------------------------------------------- */
+ public PathMap(int capacity)
+ {
+ this(capacity, false);
+ }
+
+ /* --------------------------------------------------------------- */
+ private PathMap(int capacity, boolean noDefault)
+ {
+ super(capacity);
+ _nodefault=noDefault;
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Construct from dictionary PathMap.
+ */
+ public PathMap(Map<String, ? extends O> m)
+ {
+ putAll(m);
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Add a single path match to the PathMap.
+ * @param pathSpec The path specification, or comma separated list of
+ * path specifications.
+ * @param object The object the path maps to
+ */
+ @Override
+ public O put(String pathSpec, O object)
+ {
+ if ("".equals(pathSpec.trim()))
+ {
+ MappedEntry<O> entry = new MappedEntry<>("",object);
+ entry.setMapped("");
+ _exactMap.put("", entry);
+ return super.put("", object);
+ }
+
+ StringTokenizer tok = new StringTokenizer(pathSpec,__pathSpecSeparators);
+ O old =null;
+
+ while (tok.hasMoreTokens())
+ {
+ String spec=tok.nextToken();
+
+ if (!spec.startsWith("/") && !spec.startsWith("*."))
+ throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'");
+
+ old = super.put(spec,object);
+
+ // Make entry that was just created.
+ MappedEntry<O> entry = new MappedEntry<>(spec,object);
+
+ if (entry.getKey().equals(spec))
+ {
+ if (spec.equals("/*"))
+ _prefixDefault=entry;
+ else if (spec.endsWith("/*"))
+ {
+ String mapped=spec.substring(0,spec.length()-2);
+ entry.setMapped(mapped);
+ while (!_prefixMap.put(mapped,entry))
+ _prefixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_prefixMap,1.5);
+ }
+ else if (spec.startsWith("*."))
+ {
+ String suffix=spec.substring(2);
+ while(!_suffixMap.put(suffix,entry))
+ _suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie<MappedEntry<O>>)_suffixMap,1.5);
+ }
+ else if (spec.equals(URIUtil.SLASH))
+ {
+ if (_nodefault)
+ _exactMap.put(spec,entry);
+ else
+ {
+ _default=entry;
+ _defaultSingletonList=Collections.singletonList(_default);
+ }
+ }
+ else
+ {
+ entry.setMapped(spec);
+ _exactMap.put(spec,entry);
+ }
+ }
+ }
+
+ return old;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get object matched by the path.
+ * @param path the path.
+ * @return Best matched object or null.
+ */
+ public O match(String path)
+ {
+ MappedEntry<O> entry = getMatch(path);
+ if (entry!=null)
+ return entry.getValue();
+ return null;
+ }
+
+
+ /* --------------------------------------------------------------- */
+ /** Get the entry mapped by the best specification.
+ * @param path the path.
+ * @return Map.Entry of the best matched or null.
+ */
+ public MappedEntry<O> getMatch(String path)
+ {
+ if (path==null)
+ return null;
+
+ int l=path.length();
+
+ MappedEntry<O> entry=null;
+
+ //special case
+ if (l == 1 && path.charAt(0)=='/')
+ {
+ entry = _exactMap.get("");
+ if (entry != null)
+ return entry;
+ }
+
+ // try exact match
+ entry=_exactMap.get(path);
+ if (entry!=null)
+ return entry;
+
+ // prefix search
+ int i=l;
+ final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
+ while(i>=0)
+ {
+ entry=prefix_map.getBest(path,0,i);
+ if (entry==null)
+ break;
+ String key = entry.getKey();
+ if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+ return entry;
+ i=key.length()-3;
+ }
+
+ // Prefix Default
+ if (_prefixDefault!=null)
+ return _prefixDefault;
+
+ // Extension search
+ i=0;
+ final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
+ while ((i=path.indexOf('.',i+1))>0)
+ {
+ entry=suffix_map.get(path,i+1,l-i-1);
+ if (entry!=null)
+ return entry;
+ }
+
+ // Default
+ return _default;
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Get all entries matched by the path.
+ * Best match first.
+ * @param path Path to match
+ * @return List of Map.Entry instances key=pathSpec
+ */
+ public List<? extends Map.Entry<String,O>> getMatches(String path)
+ {
+ MappedEntry<O> entry;
+ List<MappedEntry<O>> entries=new ArrayList<>();
+
+ if (path==null)
+ return entries;
+ if (path.length()==0)
+ return _defaultSingletonList;
+
+ // try exact match
+ entry=_exactMap.get(path);
+ if (entry!=null)
+ entries.add(entry);
+
+ // prefix search
+ int l=path.length();
+ int i=l;
+ final Trie<PathMap.MappedEntry<O>> prefix_map=_prefixMap;
+ while(i>=0)
+ {
+ entry=prefix_map.getBest(path,0,i);
+ if (entry==null)
+ break;
+ String key = entry.getKey();
+ if (key.length()-2>=path.length() || path.charAt(key.length()-2)=='/')
+ entries.add(entry);
+
+ i=key.length()-3;
+ }
+
+ // Prefix Default
+ if (_prefixDefault!=null)
+ entries.add(_prefixDefault);
+
+ // Extension search
+ i=0;
+ final Trie<PathMap.MappedEntry<O>> suffix_map=_suffixMap;
+ while ((i=path.indexOf('.',i+1))>0)
+ {
+ entry=suffix_map.get(path,i+1,l-i-1);
+ if (entry!=null)
+ entries.add(entry);
+ }
+
+ // root match
+ if ("/".equals(path))
+ {
+ entry=_exactMap.get("");
+ if (entry!=null)
+ entries.add(entry);
+ }
+
+ // Default
+ if (_default!=null)
+ entries.add(_default);
+
+ return entries;
+ }
+
+
+ /* --------------------------------------------------------------- */
+ /** Return whether the path matches any entries in the PathMap,
+ * excluding the default entry
+ * @param path Path to match
+ * @return Whether the PathMap contains any entries that match this
+ */
+ public boolean containsMatch(String path)
+ {
+ MappedEntry<?> match = getMatch(path);
+ return match!=null && !match.equals(_default);
+ }
+
+ /* --------------------------------------------------------------- */
+ @Override
+ public O remove(Object pathSpec)
+ {
+ if (pathSpec!=null)
+ {
+ String spec=(String) pathSpec;
+ if (spec.equals("/*"))
+ _prefixDefault=null;
+ else if (spec.endsWith("/*"))
+ _prefixMap.remove(spec.substring(0,spec.length()-2));
+ else if (spec.startsWith("*."))
+ _suffixMap.remove(spec.substring(2));
+ else if (spec.equals(URIUtil.SLASH))
+ {
+ _default=null;
+ _defaultSingletonList=null;
+ }
+ else
+ _exactMap.remove(spec);
+ }
+ return super.remove(pathSpec);
+ }
+
+ /* --------------------------------------------------------------- */
+ @Override
+ public void clear()
+ {
+ _exactMap.clear();
+ _prefixMap=new ArrayTernaryTrie<>(false);
+ _suffixMap=new ArrayTernaryTrie<>(false);
+ _default=null;
+ _defaultSingletonList=null;
+ _prefixDefault=null;
+ super.clear();
+ }
+
+ /* --------------------------------------------------------------- */
+ /**
+ * @return true if match.
+ */
+ public static boolean match(String pathSpec, String path)
+ throws IllegalArgumentException
+ {
+ return match(pathSpec, path, false);
+ }
+
+ /* --------------------------------------------------------------- */
+ /**
+ * @return true if match.
+ */
+ public static boolean match(String pathSpec, String path, boolean noDefault)
+ throws IllegalArgumentException
+ {
+ if (pathSpec.length()==0)
+ return "/".equals(path);
+
+ char c = pathSpec.charAt(0);
+ if (c=='/')
+ {
+ if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path))
+ return true;
+
+ if(isPathWildcardMatch(pathSpec, path))
+ return true;
+ }
+ else if (c=='*')
+ return path.regionMatches(path.length()-pathSpec.length()+1,
+ pathSpec,1,pathSpec.length()-1);
+ return false;
+ }
+
+ /* --------------------------------------------------------------- */
+ private static boolean isPathWildcardMatch(String pathSpec, String path)
+ {
+ // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+ int cpl=pathSpec.length()-2;
+ if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl))
+ {
+ if (path.length()==cpl || '/'==path.charAt(cpl))
+ return true;
+ }
+ return false;
+ }
+
+
+ /* --------------------------------------------------------------- */
+ /** Return the portion of a path that matches a path spec.
+ * @return null if no match at all.
+ */
+ public static String pathMatch(String pathSpec, String path)
+ {
+ char c = pathSpec.charAt(0);
+
+ if (c=='/')
+ {
+ if (pathSpec.length()==1)
+ return path;
+
+ if (pathSpec.equals(path))
+ return path;
+
+ if (isPathWildcardMatch(pathSpec, path))
+ return path.substring(0,pathSpec.length()-2);
+ }
+ else if (c=='*')
+ {
+ if (path.regionMatches(path.length()-(pathSpec.length()-1),
+ pathSpec,1,pathSpec.length()-1))
+ return path;
+ }
+ return null;
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Return the portion of a path that is after a path spec.
+ * @return The path info string
+ */
+ public static String pathInfo(String pathSpec, String path)
+ {
+ if ("".equals(pathSpec))
+ return path; //servlet 3 spec sec 12.2 will be '/'
+
+ char c = pathSpec.charAt(0);
+
+ if (c=='/')
+ {
+ if (pathSpec.length()==1)
+ return null;
+
+ boolean wildcard = isPathWildcardMatch(pathSpec, path);
+
+ // handle the case where pathSpec uses a wildcard and path info is "/*"
+ if (pathSpec.equals(path) && !wildcard)
+ return null;
+
+ if (wildcard)
+ {
+ if (path.length()==pathSpec.length()-2)
+ return null;
+ return path.substring(pathSpec.length()-2);
+ }
+ }
+ return null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Relative path.
+ * @param base The base the path is relative to.
+ * @param pathSpec The spec of the path segment to ignore.
+ * @param path the additional path
+ * @return base plus path with pathspec removed
+ */
+ public static String relativePath(String base,
+ String pathSpec,
+ String path )
+ {
+ String info=pathInfo(pathSpec,path);
+ if (info==null)
+ info=path;
+
+ if( info.startsWith( "./"))
+ info = info.substring( 2);
+ if( base.endsWith( URIUtil.SLASH))
+ if( info.startsWith( URIUtil.SLASH))
+ path = base + info.substring(1);
+ else
+ path = base + info;
+ else
+ if( info.startsWith( URIUtil.SLASH))
+ path = base + info;
+ else
+ path = base + URIUtil.SLASH + info;
+ return path;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class MappedEntry<O> implements Map.Entry<String,O>
+ {
+ private final String key;
+ private final O value;
+ private String mapped;
+
+ MappedEntry(String key, O value)
+ {
+ this.key=key;
+ this.value=value;
+ }
+
+ @Override
+ public String getKey()
+ {
+ return key;
+ }
+
+ @Override
+ public O getValue()
+ {
+ return value;
+ }
+
+ @Override
+ public O setValue(O o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString()
+ {
+ return key+"="+value;
+ }
+
+ public String getMapped()
+ {
+ return mapped;
+ }
+
+ void setMapped(String mapped)
+ {
+ this.mapped = mapped;
+ }
+ }
+}
--- /dev/null
+text/html = ISO-8859-1
+text/plain = ISO-8859-1
+text/xml = UTF-8
+text/json = UTF-8
--- /dev/null
+ai=application/postscript
+aif=audio/x-aiff
+aifc=audio/x-aiff
+aiff=audio/x-aiff
+apk=application/vnd.android.package-archive
+asc=text/plain
+asf=video/x.ms.asf
+asx=video/x.ms.asx
+au=audio/basic
+avi=video/x-msvideo
+bcpio=application/x-bcpio
+bin=application/octet-stream
+cab=application/x-cabinet
+cdf=application/x-netcdf
+class=application/java-vm
+cpio=application/x-cpio
+cpt=application/mac-compactpro
+crt=application/x-x509-ca-cert
+csh=application/x-csh
+css=text/css
+csv=text/comma-separated-values
+dcr=application/x-director
+dir=application/x-director
+dll=application/x-msdownload
+dms=application/octet-stream
+doc=application/msword
+dtd=application/xml-dtd
+dvi=application/x-dvi
+dxr=application/x-director
+eps=application/postscript
+etx=text/x-setext
+exe=application/octet-stream
+ez=application/andrew-inset
+gif=image/gif
+gtar=application/x-gtar
+gz=application/gzip
+gzip=application/gzip
+hdf=application/x-hdf
+hqx=application/mac-binhex40
+htc=text/x-component
+htm=text/html
+html=text/html
+ice=x-conference/x-cooltalk
+ico=image/x-icon
+ief=image/ief
+iges=model/iges
+igs=model/iges
+jad=text/vnd.sun.j2me.app-descriptor
+jar=application/java-archive
+java=text/plain
+jnlp=application/x-java-jnlp-file
+jpe=image/jpeg
+jpeg=image/jpeg
+jpg=image/jpeg
+js=application/javascript
+json=application/json
+jsp=text/html
+kar=audio/midi
+latex=application/x-latex
+lha=application/octet-stream
+lzh=application/octet-stream
+man=application/x-troff-man
+mathml=application/mathml+xml
+me=application/x-troff-me
+mesh=model/mesh
+mid=audio/midi
+midi=audio/midi
+mif=application/vnd.mif
+mol=chemical/x-mdl-molfile
+mov=video/quicktime
+movie=video/x-sgi-movie
+mp2=audio/mpeg
+mp3=audio/mpeg
+mpe=video/mpeg
+mpeg=video/mpeg
+mpg=video/mpeg
+mpga=audio/mpeg
+ms=application/x-troff-ms
+msh=model/mesh
+msi=application/octet-stream
+nc=application/x-netcdf
+oda=application/oda
+odb=application/vnd.oasis.opendocument.database
+odc=application/vnd.oasis.opendocument.chart
+odf=application/vnd.oasis.opendocument.formula
+odg=application/vnd.oasis.opendocument.graphics
+odi=application/vnd.oasis.opendocument.image
+odm=application/vnd.oasis.opendocument.text-master
+odp=application/vnd.oasis.opendocument.presentation
+ods=application/vnd.oasis.opendocument.spreadsheet
+odt=application/vnd.oasis.opendocument.text
+ogg=application/ogg
+otc=application/vnd.oasis.opendocument.chart-template
+otf=application/vnd.oasis.opendocument.formula-template
+otg=application/vnd.oasis.opendocument.graphics-template
+oth=application/vnd.oasis.opendocument.text-web
+oti=application/vnd.oasis.opendocument.image-template
+otp=application/vnd.oasis.opendocument.presentation-template
+ots=application/vnd.oasis.opendocument.spreadsheet-template
+ott=application/vnd.oasis.opendocument.text-template
+pbm=image/x-portable-bitmap
+pdb=chemical/x-pdb
+pdf=application/pdf
+pgm=image/x-portable-graymap
+pgn=application/x-chess-pgn
+png=image/png
+pnm=image/x-portable-anymap
+ppm=image/x-portable-pixmap
+pps=application/vnd.ms-powerpoint
+ppt=application/vnd.ms-powerpoint
+ps=application/postscript
+qml=text/x-qml
+qt=video/quicktime
+ra=audio/x-pn-realaudio
+ram=audio/x-pn-realaudio
+ras=image/x-cmu-raster
+rdf=application/rdf+xml
+rgb=image/x-rgb
+rm=audio/x-pn-realaudio
+roff=application/x-troff
+rpm=application/x-rpm
+rtf=application/rtf
+rtx=text/richtext
+rv=video/vnd.rn-realvideo
+ser=application/java-serialized-object
+sgm=text/sgml
+sgml=text/sgml
+sh=application/x-sh
+shar=application/x-shar
+silo=model/mesh
+sit=application/x-stuffit
+skd=application/x-koan
+skm=application/x-koan
+skp=application/x-koan
+skt=application/x-koan
+smi=application/smil
+smil=application/smil
+snd=audio/basic
+spl=application/x-futuresplash
+src=application/x-wais-source
+sv4cpio=application/x-sv4cpio
+sv4crc=application/x-sv4crc
+svg=image/svg+xml
+swf=application/x-shockwave-flash
+t=application/x-troff
+tar=application/x-tar
+tar.gz=application/x-gtar
+tcl=application/x-tcl
+tex=application/x-tex
+texi=application/x-texinfo
+texinfo=application/x-texinfo
+tgz=application/x-gtar
+tif=image/tiff
+tiff=image/tiff
+tr=application/x-troff
+tsv=text/tab-separated-values
+txt=text/plain
+ustar=application/x-ustar
+vcd=application/x-cdlink
+vrml=model/vrml
+vxml=application/voicexml+xml
+wav=audio/x-wav
+wbmp=image/vnd.wap.wbmp
+wml=text/vnd.wap.wml
+wmlc=application/vnd.wap.wmlc
+wmls=text/vnd.wap.wmlscript
+wmlsc=application/vnd.wap.wmlscriptc
+wrl=model/vrml
+wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate
+xbm=image/x-xbitmap
+xht=application/xhtml+xml
+xhtml=application/xhtml+xml
+xls=application/vnd.ms-excel
+xml=application/xml
+xpm=image/x-xpixmap
+xsd=application/xml
+xsl=application/xml
+xslt=application/xslt+xml
+xul=application/vnd.mozilla.xul+xml
+xwd=image/x-xwindowdump
+xyz=chemical/x-xyz
+z=application/compress
+zip=application/zip
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Http : Tools for Http processing
+ */
+package org.eclipse.jetty.http;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+/**
+ * <p>A convenience base implementation of {@link Connection}.</p>
+ * <p>This class uses the capabilities of the {@link EndPoint} API to provide a
+ * more traditional style of async reading. A call to {@link #fillInterested()}
+ * will schedule a callback to {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+ * as appropriate.</p>
+ */
+public abstract class AbstractConnection implements Connection
+{
+ private static final Logger LOG = Log.getLogger(AbstractConnection.class);
+
+ public static final boolean EXECUTE_ONFILLABLE=true;
+
+ private final List<Listener> listeners = new CopyOnWriteArrayList<>();
+ private final AtomicReference<State> _state = new AtomicReference<>(IDLE);
+ private final long _created=System.currentTimeMillis();
+ private final EndPoint _endPoint;
+ private final Executor _executor;
+ private final Callback _readCallback;
+ private final boolean _executeOnfillable;
+ private int _inputBufferSize=2048;
+
+ protected AbstractConnection(EndPoint endp, Executor executor)
+ {
+ this(endp,executor,EXECUTE_ONFILLABLE);
+ }
+
+ protected AbstractConnection(EndPoint endp, Executor executor, final boolean executeOnfillable)
+ {
+ if (executor == null)
+ throw new IllegalArgumentException("Executor must not be null!");
+ _endPoint = endp;
+ _executor = executor;
+ _readCallback = new ReadCallback();
+ _executeOnfillable=executeOnfillable;
+ _state.set(IDLE);
+ }
+
+ @Override
+ public void addListener(Listener listener)
+ {
+ listeners.add(listener);
+ }
+
+ public int getInputBufferSize()
+ {
+ return _inputBufferSize;
+ }
+
+ public void setInputBufferSize(int inputBufferSize)
+ {
+ _inputBufferSize = inputBufferSize;
+ }
+
+ protected Executor getExecutor()
+ {
+ return _executor;
+ }
+
+ protected void failedCallback(final Callback callback, final Throwable x)
+ {
+ if (NonBlockingThread.isNonBlockingThread())
+ {
+ try
+ {
+ getExecutor().execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ callback.failed(x);
+ }
+ });
+ }
+ catch(RejectedExecutionException e)
+ {
+ LOG.debug(e);
+ callback.failed(x);
+ }
+ }
+ else
+ {
+ callback.failed(x);
+ }
+ }
+
+ /**
+ * <p>Utility method to be called to register read interest.</p>
+ * <p>After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+ * will be called back as appropriate.</p>
+ * @see #onFillable()
+ */
+ public void fillInterested()
+ {
+ LOG.debug("fillInterested {}",this);
+
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.fillInterested()))
+ break;
+ }
+ }
+
+ public void fillInterested(Callback callback)
+ {
+ LOG.debug("fillInterested {}",this);
+
+ while(true)
+ {
+ State state=_state.get();
+ // TODO yuck
+ if (state instanceof FillingInterestedCallback && ((FillingInterestedCallback)state)._callback==callback)
+ break;
+ State next=new FillingInterestedCallback(callback,state);
+ if (next(state,next))
+ break;
+ }
+ }
+
+ /**
+ * <p>Callback method invoked when the endpoint is ready to be read.</p>
+ * @see #fillInterested()
+ */
+ public abstract void onFillable();
+
+ /**
+ * <p>Callback method invoked when the endpoint failed to be ready to be read.</p>
+ * @param cause the exception that caused the failure
+ */
+ protected void onFillInterestedFailed(Throwable cause)
+ {
+ LOG.debug("{} onFillInterestedFailed {}", this, cause);
+ if (_endPoint.isOpen())
+ {
+ boolean close = true;
+ if (cause instanceof TimeoutException)
+ close = onReadTimeout();
+ if (close)
+ {
+ if (_endPoint.isOutputShutdown())
+ _endPoint.close();
+ else
+ _endPoint.shutdownOutput();
+ }
+ }
+
+ if (_endPoint.isOpen())
+ fillInterested();
+ }
+
+ /**
+ * <p>Callback method invoked when the endpoint failed to be ready to be read after a timeout</p>
+ * @return true to signal that the endpoint must be closed, false to keep the endpoint open
+ */
+ protected boolean onReadTimeout()
+ {
+ return true;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ LOG.debug("onOpen {}", this);
+
+ for (Listener listener : listeners)
+ listener.onOpened(this);
+ }
+
+ @Override
+ public void onClose()
+ {
+ LOG.debug("onClose {}",this);
+
+ for (Listener listener : listeners)
+ listener.onClosed(this);
+ }
+
+ @Override
+ public EndPoint getEndPoint()
+ {
+ return _endPoint;
+ }
+
+ @Override
+ public void close()
+ {
+ getEndPoint().close();
+ }
+
+ @Override
+ public int getMessagesIn()
+ {
+ return -1;
+ }
+
+ @Override
+ public int getMessagesOut()
+ {
+ return -1;
+ }
+
+ @Override
+ public long getBytesIn()
+ {
+ return -1;
+ }
+
+ @Override
+ public long getBytesOut()
+ {
+ return -1;
+ }
+
+ @Override
+ public long getCreatedTimeStamp()
+ {
+ return _created;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), _state.get());
+ }
+
+ public boolean next(State state, State next)
+ {
+ if (next==null)
+ return true;
+ if(_state.compareAndSet(state,next))
+ {
+ LOG.debug("{}-->{} {}",state,next,this);
+ if (next!=state)
+ next.onEnter(AbstractConnection.this);
+ return true;
+ }
+ return false;
+ }
+
+ private static final class IdleState extends State
+ {
+ private IdleState()
+ {
+ super("IDLE");
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return FILL_INTERESTED;
+ }
+ }
+
+
+ private static final class FillInterestedState extends State
+ {
+ private FillInterestedState()
+ {
+ super("FILL_INTERESTED");
+ }
+
+ @Override
+ public void onEnter(AbstractConnection connection)
+ {
+ connection.getEndPoint().fillInterested(connection._readCallback);
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return this;
+ }
+
+ @Override
+ public State onFillable()
+ {
+ return FILLING;
+ }
+
+ @Override
+ State onFailed()
+ {
+ return IDLE;
+ }
+ }
+
+
+ private static final class RefillingState extends State
+ {
+ private RefillingState()
+ {
+ super("REFILLING");
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return FILLING_FILL_INTERESTED;
+ }
+
+ @Override
+ public State onFilled()
+ {
+ return IDLE;
+ }
+ }
+
+
+ private static final class FillingFillInterestedState extends State
+ {
+ private FillingFillInterestedState(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return this;
+ }
+
+ State onFilled()
+ {
+ return FILL_INTERESTED;
+ }
+ }
+
+
+ private static final class FillingState extends State
+ {
+ private FillingState()
+ {
+ super("FILLING");
+ }
+
+ @Override
+ public void onEnter(AbstractConnection connection)
+ {
+ if (connection._executeOnfillable)
+ connection.getExecutor().execute(connection._runOnFillable);
+ else
+ connection._runOnFillable.run();
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return FILLING_FILL_INTERESTED;
+ }
+
+ @Override
+ public State onFilled()
+ {
+ return IDLE;
+ }
+ }
+
+
+ public static class State
+ {
+ private final String _name;
+ State(String name)
+ {
+ _name=name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return _name;
+ }
+
+ void onEnter(AbstractConnection connection)
+ {
+ }
+
+ State fillInterested()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+
+ State onFillable()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+
+ State onFilled()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+
+ State onFailed()
+ {
+ throw new IllegalStateException(this.toString());
+ }
+ }
+
+
+ public static final State IDLE=new IdleState();
+
+ public static final State FILL_INTERESTED=new FillInterestedState();
+
+ public static final State FILLING=new FillingState();
+
+ public static final State REFILLING=new RefillingState();
+
+ public static final State FILLING_FILL_INTERESTED=new FillingFillInterestedState("FILLING_FILL_INTERESTED");
+
+ public class NestedState extends State
+ {
+ private final State _nested;
+
+ NestedState(State nested)
+ {
+ super("NESTED("+nested+")");
+ _nested=nested;
+ }
+ NestedState(String name,State nested)
+ {
+ super(name+"("+nested+")");
+ _nested=nested;
+ }
+
+ @Override
+ State fillInterested()
+ {
+ return new NestedState(_nested.fillInterested());
+ }
+
+ @Override
+ State onFillable()
+ {
+ return new NestedState(_nested.onFillable());
+ }
+
+ @Override
+ State onFilled()
+ {
+ return new NestedState(_nested.onFilled());
+ }
+ }
+
+
+ public class FillingInterestedCallback extends NestedState
+ {
+ private final Callback _callback;
+
+ FillingInterestedCallback(Callback callback,State nested)
+ {
+ super("FILLING_INTERESTED_CALLBACK",nested==FILLING?REFILLING:nested);
+ _callback=callback;
+ }
+
+ @Override
+ void onEnter(final AbstractConnection connection)
+ {
+ Callback callback=new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ while(true)
+ {
+ State state = connection._state.get();
+ if (!(state instanceof NestedState))
+ break;
+ State nested=((NestedState)state)._nested;
+ if (connection.next(state,nested))
+ break;
+ }
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ while(true)
+ {
+ State state = connection._state.get();
+ if (!(state instanceof NestedState))
+ break;
+ State nested=((NestedState)state)._nested;
+ if (connection.next(state,nested))
+ break;
+ }
+ _callback.failed(x);
+ }
+ };
+
+ connection.getEndPoint().fillInterested(callback);
+ }
+ }
+
+ private final Runnable _runOnFillable = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ onFillable();
+ }
+ finally
+ {
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.onFilled()))
+ break;
+ }
+ }
+ }
+ };
+
+
+ private class ReadCallback implements Callback
+ {
+ @Override
+ public void succeeded()
+ {
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.onFillable()))
+ break;
+ }
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ _executor.execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ while(true)
+ {
+ State state=_state.get();
+ if (next(state,state.onFailed()))
+ break;
+ }
+ onFillInterestedFailed(x);
+ }
+ });
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("AC.ReadCB@%x{%s}", AbstractConnection.this.hashCode(),AbstractConnection.this);
+ }
+ };
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
+{
+ private static final Logger LOG = Log.getLogger(AbstractEndPoint.class);
+ private final long _created=System.currentTimeMillis();
+ private final InetSocketAddress _local;
+ private final InetSocketAddress _remote;
+ private volatile Connection _connection;
+
+ private final FillInterest _fillInterest = new FillInterest()
+ {
+ @Override
+ protected boolean needsFill() throws IOException
+ {
+ return AbstractEndPoint.this.needsFill();
+ }
+ };
+
+ private final WriteFlusher _writeFlusher = new WriteFlusher(this)
+ {
+ @Override
+ protected void onIncompleteFlushed()
+ {
+ AbstractEndPoint.this.onIncompleteFlush();
+ }
+ };
+
+ protected AbstractEndPoint(Scheduler scheduler,InetSocketAddress local,InetSocketAddress remote)
+ {
+ super(scheduler);
+ _local=local;
+ _remote=remote;
+ }
+
+ @Override
+ public long getCreatedTimeStamp()
+ {
+ return _created;
+ }
+
+ @Override
+ public InetSocketAddress getLocalAddress()
+ {
+ return _local;
+ }
+
+ @Override
+ public InetSocketAddress getRemoteAddress()
+ {
+ return _remote;
+ }
+
+ @Override
+ public Connection getConnection()
+ {
+ return _connection;
+ }
+
+ @Override
+ public void setConnection(Connection connection)
+ {
+ _connection = connection;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ LOG.debug("onOpen {}",this);
+ super.onOpen();
+ }
+
+ @Override
+ public void onClose()
+ {
+ super.onClose();
+ LOG.debug("onClose {}",this);
+ _writeFlusher.onClose();
+ _fillInterest.onClose();
+ }
+
+ @Override
+ public void close()
+ {
+ onClose();
+ }
+
+ @Override
+ public void fillInterested(Callback callback) throws IllegalStateException
+ {
+ notIdle();
+ _fillInterest.register(callback);
+ }
+
+ @Override
+ public void write(Callback callback, ByteBuffer... buffers) throws IllegalStateException
+ {
+ _writeFlusher.write(callback, buffers);
+ }
+
+ protected abstract void onIncompleteFlush();
+
+ protected abstract boolean needsFill() throws IOException;
+
+ protected FillInterest getFillInterest()
+ {
+ return _fillInterest;
+ }
+
+ protected WriteFlusher getWriteFlusher()
+ {
+ return _writeFlusher;
+ }
+
+ @Override
+ protected void onIdleExpired(TimeoutException timeout)
+ {
+ boolean output_shutdown=isOutputShutdown();
+ boolean input_shutdown=isInputShutdown();
+ boolean fillFailed = _fillInterest.onFail(timeout);
+ boolean writeFailed = _writeFlusher.onFail(timeout);
+
+ // If the endpoint is half closed and there was no onFail handling, the close here
+ // This handles the situation where the connection has completed its close handling
+ // and the endpoint is half closed, but the other party does not complete the close.
+ // This perhaps should not check for half closed, however the servlet spec case allows
+ // for a dispatched servlet or suspended request to extend beyond the connections idle
+ // time. So if this test would always close an idle endpoint that is not handled, then
+ // we would need a mode to ignore timeouts for some HTTP states
+ if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed))
+ close();
+ else
+ LOG.debug("Ignored idle endpoint {}",this);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s<->%d,%s,%s,%s,%s,%s,%d,%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ getRemoteAddress(),
+ getLocalAddress().getPort(),
+ isOpen()?"Open":"CLOSED",
+ isInputShutdown()?"ISHUT":"in",
+ isOutputShutdown()?"OSHUT":"out",
+ _fillInterest.isInterested()?"R":"-",
+ _writeFlusher.isInProgress()?"W":"-",
+ getIdleTimeout(),
+ getConnection()==null?null:getConnection().getClass().getSimpleName());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class ArrayByteBufferPool implements ByteBufferPool
+{
+ private final int _min;
+ private final Bucket[] _direct;
+ private final Bucket[] _indirect;
+ private final int _inc;
+
+ public ArrayByteBufferPool()
+ {
+ this(0,1024,64*1024);
+ }
+
+ public ArrayByteBufferPool(int minSize, int increment, int maxSize)
+ {
+ if (minSize>=increment)
+ throw new IllegalArgumentException("minSize >= increment");
+ if ((maxSize%increment)!=0 || increment>=maxSize)
+ throw new IllegalArgumentException("increment must be a divisor of maxSize");
+ _min=minSize;
+ _inc=increment;
+
+ _direct=new Bucket[maxSize/increment];
+ _indirect=new Bucket[maxSize/increment];
+
+ int size=0;
+ for (int i=0;i<_direct.length;i++)
+ {
+ size+=_inc;
+ _direct[i]=new Bucket(size);
+ _indirect[i]=new Bucket(size);
+ }
+ }
+
+ @Override
+ public ByteBuffer acquire(int size, boolean direct)
+ {
+ Bucket bucket = bucketFor(size,direct);
+ ByteBuffer buffer = bucket==null?null:bucket._queue.poll();
+
+ if (buffer == null)
+ {
+ int capacity = bucket==null?size:bucket._size;
+ buffer = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
+ }
+
+ return buffer;
+ }
+
+ @Override
+ public void release(ByteBuffer buffer)
+ {
+ if (buffer!=null)
+ {
+ Bucket bucket = bucketFor(buffer.capacity(),buffer.isDirect());
+ if (bucket!=null)
+ {
+ BufferUtil.clear(buffer);
+ bucket._queue.offer(buffer);
+ }
+ }
+ }
+
+ public void clear()
+ {
+ for (int i=0;i<_direct.length;i++)
+ {
+ _direct[i]._queue.clear();
+ _indirect[i]._queue.clear();
+ }
+ }
+
+ private Bucket bucketFor(int size,boolean direct)
+ {
+ if (size<=_min)
+ return null;
+ int b=(size-1)/_inc;
+ if (b>=_direct.length)
+ return null;
+ Bucket bucket = direct?_direct[b]:_indirect[b];
+
+ return bucket;
+ }
+
+ public static class Bucket
+ {
+ public final int _size;
+ public final Queue<ByteBuffer> _queue= new ConcurrentLinkedQueue<>();
+
+ Bucket(int size)
+ {
+ _size=size;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("Bucket@%x{%d,%d}",hashCode(),_size,_queue.size());
+ }
+ }
+
+
+ // Package local for testing
+ Bucket[] bucketsFor(boolean direct)
+ {
+ return direct ? _direct : _indirect;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** ByteArrayEndPoint.
+ *
+ */
+public class ByteArrayEndPoint extends AbstractEndPoint
+{
+ static final Logger LOG = Log.getLogger(ByteArrayEndPoint.class);
+ public final static InetSocketAddress NOIP=new InetSocketAddress(0);
+
+ protected volatile ByteBuffer _in;
+ protected volatile ByteBuffer _out;
+ protected volatile boolean _ishut;
+ protected volatile boolean _oshut;
+ protected volatile boolean _closed;
+ protected volatile boolean _growOutput;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ByteArrayEndPoint()
+ {
+ this(null,0,null,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ByteArrayEndPoint(byte[] input, int outputSize)
+ {
+ this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ByteArrayEndPoint(String input, int outputSize)
+ {
+ this(null,0,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayEndPoint(Scheduler scheduler, long idleTimeoutMs)
+ {
+ this(scheduler,idleTimeoutMs,null,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, byte[] input, int outputSize)
+ {
+ this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, String input, int outputSize)
+ {
+ this(timer,idleTimeoutMs,input!=null?BufferUtil.toBuffer(input):null,BufferUtil.allocate(outputSize));
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayEndPoint(Scheduler timer, long idleTimeoutMs, ByteBuffer input, ByteBuffer output)
+ {
+ super(timer,NOIP,NOIP);
+ _in=input==null?BufferUtil.EMPTY_BUFFER:input;
+ _out=output==null?BufferUtil.allocate(1024):output;
+ setIdleTimeout(idleTimeoutMs);
+ }
+
+
+
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void onIncompleteFlush()
+ {
+ // Don't need to do anything here as takeOutput does the signalling.
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected boolean needsFill() throws IOException
+ {
+ if (_closed)
+ throw new ClosedChannelException();
+ return _in == null || BufferUtil.hasContent(_in);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the in.
+ */
+ public ByteBuffer getIn()
+ {
+ return _in;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public void setInputEOF()
+ {
+ _in = null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param in The in to set.
+ */
+ public void setInput(ByteBuffer in)
+ {
+ _in = in;
+ if (in == null || BufferUtil.hasContent(in))
+ getFillInterest().fillable();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setInput(String s)
+ {
+ setInput(BufferUtil.toBuffer(s,StandardCharsets.UTF_8));
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setInput(String s,Charset charset)
+ {
+ setInput(BufferUtil.toBuffer(s,charset));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public ByteBuffer getOutput()
+ {
+ return _out;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public String getOutputString()
+ {
+ return getOutputString(StandardCharsets.UTF_8);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public String getOutputString(Charset charset)
+ {
+ return BufferUtil.toString(_out,charset);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public ByteBuffer takeOutput()
+ {
+ ByteBuffer b=_out;
+ _out=BufferUtil.allocate(b.capacity());
+ getWriteFlusher().completeWrite();
+ return b;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public String takeOutputString()
+ {
+ return takeOutputString(StandardCharsets.UTF_8);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the out.
+ */
+ public String takeOutputString(Charset charset)
+ {
+ ByteBuffer buffer=takeOutput();
+ return BufferUtil.toString(buffer,charset);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param out The out to set.
+ */
+ public void setOutput(ByteBuffer out)
+ {
+ _out = out;
+ getWriteFlusher().completeWrite();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#isOpen()
+ */
+ @Override
+ public boolean isOpen()
+ {
+ return !_closed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ @Override
+ public boolean isInputShutdown()
+ {
+ return _ishut||_closed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ @Override
+ public boolean isOutputShutdown()
+ {
+ return _oshut||_closed;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void shutdownInput()
+ {
+ _ishut=true;
+ if (_oshut)
+ close();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#shutdownOutput()
+ */
+ @Override
+ public void shutdownOutput()
+ {
+ _oshut=true;
+ if (_ishut)
+ close();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#close()
+ */
+ @Override
+ public void close()
+ {
+ super.close();
+ _closed=true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return <code>true</code> if there are bytes remaining to be read from the encoded input
+ */
+ public boolean hasMore()
+ {
+ return getOutput().position()>0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
+ */
+ @Override
+ public int fill(ByteBuffer buffer) throws IOException
+ {
+ if (_closed)
+ throw new EofException("CLOSED");
+ if (_in==null)
+ shutdownInput();
+ if (_ishut)
+ return -1;
+ int filled=BufferUtil.append(buffer,_in);
+ if (filled>0)
+ notIdle();
+ return filled;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+ */
+ @Override
+ public boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ if (_closed)
+ throw new IOException("CLOSED");
+ if (_oshut)
+ throw new IOException("OSHUT");
+
+ boolean flushed=true;
+ boolean idle=true;
+
+ for (ByteBuffer b : buffers)
+ {
+ if (BufferUtil.hasContent(b))
+ {
+ if (_growOutput && b.remaining()>BufferUtil.space(_out))
+ {
+ BufferUtil.compact(_out);
+ if (b.remaining()>BufferUtil.space(_out))
+ {
+ ByteBuffer n = BufferUtil.allocate(_out.capacity()+b.remaining()*2);
+ BufferUtil.append(n,_out);
+ _out=n;
+ }
+ }
+
+ if (BufferUtil.append(_out,b)>0)
+ idle=false;
+
+ if (BufferUtil.hasContent(b))
+ {
+ flushed=false;
+ break;
+ }
+ }
+ }
+ if (!idle)
+ notIdle();
+ return flushed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public void reset()
+ {
+ getFillInterest().onClose();
+ getWriteFlusher().onClose();
+ _ishut=false;
+ _oshut=false;
+ _closed=false;
+ _in=null;
+ BufferUtil.clear(_out);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.io.EndPoint#getConnection()
+ */
+ @Override
+ public Object getTransport()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the growOutput
+ */
+ public boolean isGrowOutput()
+ {
+ return _growOutput;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param growOutput the growOutput to set
+ */
+ public void setGrowOutput(boolean growOutput)
+ {
+ _growOutput=growOutput;
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * <p>A {@link ByteBuffer} pool.</p>
+ * <p>Acquired buffers may be {@link #release(ByteBuffer) released} but they do not need to;
+ * if they are released, they may be recycled and reused, otherwise they will be garbage
+ * collected as usual.</p>
+ */
+public interface ByteBufferPool
+{
+ /**
+ * <p>Requests a {@link ByteBuffer} of the given size.</p>
+ * <p>The returned buffer may have a bigger capacity than the size being
+ * requested but it will have the limit set to the given size.</p>
+ *
+ * @param size the size of the buffer
+ * @param direct whether the buffer must be direct or not
+ * @return the requested buffer
+ * @see #release(ByteBuffer)
+ */
+ public ByteBuffer acquire(int size, boolean direct);
+
+ /**
+ * <p>Returns a {@link ByteBuffer}, usually obtained with {@link #acquire(int, boolean)}
+ * (but not necessarily), making it available for recycling and reuse.</p>
+ *
+ * @param buffer the buffer to return
+ * @see #acquire(int, boolean)
+ */
+ public void release(ByteBuffer buffer);
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Channel End Point.
+ * <p>Holds the channel and socket for an NIO endpoint.
+ */
+public class ChannelEndPoint extends AbstractEndPoint
+{
+ private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
+
+ private final ByteChannel _channel;
+ private final Socket _socket;
+ private volatile boolean _ishut;
+ private volatile boolean _oshut;
+
+ public ChannelEndPoint(Scheduler scheduler,SocketChannel channel)
+ {
+ super(scheduler,
+ (InetSocketAddress)channel.socket().getLocalSocketAddress(),
+ (InetSocketAddress)channel.socket().getRemoteSocketAddress());
+ _channel = channel;
+ _socket=channel.socket();
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return _channel.isOpen();
+ }
+
+ protected void shutdownInput()
+ {
+ LOG.debug("ishut {}", this);
+ _ishut=true;
+ if (_oshut)
+ close();
+ }
+
+ @Override
+ public void shutdownOutput()
+ {
+ LOG.debug("oshut {}", this);
+ _oshut = true;
+ if (_channel.isOpen())
+ {
+ try
+ {
+ if (!_socket.isOutputShutdown())
+ _socket.shutdownOutput();
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ finally
+ {
+ if (_ishut)
+ {
+ close();
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isOutputShutdown()
+ {
+ return _oshut || !_channel.isOpen() || _socket.isOutputShutdown();
+ }
+
+ @Override
+ public boolean isInputShutdown()
+ {
+ return _ishut || !_channel.isOpen() || _socket.isInputShutdown();
+ }
+
+ @Override
+ public void close()
+ {
+ super.close();
+ LOG.debug("close {}", this);
+ try
+ {
+ _channel.close();
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ finally
+ {
+ _ishut=true;
+ _oshut=true;
+ }
+ }
+
+ @Override
+ public int fill(ByteBuffer buffer) throws IOException
+ {
+ if (_ishut)
+ return -1;
+
+ int pos=BufferUtil.flipToFill(buffer);
+ try
+ {
+ int filled = _channel.read(buffer);
+ if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+ LOG.debug("filled {} {}", filled, this);
+
+ if (filled>0)
+ notIdle();
+ else if (filled==-1)
+ shutdownInput();
+
+ return filled;
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ shutdownInput();
+ return -1;
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(buffer,pos);
+ }
+ }
+
+ @Override
+ public boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ int flushed=0;
+ try
+ {
+ if (buffers.length==1)
+ flushed=_channel.write(buffers[0]);
+ else if (buffers.length>1 && _channel instanceof GatheringByteChannel)
+ flushed= (int)((GatheringByteChannel)_channel).write(buffers,0,buffers.length);
+ else
+ {
+ for (ByteBuffer b : buffers)
+ {
+ if (b.hasRemaining())
+ {
+ int l=_channel.write(b);
+ if (l>0)
+ flushed+=l;
+ if (b.hasRemaining())
+ break;
+ }
+ }
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("flushed {} {}", flushed, this);
+ }
+ catch (IOException e)
+ {
+ throw new EofException(e);
+ }
+
+ if (flushed>0)
+ notIdle();
+
+ for (ByteBuffer b : buffers)
+ if (!BufferUtil.isEmpty(b))
+ return false;
+
+ return true;
+ }
+
+ public ByteChannel getChannel()
+ {
+ return _channel;
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return _channel;
+ }
+
+ public Socket getSocket()
+ {
+ return _socket;
+ }
+
+ @Override
+ protected void onIncompleteFlush()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected boolean needsFill() throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Factory for client-side {@link Connection} instances.
+ */
+public interface ClientConnectionFactory
+{
+ /**
+ *
+ * @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to
+ * @param context the context data to create the connection
+ * @return a new {@link Connection}
+ * @throws IOException if the connection cannot be created
+ */
+ public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException;
+
+ public static class Helper
+ {
+ private static Logger LOG = Log.getLogger(Helper.class);
+
+ private Helper()
+ {
+ }
+
+ /**
+ * Replaces the given {@code oldConnection} with the given {@code newConnection} on the
+ * {@link EndPoint} associated with {@code oldConnection}, performing connection lifecycle management.
+ * <p />
+ * The {@code oldConnection} will be closed by invoking {@link org.eclipse.jetty.io.Connection#onClose()}
+ * and the {@code newConnection} will be opened by invoking {@link org.eclipse.jetty.io.Connection#onOpen()}.
+ * @param oldConnection the old connection to replace
+ * @param newConnection the new connection replacement
+ */
+ public static void replaceConnection(Connection oldConnection, Connection newConnection)
+ {
+ close(oldConnection);
+ oldConnection.getEndPoint().setConnection(newConnection);
+ open(newConnection);
+ }
+
+ private static void open(Connection connection)
+ {
+ try
+ {
+ connection.onOpen();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ }
+ }
+
+ private static void close(Connection connection)
+ {
+ try
+ {
+ connection.onClose();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.Closeable;
+
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * <p>A {@link Connection} is associated to an {@link EndPoint} so that I/O events
+ * happening on the {@link EndPoint} can be processed by the {@link Connection}.</p>
+ * <p>A typical implementation of {@link Connection} overrides {@link #onOpen()} to
+ * {@link EndPoint#fillInterested(Callback) set read interest} on the {@link EndPoint},
+ * and when the {@link EndPoint} signals read readyness, this {@link Connection} can
+ * read bytes from the network and interpret them.</p>
+ */
+public interface Connection extends Closeable
+{
+ public void addListener(Listener listener);
+
+ /**
+ * <p>Callback method invoked when this {@link Connection} is opened.</p>
+ * <p>Creators of the connection implementation are responsible for calling this method.</p>
+ */
+ public void onOpen();
+
+ /**
+ * <p>Callback method invoked when this {@link Connection} is closed.</p>
+ * <p>Creators of the connection implementation are responsible for calling this method.</p>
+ */
+ public void onClose();
+
+ /**
+ * @return the {@link EndPoint} associated with this {@link Connection}
+ */
+ public EndPoint getEndPoint();
+
+ /**
+ * <p>Performs a logical close of this connection.</p>
+ * <p>For simple connections, this may just mean to delegate the close to the associated
+ * {@link EndPoint} but, for example, SSL connections should write the SSL close message
+ * before closing the associated {@link EndPoint}.</p>
+ */
+ @Override
+ public void close();
+
+ public int getMessagesIn();
+ public int getMessagesOut();
+ public long getBytesIn();
+ public long getBytesOut();
+ public long getCreatedTimeStamp();
+
+
+ public interface Listener
+ {
+ public void onOpened(Connection connection);
+
+ public void onClosed(Connection connection);
+
+ public static class Adapter implements Listener
+ {
+ @Override
+ public void onOpened(Connection connection)
+ {
+ }
+
+ @Override
+ public void onClosed(Connection connection)
+ {
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadPendingException;
+import java.nio.channels.WritePendingException;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FutureCallback;
+
+
+/**
+ *
+ * A transport EndPoint
+ *
+ * <h3>Asynchronous Methods</h3>
+ * <p>The asynchronous scheduling methods of {@link EndPoint}
+ * has been influenced by NIO.2 Futures and Completion
+ * handlers, but does not use those actual interfaces because they have
+ * some inefficiencies.</p>
+ * <p>This class will frequently be used in conjunction with some of the utility
+ * implementations of {@link Callback}, such as {@link FutureCallback} and
+ * {@link ExecutorCallback}. Examples are:</p>
+ *
+ * <h3>Blocking Read</h3>
+ * <p>A FutureCallback can be used to block until an endpoint is ready to be filled
+ * from:
+ * <blockquote><pre>
+ * FutureCallback<String> future = new FutureCallback<>();
+ * endpoint.fillInterested("ContextObj",future);
+ * ...
+ * String context = future.get(); // This blocks
+ * int filled=endpoint.fill(mybuffer);
+ * </pre></blockquote></p>
+ *
+ * <h3>Dispatched Read</h3>
+ * <p>By using a different callback, the read can be done asynchronously in its own dispatched thread:
+ * <blockquote><pre>
+ * endpoint.fillInterested("ContextObj",new ExecutorCallback<String>(executor)
+ * {
+ * public void onCompleted(String context)
+ * {
+ * int filled=endpoint.fill(mybuffer);
+ * ...
+ * }
+ * public void onFailed(String context,Throwable cause) {...}
+ * });
+ * </pre></blockquote></p>
+ * <p>The executor callback can also be customized to not dispatch in some circumstances when
+ * it knows it can use the callback thread and does not need to dispatch.</p>
+ *
+ * <h3>Blocking Write</h3>
+ * <p>The write contract is that the callback complete is not called until all data has been
+ * written or there is a failure. For blocking this looks like:
+ * <blockquote><pre>
+ * FutureCallback<String> future = new FutureCallback<>();
+ * endpoint.write("ContextObj",future,headerBuffer,contentBuffer);
+ * String context = future.get(); // This blocks
+ * </pre></blockquote></p>
+ *
+ * <h3>Dispatched Write</h3>
+ * <p>Note also that multiple buffers may be passed in write so that gather writes
+ * can be done:
+ * <blockquote><pre>
+ * endpoint.write("ContextObj",new ExecutorCallback<String>(executor)
+ * {
+ * public void onCompleted(String context)
+ * {
+ * int filled=endpoint.fill(mybuffer);
+ * ...
+ * }
+ * public void onFailed(String context,Throwable cause) {...}
+ * },headerBuffer,contentBuffer);
+ * </pre></blockquote></p>
+ */
+public interface EndPoint extends Closeable
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The local Inet address to which this <code>EndPoint</code> is bound, or <code>null</code>
+ * if this <code>EndPoint</code> does not represent a network connection.
+ */
+ InetSocketAddress getLocalAddress();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The remote Inet address to which this <code>EndPoint</code> is bound, or <code>null</code>
+ * if this <code>EndPoint</code> does not represent a network connection.
+ */
+ InetSocketAddress getRemoteAddress();
+
+ /* ------------------------------------------------------------ */
+ boolean isOpen();
+
+ /* ------------------------------------------------------------ */
+ long getCreatedTimeStamp();
+
+ /* ------------------------------------------------------------ */
+ /** Shutdown the output.
+ * <p>This call indicates that no more data will be sent on this endpoint that
+ * that the remote end should read an EOF once all previously sent data has been
+ * consumed. Shutdown may be done either at the TCP/IP level, as a protocol exchange (Eg
+ * TLS close handshake) or both.
+ * <p>
+ * If the endpoint has {@link #isInputShutdown()} true, then this call has the same effect
+ * as {@link #close()}.
+ */
+ void shutdownOutput();
+
+ /* ------------------------------------------------------------ */
+ /** Test if output is shutdown.
+ * The output is shutdown by a call to {@link #shutdownOutput()}
+ * or {@link #close()}.
+ * @return true if the output is shutdown or the endpoint is closed.
+ */
+ boolean isOutputShutdown();
+
+ /* ------------------------------------------------------------ */
+ /** Test if the input is shutdown.
+ * The input is shutdown if an EOF has been read while doing
+ * a {@link #fill(ByteBuffer)}. Once the input is shutdown, all calls to
+ * {@link #fill(ByteBuffer)} will return -1, until such time as the
+ * end point is close, when they will return {@link EofException}.
+ * @return True if the input is shutdown or the endpoint is closed.
+ */
+ boolean isInputShutdown();
+
+ /**
+ * Close any backing stream associated with the endpoint
+ */
+ @Override
+ void close();
+
+ /**
+ * Fill the passed buffer with data from this endpoint. The bytes are appended to any
+ * data already in the buffer by writing from the buffers limit up to it's capacity.
+ * The limit is updated to include the filled bytes.
+ *
+ * @param buffer The buffer to fill. The position and limit are modified during the fill. After the
+ * operation, the position is unchanged and the limit is increased to reflect the new data filled.
+ * @return an <code>int</code> value indicating the number of bytes
+ * filled or -1 if EOF is read or the input is shutdown.
+ * @throws EofException If the endpoint is closed.
+ */
+ int fill(ByteBuffer buffer) throws IOException;
+
+
+ /**
+ * Flush data from the passed header/buffer to this endpoint. As many bytes as can be consumed
+ * are taken from the header/buffer position up until the buffer limit. The header/buffers position
+ * is updated to indicate how many bytes have been consumed.
+ * @return True IFF all the buffers have been consumed and the endpoint has flushed the data to its
+ * destination (ie is not buffering any data).
+ *
+ * @throws EofException If the endpoint is closed or output is shutdown.
+ */
+ boolean flush(ByteBuffer... buffer) throws IOException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The underlying transport object (socket, channel, etc.)
+ */
+ Object getTransport();
+
+ /* ------------------------------------------------------------ */
+ /** Get the max idle time in ms.
+ * <p>The max idle time is the time the endpoint can be idle before
+ * extraordinary handling takes place.
+ * @return the max idle time in ms or if ms <= 0 implies an infinite timeout
+ */
+ long getIdleTimeout();
+
+ /* ------------------------------------------------------------ */
+ /** Set the idle timeout.
+ * @param idleTimeout the idle timeout in MS. Timeout <= 0 implies an infinite timeout
+ */
+ void setIdleTimeout(long idleTimeout);
+
+
+ /**
+ * <p>Requests callback methods to be invoked when a call to {@link #fill(ByteBuffer)} would return data or EOF.</p>
+ *
+ * @param callback the callback to call when an error occurs or we are readable.
+ * @throws ReadPendingException if another read operation is concurrent.
+ */
+ void fillInterested(Callback callback) throws ReadPendingException;
+
+ /**
+ * <p>Writes the given buffers via {@link #flush(ByteBuffer...)} and invokes callback methods when either
+ * all the data has been flushed or an error occurs.</p>
+ *
+ * @param callback the callback to call when an error occurs or the write completed.
+ * @param buffers one or more {@link ByteBuffer}s that will be flushed.
+ * @throws WritePendingException if another write operation is concurrent.
+ */
+ void write(Callback callback, ByteBuffer... buffers) throws WritePendingException;
+
+ /**
+ * @return the {@link Connection} associated with this {@link EndPoint}
+ * @see #setConnection(Connection)
+ */
+ Connection getConnection();
+
+ /**
+ * @param connection the {@link Connection} associated with this {@link EndPoint}
+ * @see #getConnection()
+ */
+ void setConnection(Connection connection);
+
+ /**
+ * <p>Callback method invoked when this {@link EndPoint} is opened.</p>
+ * @see #onClose()
+ */
+ void onOpen();
+
+ /**
+ * <p>Callback method invoked when this {@link EndPoint} is close.</p>
+ * @see #onOpen()
+ */
+ void onClose();
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.EOFException;
+
+
+/* ------------------------------------------------------------ */
+/** A Jetty specialization of EOFException.
+ * <p> This is thrown by Jetty to distinguish between EOF received from
+ * the connection, vs and EOF thrown by some application talking to some other file/socket etc.
+ * The only difference in handling is that Jetty EOFs are logged less verbosely.
+ */
+public class EofException extends EOFException
+{
+ public EofException()
+ {
+ }
+
+ public EofException(String reason)
+ {
+ super(reason);
+ }
+
+ public EofException(Throwable th)
+ {
+ if (th!=null)
+ initCause(th);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ReadPendingException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * A Utility class to help implement {@link EndPoint#fillInterested(Callback)}
+ * by keeping state and calling the context and callback objects.
+ *
+ */
+public abstract class FillInterest
+{
+ private final static Logger LOG = Log.getLogger(FillInterest.class);
+ private final AtomicReference<Callback> _interested = new AtomicReference<>(null);
+
+ /* ------------------------------------------------------------ */
+ protected FillInterest()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Call to register interest in a callback when a read is possible.
+ * The callback will be called either immediately if {@link #needsFill()}
+ * returns true or eventually once {@link #fillable()} is called.
+ * @param callback
+ * @throws ReadPendingException
+ */
+ public <C> void register(Callback callback) throws ReadPendingException
+ {
+ if (callback==null)
+ throw new IllegalArgumentException();
+
+ if (!_interested.compareAndSet(null,callback))
+ {
+ LOG.warn("Read pending for "+_interested.get()+" pervented "+callback);
+ throw new ReadPendingException();
+ }
+ try
+ {
+ if (needsFill())
+ fillable();
+ }
+ catch(IOException e)
+ {
+ onFail(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Call to signal that a read is now possible.
+ */
+ public void fillable()
+ {
+ Callback callback=_interested.get();
+ if (callback!=null && _interested.compareAndSet(callback,null))
+ callback.succeeded();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if a read callback has been registered
+ */
+ public boolean isInterested()
+ {
+ return _interested.get()!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Call to signal a failure to a registered interest
+ * @return true if the cause was passed to a {@link Callback} instance
+ */
+ public boolean onFail(Throwable cause)
+ {
+ Callback callback=_interested.get();
+ if (callback!=null && _interested.compareAndSet(callback,null))
+ {
+ callback.failed(cause);
+ return true;
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void onClose()
+ {
+ Callback callback=_interested.get();
+ if (callback!=null && _interested.compareAndSet(callback,null))
+ callback.failed(new ClosedChannelException());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("FillInterest@%x{%b,%s}",hashCode(),_interested.get(),_interested.get());
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Register the read interest
+ * Abstract method to be implemented by the Specific ReadInterest to
+ * enquire if a read is immediately possible and if not to schedule a future
+ * call to {@link #fillable()} or {@link #onFail(Throwable)}
+ * @return true if a read is possible
+ * @throws IOException
+ */
+ abstract protected boolean needsFill() throws IOException;
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An Abstract implementation of an Idle Timeout.
+ * <p/>
+ * This implementation is optimised that timeout operations are not cancelled on
+ * every operation. Rather timeout are allowed to expire and a check is then made
+ * to see when the last operation took place. If the idle timeout has not expired,
+ * the timeout is rescheduled for the earliest possible time a timeout could occur.
+ */
+public abstract class IdleTimeout
+{
+ private static final Logger LOG = Log.getLogger(IdleTimeout.class);
+ private final Scheduler _scheduler;
+ private final AtomicReference<Scheduler.Task> _timeout = new AtomicReference<>();
+ private volatile long _idleTimeout;
+ private volatile long _idleTimestamp = System.currentTimeMillis();
+
+ private final Runnable _idleTask = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ long idleLeft = checkIdleTimeout();
+ if (idleLeft >= 0)
+ scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout());
+ }
+ };
+
+ /**
+ * @param scheduler A scheduler used to schedule checks for the idle timeout.
+ */
+ public IdleTimeout(Scheduler scheduler)
+ {
+ _scheduler = scheduler;
+ }
+
+ public long getIdleTimestamp()
+ {
+ return _idleTimestamp;
+ }
+
+ public long getIdleTimeout()
+ {
+ return _idleTimeout;
+ }
+
+ public void setIdleTimeout(long idleTimeout)
+ {
+ long old = _idleTimeout;
+ _idleTimeout = idleTimeout;
+
+ // Do we have an old timeout
+ if (old > 0)
+ {
+ // if the old was less than or equal to the new timeout, then nothing more to do
+ if (old <= idleTimeout)
+ return;
+
+ // old timeout is too long, so cancel it.
+ deactivate();
+ }
+
+ // If we have a new timeout, then check and reschedule
+ if (isOpen())
+ activate();
+ }
+
+ /**
+ * This method should be called when non-idle activity has taken place.
+ */
+ public void notIdle()
+ {
+ _idleTimestamp = System.currentTimeMillis();
+ }
+
+ private void scheduleIdleTimeout(long delay)
+ {
+ Scheduler.Task newTimeout = null;
+ if (isOpen() && delay > 0 && _scheduler != null)
+ newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
+ Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
+ if (oldTimeout != null)
+ oldTimeout.cancel();
+ }
+
+ public void onOpen()
+ {
+ activate();
+ }
+
+ private void activate()
+ {
+ if (_idleTimeout > 0)
+ _idleTask.run();
+ }
+
+ public void onClose()
+ {
+ deactivate();
+ }
+
+ private void deactivate()
+ {
+ Scheduler.Task oldTimeout = _timeout.getAndSet(null);
+ if (oldTimeout != null)
+ oldTimeout.cancel();
+ }
+
+ protected long checkIdleTimeout()
+ {
+ if (isOpen())
+ {
+ long idleTimestamp = getIdleTimestamp();
+ long idleTimeout = getIdleTimeout();
+ long idleElapsed = System.currentTimeMillis() - idleTimestamp;
+ long idleLeft = idleTimeout - idleElapsed;
+
+ LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft);
+
+ if (idleTimestamp != 0 && idleTimeout > 0)
+ {
+ if (idleLeft <= 0)
+ {
+ LOG.debug("{} idle timeout expired", this);
+ try
+ {
+ onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"));
+ }
+ finally
+ {
+ notIdle();
+ }
+ }
+ }
+
+ return idleLeft >= 0 ? idleLeft : 0;
+ }
+ return -1;
+ }
+
+ /**
+ * This abstract method is called when the idle timeout has expired.
+ *
+ * @param timeout a TimeoutException
+ */
+ protected abstract void onIdleExpired(TimeoutException timeout);
+
+ /**
+ * This abstract method should be called to check if idle timeouts
+ * should still be checked.
+ *
+ * @return True if the entity monitored should still be checked for idle timeouts
+ */
+ public abstract boolean isOpen();
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.LeakDetector;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements ByteBufferPool
+{
+ private static final Logger LOG = Log.getLogger(LeakTrackingByteBufferPool.class);
+
+ private final LeakDetector<ByteBuffer> leakDetector = new LeakDetector<ByteBuffer>()
+ {
+ @Override
+ protected void leaked(LeakInfo leakInfo)
+ {
+ LeakTrackingByteBufferPool.this.leaked(leakInfo);
+ }
+ };
+
+ private final ByteBufferPool delegate;
+
+ public LeakTrackingByteBufferPool(ByteBufferPool delegate)
+ {
+ this.delegate = delegate;
+ addBean(leakDetector);
+ addBean(delegate);
+ }
+
+ @Override
+ public ByteBuffer acquire(int size, boolean direct)
+ {
+ ByteBuffer buffer = delegate.acquire(size, direct);
+ if (!leakDetector.acquired(buffer))
+ LOG.warn("ByteBuffer {}@{} not tracked", buffer, System.identityHashCode(buffer));
+ return buffer;
+ }
+
+ @Override
+ public void release(ByteBuffer buffer)
+ {
+ if (buffer == null)
+ return;
+ if (!leakDetector.released(buffer))
+ LOG.warn("ByteBuffer {}@{} released but not acquired", buffer, System.identityHashCode(buffer));
+ delegate.release(buffer);
+ }
+
+ protected void leaked(LeakDetector<ByteBuffer>.LeakInfo leakInfo)
+ {
+ LOG.warn("ByteBuffer " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.nio.ByteBuffer;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.BufferUtil;
+
+public class MappedByteBufferPool implements ByteBufferPool
+{
+ private final ConcurrentMap<Integer, Queue<ByteBuffer>> directBuffers = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Integer, Queue<ByteBuffer>> heapBuffers = new ConcurrentHashMap<>();
+ private final int factor;
+
+ public MappedByteBufferPool()
+ {
+ this(1024);
+ }
+
+ public MappedByteBufferPool(int factor)
+ {
+ this.factor = factor;
+ }
+
+ @Override
+ public ByteBuffer acquire(int size, boolean direct)
+ {
+ int bucket = bucketFor(size);
+ ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(direct);
+
+ ByteBuffer result = null;
+ Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
+ if (byteBuffers != null)
+ result = byteBuffers.poll();
+
+ if (result == null)
+ {
+ int capacity = bucket * factor;
+ result = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
+ }
+
+ BufferUtil.clear(result);
+ return result;
+ }
+
+ @Override
+ public void release(ByteBuffer buffer)
+ {
+ if (buffer == null)
+ return; // nothing to do
+
+ // validate that this buffer is from this pool
+ assert((buffer.capacity() % factor) == 0);
+
+ int bucket = bucketFor(buffer.capacity());
+ ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(buffer.isDirect());
+
+ // Avoid to create a new queue every time, just to be discarded immediately
+ Queue<ByteBuffer> byteBuffers = buffers.get(bucket);
+ if (byteBuffers == null)
+ {
+ byteBuffers = new ConcurrentLinkedQueue<>();
+ Queue<ByteBuffer> existing = buffers.putIfAbsent(bucket, byteBuffers);
+ if (existing != null)
+ byteBuffers = existing;
+ }
+
+ BufferUtil.clear(buffer);
+ byteBuffers.offer(buffer);
+ }
+
+ public void clear()
+ {
+ directBuffers.clear();
+ heapBuffers.clear();
+ }
+
+ private int bucketFor(int size)
+ {
+ int bucket = size / factor;
+ if (size % factor > 0)
+ ++bucket;
+ return bucket;
+ }
+
+ // Package local for testing
+ ConcurrentMap<Integer, Queue<ByteBuffer>> buffersFor(boolean direct)
+ {
+ return direct ? directBuffers : heapBuffers;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingClientConnection extends AbstractConnection
+{
+ private static final Logger LOG = Log.getLogger(NegotiatingClientConnection.class);
+
+ private final SSLEngine engine;
+ private final ClientConnectionFactory connectionFactory;
+ private final Map<String, Object> context;
+ private volatile boolean completed;
+
+ protected NegotiatingClientConnection(EndPoint endp, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map<String, Object> context)
+ {
+ super(endp, executor);
+ this.engine = sslEngine;
+ this.connectionFactory = connectionFactory;
+ this.context = context;
+ }
+
+ protected SSLEngine getSSLEngine()
+ {
+ return engine;
+ }
+
+ protected void completed()
+ {
+ completed = true;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ try
+ {
+ getEndPoint().flush(BufferUtil.EMPTY_BUFFER);
+ if (completed)
+ replaceConnection();
+ else
+ fillInterested();
+ }
+ catch (IOException x)
+ {
+ close();
+ throw new RuntimeIOException(x);
+ }
+ }
+
+ @Override
+ public void onFillable()
+ {
+ while (true)
+ {
+ int filled = fill();
+ if (filled == 0 && !completed)
+ fillInterested();
+ if (filled <= 0 || completed)
+ break;
+ }
+ if (completed)
+ replaceConnection();
+ }
+
+ private int fill()
+ {
+ try
+ {
+ return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+ }
+ catch (IOException x)
+ {
+ LOG.debug(x);
+ close();
+ return -1;
+ }
+ }
+
+ private void replaceConnection()
+ {
+ EndPoint endPoint = getEndPoint();
+ try
+ {
+ Connection oldConnection = endPoint.getConnection();
+ Connection newConnection = connectionFactory.newConnection(endPoint, context);
+ ClientConnectionFactory.Helper.replaceConnection(oldConnection, newConnection);
+ }
+ catch (Throwable x)
+ {
+ LOG.debug(x);
+ close();
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ // Gentler close for SSL.
+ getEndPoint().shutdownOutput();
+ super.close();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+
+public abstract class NegotiatingClientConnectionFactory implements ClientConnectionFactory
+{
+ private final ClientConnectionFactory connectionFactory;
+
+ protected NegotiatingClientConnectionFactory(ClientConnectionFactory connectionFactory)
+ {
+ this.connectionFactory = connectionFactory;
+ }
+
+ public ClientConnectionFactory getClientConnectionFactory()
+ {
+ return connectionFactory;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+/**
+ * <p>A listener for raw network traffic within Jetty.</p>
+ * <p>{@link NetworkTrafficListener}s can be installed in a
+ * <code>org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector</code>,
+ * and are notified of the following network traffic events:</p>
+ * <ul>
+ * <li>Connection opened, when the server has accepted the connection from a remote client</li>
+ * <li>Incoming bytes, when the server receives bytes sent from a remote client</li>
+ * <li>Outgoing bytes, when the server sends bytes to a remote client</li>
+ * <li>Connection closed, when the server has closed the connection to a remote client</li>
+ * </ul>
+ * <p>{@link NetworkTrafficListener}s can be used to log the network traffic viewed by
+ * a Jetty server (for example logging to filesystem) for activities such as debugging
+ * or request/response cycles or for replaying request/response cycles to other servers.</p>
+ */
+public interface NetworkTrafficListener
+{
+ /**
+ * <p>Callback method invoked when a connection from a remote client has been accepted.</p>
+ * <p>The {@code socket} parameter can be used to extract socket address information of
+ * the remote client.</p>
+ *
+ * @param socket the socket associated with the remote client
+ */
+ public void opened(Socket socket);
+
+ /**
+ * <p>Callback method invoked when bytes sent by a remote client arrived on the server.</p>
+ *
+ * @param socket the socket associated with the remote client
+ * @param bytes the read-only buffer containing the incoming bytes
+ */
+ public void incoming(Socket socket, ByteBuffer bytes);
+
+ /**
+ * <p>Callback method invoked when bytes are sent to a remote client from the server.</p>
+ * <p>This method is invoked after the bytes have been actually written to the remote client.</p>
+ *
+ * @param socket the socket associated with the remote client
+ * @param bytes the read-only buffer containing the outgoing bytes
+ */
+ public void outgoing(Socket socket, ByteBuffer bytes);
+
+ /**
+ * <p>Callback method invoked when a connection to a remote client has been closed.</p>
+ * <p>The {@code socket} parameter is already closed when this method is called, so it
+ * cannot be queried for socket address information of the remote client.<br />
+ * However, the {@code socket} parameter is the same object passed to {@link #opened(Socket)},
+ * so it is possible to map socket information in {@link #opened(Socket)} and retrieve it
+ * in this method.
+ *
+ * @param socket the (closed) socket associated with the remote client
+ */
+ public void closed(Socket socket);
+
+ /**
+ * <p>A commodity class that implements {@link NetworkTrafficListener} with empty methods.</p>
+ */
+ public static class Adapter implements NetworkTrafficListener
+ {
+ public void opened(Socket socket)
+ {
+ }
+
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ }
+
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ }
+
+ public void closed(Socket socket)
+ {
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
+{
+ private static final Logger LOG = Log.getLogger(NetworkTrafficSelectChannelEndPoint.class);
+
+ private final List<NetworkTrafficListener> listeners;
+
+ public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List<NetworkTrafficListener> listeners) throws IOException
+ {
+ super(channel, selectSet, key, scheduler, idleTimeout);
+ this.listeners = listeners;
+ }
+
+ @Override
+ public int fill(ByteBuffer buffer) throws IOException
+ {
+ int read = super.fill(buffer);
+ notifyIncoming(buffer, read);
+ return read;
+ }
+
+ @Override
+ public boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ boolean flushed=true;
+ for (ByteBuffer b : buffers)
+ {
+ if (b.hasRemaining())
+ {
+ int position = b.position();
+ ByteBuffer view=b.slice();
+ flushed&=super.flush(b);
+ int l=b.position()-position;
+ view.limit(view.position()+l);
+ notifyOutgoing(view);
+ if (!flushed)
+ break;
+ }
+ }
+ return flushed;
+ }
+
+
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ if (listeners != null && !listeners.isEmpty())
+ {
+ for (NetworkTrafficListener listener : listeners)
+ {
+ try
+ {
+ listener.opened(getSocket());
+ }
+ catch (Exception x)
+ {
+ LOG.warn(x);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onClose()
+ {
+ super.onClose();
+ if (listeners != null && !listeners.isEmpty())
+ {
+ for (NetworkTrafficListener listener : listeners)
+ {
+ try
+ {
+ listener.closed(getSocket());
+ }
+ catch (Exception x)
+ {
+ LOG.warn(x);
+ }
+ }
+ }
+ }
+
+
+ public void notifyIncoming(ByteBuffer buffer, int read)
+ {
+ if (listeners != null && !listeners.isEmpty() && read > 0)
+ {
+ for (NetworkTrafficListener listener : listeners)
+ {
+ try
+ {
+ ByteBuffer view = buffer.asReadOnlyBuffer();
+ listener.incoming(getSocket(), view);
+ }
+ catch (Exception x)
+ {
+ LOG.warn(x);
+ }
+ }
+ }
+ }
+
+ public void notifyOutgoing(ByteBuffer view)
+ {
+ if (listeners != null && !listeners.isEmpty() && view.hasRemaining())
+ {
+ Socket socket=getSocket();
+ for (NetworkTrafficListener listener : listeners)
+ {
+ try
+ {
+ listener.outgoing(socket, view);
+ }
+ catch (Exception x)
+ {
+ LOG.warn(x);
+ }
+ }
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+/* ------------------------------------------------------------ */
+/**
+ * Subclass of {@link java.lang.RuntimeException} used to signal that there
+ * was an {@link java.io.IOException} thrown by underlying {@link java.io.Writer}
+ */
+public class RuntimeIOException extends RuntimeException
+{
+ public RuntimeIOException()
+ {
+ super();
+ }
+
+ public RuntimeIOException(String message)
+ {
+ super(message);
+ }
+
+ public RuntimeIOException(Throwable cause)
+ {
+ super(cause);
+ }
+
+ public RuntimeIOException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An ChannelEndpoint that can be scheduled by {@link SelectorManager}.
+ */
+public class SelectChannelEndPoint extends ChannelEndPoint implements SelectorManager.SelectableEndPoint
+{
+ public static final Logger LOG = Log.getLogger(SelectChannelEndPoint.class);
+
+ private final Runnable _updateTask = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ if (getChannel().isOpen())
+ {
+ int oldInterestOps = _key.interestOps();
+ int newInterestOps = _interestOps.get();
+ if (newInterestOps != oldInterestOps)
+ setKeyInterests(oldInterestOps, newInterestOps);
+ }
+ }
+ catch (CancelledKeyException x)
+ {
+ LOG.debug("Ignoring key update for concurrently closed channel {}", this);
+ close();
+ }
+ catch (Exception x)
+ {
+ LOG.warn("Ignoring key update for " + this, x);
+ close();
+ }
+ }
+ };
+
+ /**
+ * true if {@link ManagedSelector#destroyEndPoint(EndPoint)} has not been called
+ */
+ private final AtomicBoolean _open = new AtomicBoolean();
+ private final SelectorManager.ManagedSelector _selector;
+ private final SelectionKey _key;
+ /**
+ * The desired value for {@link SelectionKey#interestOps()}
+ */
+ private final AtomicInteger _interestOps = new AtomicInteger();
+
+ public SelectChannelEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler, long idleTimeout)
+ {
+ super(scheduler,channel);
+ _selector = selector;
+ _key = key;
+ setIdleTimeout(idleTimeout);
+ }
+
+ @Override
+ protected boolean needsFill()
+ {
+ updateLocalInterests(SelectionKey.OP_READ, true);
+ return false;
+ }
+
+ @Override
+ protected void onIncompleteFlush()
+ {
+ updateLocalInterests(SelectionKey.OP_WRITE, true);
+ }
+
+ @Override
+ public void onSelected()
+ {
+ assert _selector.isSelectorThread();
+ int oldInterestOps = _key.interestOps();
+ int readyOps = _key.readyOps();
+ int newInterestOps = oldInterestOps & ~readyOps;
+ setKeyInterests(oldInterestOps, newInterestOps);
+ updateLocalInterests(readyOps, false);
+ if (_key.isReadable())
+ getFillInterest().fillable();
+ if (_key.isWritable())
+ getWriteFlusher().completeWrite();
+ }
+
+
+ private void updateLocalInterests(int operation, boolean add)
+ {
+ while (true)
+ {
+ int oldInterestOps = _interestOps.get();
+ int newInterestOps;
+ if (add)
+ newInterestOps = oldInterestOps | operation;
+ else
+ newInterestOps = oldInterestOps & ~operation;
+
+ if (isInputShutdown())
+ newInterestOps &= ~SelectionKey.OP_READ;
+ if (isOutputShutdown())
+ newInterestOps &= ~SelectionKey.OP_WRITE;
+
+ if (newInterestOps != oldInterestOps)
+ {
+ if (_interestOps.compareAndSet(oldInterestOps, newInterestOps))
+ {
+ LOG.debug("Local interests updated {} -> {} for {}", oldInterestOps, newInterestOps, this);
+ _selector.updateKey(_updateTask);
+ }
+ else
+ {
+ LOG.debug("Local interests update conflict: now {}, was {}, attempted {} for {}", _interestOps.get(), oldInterestOps, newInterestOps, this);
+ continue;
+ }
+ }
+ else
+ {
+ LOG.debug("Ignoring local interests update {} -> {} for {}", oldInterestOps, newInterestOps, this);
+ }
+ break;
+ }
+ }
+
+
+ private void setKeyInterests(int oldInterestOps, int newInterestOps)
+ {
+ LOG.debug("Key interests updated {} -> {}", oldInterestOps, newInterestOps);
+ _key.interestOps(newInterestOps);
+ }
+
+ @Override
+ public void close()
+ {
+ if (_open.compareAndSet(true, false))
+ {
+ super.close();
+ _selector.destroyEndPoint(this);
+ }
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ // We cannot rely on super.isOpen(), because there is a race between calls to close() and isOpen():
+ // a thread may call close(), which flips the boolean but has not yet called super.close(), and
+ // another thread calls isOpen() which would return true - wrong - if based on super.isOpen().
+ return _open.get();
+ }
+
+ @Override
+ public void onOpen()
+ {
+ if (_open.compareAndSet(false, true))
+ super.onOpen();
+ }
+
+ @Override
+ public String toString()
+ {
+ // Do NOT use synchronized (this)
+ // because it's very easy to deadlock when debugging is enabled.
+ // We do a best effort to print the right toString() and that's it.
+ try
+ {
+ boolean valid = _key!=null && _key.isValid();
+ int keyInterests = valid ? _key.interestOps() : -1;
+ int keyReadiness = valid ? _key.readyOps() : -1;
+ return String.format("%s{io=%d,kio=%d,kro=%d}",
+ super.toString(),
+ _interestOps.get(),
+ keyInterests,
+ keyReadiness);
+ }
+ catch (CancelledKeyException x)
+ {
+ return String.format("%s{io=%s,kio=-2,kro=-2}", super.toString(), _interestOps.get());
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.ConcurrentArrayQueue;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>{@link SelectorManager} manages a number of {@link ManagedSelector}s that
+ * simplify the non-blocking primitives provided by the JVM via the {@code java.nio} package.</p>
+ * <p>{@link SelectorManager} subclasses implement methods to return protocol-specific
+ * {@link EndPoint}s and {@link Connection}s.</p>
+ */
+public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
+{
+ public static final String SUBMIT_KEY_UPDATES = "org.eclipse.jetty.io.SelectorManager.submitKeyUpdates";
+ public static final int DEFAULT_CONNECT_TIMEOUT = 15000;
+ protected static final Logger LOG = Log.getLogger(SelectorManager.class);
+ private final static boolean __submitKeyUpdates = Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES, "false"));
+
+ private final Executor executor;
+ private final Scheduler scheduler;
+ private final ManagedSelector[] _selectors;
+ private long _connectTimeout = DEFAULT_CONNECT_TIMEOUT;
+ private long _selectorIndex;
+
+ protected SelectorManager(Executor executor, Scheduler scheduler)
+ {
+ this(executor, scheduler, (Runtime.getRuntime().availableProcessors() + 1) / 2);
+ }
+
+ protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
+ {
+ if (selectors<=0)
+ throw new IllegalArgumentException("No selectors");
+ this.executor = executor;
+ this.scheduler = scheduler;
+ _selectors = new ManagedSelector[selectors];
+ }
+
+ public Executor getExecutor()
+ {
+ return executor;
+ }
+
+ public Scheduler getScheduler()
+ {
+ return scheduler;
+ }
+
+ /**
+ * Get the connect timeout
+ *
+ * @return the connect timeout (in milliseconds)
+ */
+ public long getConnectTimeout()
+ {
+ return _connectTimeout;
+ }
+
+ /**
+ * Set the connect timeout (in milliseconds)
+ *
+ * @param milliseconds the number of milliseconds for the timeout
+ */
+ public void setConnectTimeout(long milliseconds)
+ {
+ _connectTimeout = milliseconds;
+ }
+
+ /**
+ * Executes the given task in a different thread.
+ *
+ * @param task the task to execute
+ */
+ protected void execute(Runnable task)
+ {
+ executor.execute(task);
+ }
+
+ /**
+ * @return the number of selectors in use
+ */
+ public int getSelectorCount()
+ {
+ return _selectors.length;
+ }
+
+ private ManagedSelector chooseSelector()
+ {
+ // The ++ increment here is not atomic, but it does not matter,
+ // so long as the value changes sometimes, then connections will
+ // be distributed over the available selectors.
+ long s = _selectorIndex++;
+ int index = (int)(s % getSelectorCount());
+ return _selectors[index];
+ }
+
+ /**
+ * <p>Registers a channel to perform a non-blocking connect.</p>
+ * <p>The channel must be set in non-blocking mode, and {@link SocketChannel#connect(SocketAddress)}
+ * must be called prior to calling this method.</p>
+ *
+ * @param channel the channel to register
+ * @param attachment the attachment object
+ */
+ public void connect(SocketChannel channel, Object attachment)
+ {
+ ManagedSelector set = chooseSelector();
+ set.submit(set.new Connect(channel, attachment));
+ }
+
+ /**
+ * <p>Registers a channel to perform non-blocking read/write operations.</p>
+ * <p>This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
+ * or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.</p>
+ *
+ * @param channel the channel to register
+ */
+ public void accept(final SocketChannel channel)
+ {
+ final ManagedSelector selector = chooseSelector();
+ selector.submit(selector.new Accept(channel));
+ }
+
+ /**
+ * <p>Registers a server channel for accept operations.
+ * When a {@link SocketChannel} is accepted from the given {@link ServerSocketChannel}
+ * then the {@link #accepted(SocketChannel)} method is called, which must be
+ * overridden by a derivation of this class to handle the accepted channel
+ *
+ * @param server the server channel to register
+ */
+ public void acceptor(final ServerSocketChannel server)
+ {
+ final ManagedSelector selector = chooseSelector();
+ selector.submit(selector.new Acceptor(server));
+ }
+
+ /**
+ * Callback method when a channel is accepted from the {@link ServerSocketChannel}
+ * passed to {@link #acceptor(ServerSocketChannel)}.
+ * The default impl throws an {@link UnsupportedOperationException}, so it must
+ * be overridden by subclasses if a server channel is provided.
+ *
+ * @param channel the
+ * @throws IOException
+ */
+ protected void accepted(SocketChannel channel) throws IOException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ for (int i = 0; i < _selectors.length; i++)
+ {
+ ManagedSelector selector = newSelector(i);
+ _selectors[i] = selector;
+ selector.start();
+ execute(new NonBlockingThread(selector));
+ }
+ }
+
+ /**
+ * <p>Factory method for {@link ManagedSelector}.</p>
+ *
+ * @param id an identifier for the {@link ManagedSelector to create}
+ * @return a new {@link ManagedSelector}
+ */
+ protected ManagedSelector newSelector(int id)
+ {
+ return new ManagedSelector(id);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ for (ManagedSelector selector : _selectors)
+ selector.stop();
+ super.doStop();
+ }
+
+ /**
+ * <p>Callback method invoked when an endpoint is opened.</p>
+ *
+ * @param endpoint the endpoint being opened
+ */
+ protected void endPointOpened(EndPoint endpoint)
+ {
+ endpoint.onOpen();
+ }
+
+ /**
+ * <p>Callback method invoked when an endpoint is closed.</p>
+ *
+ * @param endpoint the endpoint being closed
+ */
+ protected void endPointClosed(EndPoint endpoint)
+ {
+ endpoint.onClose();
+ }
+
+ /**
+ * <p>Callback method invoked when a connection is opened.</p>
+ *
+ * @param connection the connection just opened
+ */
+ public void connectionOpened(Connection connection)
+ {
+ try
+ {
+ connection.onOpen();
+ }
+ catch (Throwable x)
+ {
+ if (isRunning())
+ LOG.warn("Exception while notifying connection " + connection, x);
+ else
+ LOG.debug("Exception while notifying connection {}",connection, x);
+ }
+ }
+
+ /**
+ * <p>Callback method invoked when a connection is closed.</p>
+ *
+ * @param connection the connection just closed
+ */
+ public void connectionClosed(Connection connection)
+ {
+ try
+ {
+ connection.onClose();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug("Exception while notifying connection " + connection, x);
+ }
+ }
+
+ protected boolean finishConnect(SocketChannel channel) throws IOException
+ {
+ return channel.finishConnect();
+ }
+
+ /**
+ * <p>Callback method invoked when a non-blocking connect cannot be completed.</p>
+ * <p>By default it just logs with level warning.</p>
+ *
+ * @param channel the channel that attempted the connect
+ * @param ex the exception that caused the connect to fail
+ * @param attachment the attachment object associated at registration
+ */
+ protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+ {
+ LOG.warn(String.format("%s - %s", channel, attachment), ex);
+ }
+
+ /**
+ * <p>Factory method to create {@link EndPoint}.</p>
+ * <p>This method is invoked as a result of the registration of a channel via {@link #connect(SocketChannel, Object)}
+ * or {@link #accept(SocketChannel)}.</p>
+ *
+ * @param channel the channel associated to the endpoint
+ * @param selector the selector the channel is registered to
+ * @param selectionKey the selection key
+ * @return a new endpoint
+ * @throws IOException if the endPoint cannot be created
+ * @see #newConnection(SocketChannel, EndPoint, Object)
+ */
+ protected abstract EndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selector, SelectionKey selectionKey) throws IOException;
+
+ /**
+ * <p>Factory method to create {@link Connection}.</p>
+ *
+ * @param channel the channel associated to the connection
+ * @param endpoint the endpoint
+ * @param attachment the attachment
+ * @return a new connection
+ * @throws IOException
+ * @see #newEndPoint(SocketChannel, ManagedSelector, SelectionKey)
+ */
+ public abstract Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException;
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ ContainerLifeCycle.dumpObject(out, this);
+ ContainerLifeCycle.dump(out, indent, TypeUtil.asList(_selectors));
+ }
+
+ private enum State
+ {
+ CHANGES, MORE_CHANGES, SELECT, WAKEUP, PROCESS
+ }
+
+ /**
+ * <p>{@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.</p>
+ * <p>{@link ManagedSelector} runs the select loop, which waits on {@link Selector#select()} until events
+ * happen for registered channels. When events happen, it notifies the {@link EndPoint} associated
+ * with the channel.</p>
+ */
+ public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
+ {
+ private final AtomicReference<State> _state= new AtomicReference<>(State.PROCESS);
+ private final Queue<Runnable> _changes = new ConcurrentArrayQueue<>();
+ private final int _id;
+ private Selector _selector;
+ private volatile Thread _thread;
+
+ public ManagedSelector(int id)
+ {
+ _id = id;
+ setStopTimeout(5000);
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ _selector = Selector.open();
+ _state.set(State.PROCESS);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ LOG.debug("Stopping {}", this);
+ Stop stop = new Stop();
+ submit(stop);
+ stop.await(getStopTimeout());
+ LOG.debug("Stopped {}", this);
+ }
+
+ /**
+ * Submit a task to update a selector key. If the System property {@link SelectorManager#SUBMIT_KEY_UPDATES}
+ * is set true (default is false), the task is passed to {@link #submit(Runnable)}. Otherwise it is run immediately and the selector
+ * woken up if need be.
+ * @param update the update to a key
+ */
+ public void updateKey(Runnable update)
+ {
+ if (__submitKeyUpdates)
+ {
+ submit(update);
+ }
+ else
+ {
+ runChange(update);
+ if (_state.compareAndSet(State.SELECT, State.WAKEUP))
+ wakeup();
+ }
+ }
+
+ /**
+ * <p>Submits a change to be executed in the selector thread.</p>
+ * <p>Changes may be submitted from any thread, and the selector thread woken up
+ * (if necessary) to execute the change.</p>
+ *
+ * @param change the change to submit
+ */
+ public void submit(Runnable change)
+ {
+ // This method may be called from the selector thread, and therefore
+ // we could directly run the change without queueing, but this may
+ // lead to stack overflows on a busy server, so we always offer the
+ // change to the queue and process the state.
+
+ _changes.offer(change);
+ LOG.debug("Queued change {}", change);
+
+ out: while (true)
+ {
+ switch (_state.get())
+ {
+ case SELECT:
+ // Avoid multiple wakeup() calls if we the CAS fails
+ if (!_state.compareAndSet(State.SELECT, State.WAKEUP))
+ continue;
+ wakeup();
+ break out;
+ case CHANGES:
+ // Tell the selector thread that we have more changes.
+ // If we fail to CAS, we possibly need to wakeup(), so loop.
+ if (_state.compareAndSet(State.CHANGES, State.MORE_CHANGES))
+ break out;
+ continue;
+ case WAKEUP:
+ // Do nothing, we have already a wakeup scheduled
+ break out;
+ case MORE_CHANGES:
+ // Do nothing, we already notified the selector thread of more changes
+ break out;
+ case PROCESS:
+ // Do nothing, the changes will be run after the processing
+ break out;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ private void runChanges()
+ {
+ Runnable change;
+ while ((change = _changes.poll()) != null)
+ runChange(change);
+ }
+
+ protected void runChange(Runnable change)
+ {
+ try
+ {
+ LOG.debug("Running change {}", change);
+ change.run();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug("Could not run change " + change, x);
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ _thread = Thread.currentThread();
+ String name = _thread.getName();
+ try
+ {
+ _thread.setName(name + "-selector-" + SelectorManager.this.getClass().getSimpleName()+"@"+Integer.toHexString(SelectorManager.this.hashCode())+"/"+_id);
+ LOG.debug("Starting {} on {}", _thread, this);
+ while (isRunning())
+ select();
+ runChanges();
+ }
+ finally
+ {
+ LOG.debug("Stopped {} on {}", _thread, this);
+ _thread.setName(name);
+ }
+ }
+
+ /**
+ * <p>Process changes and waits on {@link Selector#select()}.</p>
+ *
+ * @see #submit(Runnable)
+ */
+ public void select()
+ {
+ boolean debug = LOG.isDebugEnabled();
+ try
+ {
+ _state.set(State.CHANGES);
+
+ // Run the changes, and only exit if we ran all changes
+ out: while(true)
+ {
+ switch (_state.get())
+ {
+ case CHANGES:
+ runChanges();
+ if (_state.compareAndSet(State.CHANGES, State.SELECT))
+ break out;
+ continue;
+ case MORE_CHANGES:
+ runChanges();
+ _state.set(State.CHANGES);
+ continue;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+ // Must check first for SELECT and *then* for WAKEUP
+ // because we read the state twice in the assert, and
+ // it could change from SELECT to WAKEUP in between.
+ assert _state.get() == State.SELECT || _state.get() == State.WAKEUP;
+
+ if (debug)
+ LOG.debug("Selector loop waiting on select");
+ int selected = _selector.select();
+ if (debug)
+ LOG.debug("Selector loop woken up from select, {}/{} selected", selected, _selector.keys().size());
+
+ _state.set(State.PROCESS);
+
+ Set<SelectionKey> selectedKeys = _selector.selectedKeys();
+ for (SelectionKey key : selectedKeys)
+ {
+ if (key.isValid())
+ {
+ processKey(key);
+ }
+ else
+ {
+ if (debug)
+ LOG.debug("Selector loop ignoring invalid key for channel {}", key.channel());
+ Object attachment = key.attachment();
+ if (attachment instanceof EndPoint)
+ ((EndPoint)attachment).close();
+ }
+ }
+ selectedKeys.clear();
+ }
+ catch (Throwable x)
+ {
+ if (isRunning())
+ LOG.warn(x);
+ else
+ LOG.ignore(x);
+ }
+ }
+
+ private void processKey(SelectionKey key)
+ {
+ Object attachment = key.attachment();
+ try
+ {
+ if (attachment instanceof SelectableEndPoint)
+ {
+ ((SelectableEndPoint)attachment).onSelected();
+ }
+ else if (key.isConnectable())
+ {
+ processConnect(key, (Connect)attachment);
+ }
+ else if (key.isAcceptable())
+ {
+ processAccept(key);
+ }
+ else
+ {
+ throw new IllegalStateException();
+ }
+ }
+ catch (CancelledKeyException x)
+ {
+ LOG.debug("Ignoring cancelled key for channel {}", key.channel());
+ if (attachment instanceof EndPoint)
+ closeNoExceptions((EndPoint)attachment);
+ }
+ catch (Throwable x)
+ {
+ LOG.warn("Could not process key for channel " + key.channel(), x);
+ if (attachment instanceof EndPoint)
+ closeNoExceptions((EndPoint)attachment);
+ }
+ }
+
+ private void processConnect(SelectionKey key, Connect connect)
+ {
+ SocketChannel channel = (SocketChannel)key.channel();
+ try
+ {
+ key.attach(connect.attachment);
+ boolean connected = finishConnect(channel);
+ if (connected)
+ {
+ connect.timeout.cancel();
+ key.interestOps(0);
+ EndPoint endpoint = createEndPoint(channel, key);
+ key.attach(endpoint);
+ }
+ else
+ {
+ throw new ConnectException();
+ }
+ }
+ catch (Throwable x)
+ {
+ connect.failed(x);
+ }
+ }
+
+ private void processAccept(SelectionKey key)
+ {
+ ServerSocketChannel server = (ServerSocketChannel)key.channel();
+ SocketChannel channel = null;
+ try
+ {
+ while ((channel = server.accept()) != null)
+ {
+ accepted(channel);
+ }
+ }
+ catch (Throwable x)
+ {
+ closeNoExceptions(channel);
+ LOG.warn("Accept failed for channel " + channel, x);
+ }
+ }
+
+ private void closeNoExceptions(Closeable closeable)
+ {
+ try
+ {
+ if (closeable != null)
+ closeable.close();
+ }
+ catch (Throwable x)
+ {
+ LOG.ignore(x);
+ }
+ }
+
+ public void wakeup()
+ {
+ _selector.wakeup();
+ }
+
+ public boolean isSelectorThread()
+ {
+ return Thread.currentThread() == _thread;
+ }
+
+ private EndPoint createEndPoint(SocketChannel channel, SelectionKey selectionKey) throws IOException
+ {
+ EndPoint endPoint = newEndPoint(channel, this, selectionKey);
+ endPointOpened(endPoint);
+ Connection connection = newConnection(channel, endPoint, selectionKey.attachment());
+ endPoint.setConnection(connection);
+ connectionOpened(connection);
+ LOG.debug("Created {}", endPoint);
+ return endPoint;
+ }
+
+ public void destroyEndPoint(EndPoint endPoint)
+ {
+ LOG.debug("Destroyed {}", endPoint);
+ Connection connection = endPoint.getConnection();
+ if (connection != null)
+ connectionClosed(connection);
+ endPointClosed(endPoint);
+ }
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_id)).append("\n");
+
+ Thread selecting = _thread;
+
+ Object where = "not selecting";
+ StackTraceElement[] trace = selecting == null ? null : selecting.getStackTrace();
+ if (trace != null)
+ {
+ for (StackTraceElement t : trace)
+ if (t.getClassName().startsWith("org.eclipse.jetty."))
+ {
+ where = t;
+ break;
+ }
+ }
+
+ Selector selector = _selector;
+ if (selector != null && selector.isOpen())
+ {
+ final ArrayList<Object> dump = new ArrayList<>(selector.keys().size() * 2);
+ dump.add(where);
+
+ DumpKeys dumpKeys = new DumpKeys(dump);
+ submit(dumpKeys);
+ dumpKeys.await(5, TimeUnit.SECONDS);
+
+ ContainerLifeCycle.dump(out, indent, dump);
+ }
+ }
+
+ public void dumpKeysState(List<Object> dumps)
+ {
+ Selector selector = _selector;
+ Set<SelectionKey> keys = selector.keys();
+ dumps.add(selector + " keys=" + keys.size());
+ for (SelectionKey key : keys)
+ {
+ if (key.isValid())
+ dumps.add(key.attachment() + " iOps=" + key.interestOps() + " rOps=" + key.readyOps());
+ else
+ dumps.add(key.attachment() + " iOps=-1 rOps=-1");
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ Selector selector = _selector;
+ return String.format("%s keys=%d selected=%d",
+ super.toString(),
+ selector != null && selector.isOpen() ? selector.keys().size() : -1,
+ selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
+ }
+
+ private class DumpKeys implements Runnable
+ {
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private final List<Object> _dumps;
+
+ private DumpKeys(List<Object> dumps)
+ {
+ this._dumps = dumps;
+ }
+
+ @Override
+ public void run()
+ {
+ dumpKeysState(_dumps);
+ latch.countDown();
+ }
+
+ public boolean await(long timeout, TimeUnit unit)
+ {
+ try
+ {
+ return latch.await(timeout, unit);
+ }
+ catch (InterruptedException x)
+ {
+ return false;
+ }
+ }
+ }
+
+ private class Acceptor implements Runnable
+ {
+ private final ServerSocketChannel _channel;
+
+ public Acceptor(ServerSocketChannel channel)
+ {
+ this._channel = channel;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ SelectionKey key = _channel.register(_selector, SelectionKey.OP_ACCEPT, null);
+ LOG.debug("{} acceptor={}", this, key);
+ }
+ catch (Throwable x)
+ {
+ closeNoExceptions(_channel);
+ LOG.warn(x);
+ }
+ }
+ }
+
+ private class Accept implements Runnable
+ {
+ private final SocketChannel _channel;
+
+ public Accept(SocketChannel channel)
+ {
+ this._channel = channel;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ SelectionKey key = _channel.register(_selector, 0, null);
+ EndPoint endpoint = createEndPoint(_channel, key);
+ key.attach(endpoint);
+ }
+ catch (Throwable x)
+ {
+ closeNoExceptions(_channel);
+ LOG.debug(x);
+ }
+ }
+ }
+
+ private class Connect implements Runnable
+ {
+ private final AtomicBoolean failed = new AtomicBoolean();
+ private final SocketChannel channel;
+ private final Object attachment;
+ private final Scheduler.Task timeout;
+
+ public Connect(SocketChannel channel, Object attachment)
+ {
+ this.channel = channel;
+ this.attachment = attachment;
+ this.timeout = scheduler.schedule(new ConnectTimeout(this), getConnectTimeout(), TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ channel.register(_selector, SelectionKey.OP_CONNECT, this);
+ }
+ catch (Throwable x)
+ {
+ failed(x);
+ }
+ }
+
+ protected void failed(Throwable failure)
+ {
+ if (failed.compareAndSet(false, true))
+ {
+ timeout.cancel();
+ closeNoExceptions(channel);
+ connectionFailed(channel, failure, attachment);
+ }
+ }
+ }
+
+ private class ConnectTimeout implements Runnable
+ {
+ private final Connect connect;
+
+ private ConnectTimeout(Connect connect)
+ {
+ this.connect = connect;
+ }
+
+ @Override
+ public void run()
+ {
+ SocketChannel channel = connect.channel;
+ if (channel.isConnectionPending())
+ {
+ LOG.debug("Channel {} timed out while connecting, closing it", channel);
+ connect.failed(new SocketTimeoutException());
+ }
+ }
+ }
+
+ private class Stop implements Runnable
+ {
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ for (SelectionKey key : _selector.keys())
+ {
+ Object attachment = key.attachment();
+ if (attachment instanceof EndPoint)
+ {
+ EndPointCloser closer = new EndPointCloser((EndPoint)attachment);
+ execute(closer);
+ // We are closing the SelectorManager, so we want to block the
+ // selector thread here until we have closed all EndPoints.
+ // This is different than calling close() directly, because close()
+ // can wait forever, while here we are limited by the stop timeout.
+ closer.await(getStopTimeout());
+ }
+ }
+
+ closeNoExceptions(_selector);
+ }
+ finally
+ {
+ latch.countDown();
+ }
+ }
+
+ public boolean await(long timeout)
+ {
+ try
+ {
+ return latch.await(timeout, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException x)
+ {
+ return false;
+ }
+ }
+ }
+
+ private class EndPointCloser implements Runnable
+ {
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private final EndPoint endPoint;
+
+ private EndPointCloser(EndPoint endPoint)
+ {
+ this.endPoint = endPoint;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ closeNoExceptions(endPoint.getConnection());
+ }
+ finally
+ {
+ latch.countDown();
+ }
+ }
+
+ private boolean await(long timeout)
+ {
+ try
+ {
+ return latch.await(timeout, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException x)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ /**
+ * A {@link SelectableEndPoint} is an {@link EndPoint} that wish to be notified of
+ * non-blocking events by the {@link ManagedSelector}.
+ */
+ public interface SelectableEndPoint extends EndPoint
+ {
+ /**
+ * <p>Callback method invoked when a read or write events has been detected by the {@link ManagedSelector}
+ * for this endpoint.</p>
+ */
+ void onSelected();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * A wrapper for the {@link java.io.PrintWriter} that re-throws the instances of
+ * {@link java.io.IOException} thrown by the underlying implementation of
+ * {@link java.io.Writer} as {@link RuntimeIOException} instances.
+ */
+public class UncheckedPrintWriter extends PrintWriter
+{
+ private static final Logger LOG = Log.getLogger(UncheckedPrintWriter.class);
+
+ private boolean _autoFlush = false;
+ private IOException _ioException;
+ private boolean _isClosed = false;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Line separator string. This is the value of the line.separator property
+ * at the moment that the stream was created.
+ */
+ private String _lineSeparator;
+
+ public UncheckedPrintWriter(Writer out)
+ {
+ this(out,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new PrintWriter.
+ *
+ * @param out
+ * A character-output stream
+ * @param autoFlush
+ * A boolean; if true, the println() methods will flush the
+ * output buffer
+ */
+ public UncheckedPrintWriter(Writer out, boolean autoFlush)
+ {
+ super(out,autoFlush);
+ this._autoFlush = autoFlush;
+ this._lineSeparator = System.getProperty("line.separator");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new PrintWriter, without automatic line flushing, from an
+ * existing OutputStream. This convenience constructor creates the necessary
+ * intermediate OutputStreamWriter, which will convert characters into bytes
+ * using the default character encoding.
+ *
+ * @param out
+ * An output stream
+ *
+ * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+ */
+ public UncheckedPrintWriter(OutputStream out)
+ {
+ this(out,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new PrintWriter from an existing OutputStream. This convenience
+ * constructor creates the necessary intermediate OutputStreamWriter, which
+ * will convert characters into bytes using the default character encoding.
+ *
+ * @param out
+ * An output stream
+ * @param autoFlush
+ * A boolean; if true, the println() methods will flush the
+ * output buffer
+ *
+ * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+ */
+ public UncheckedPrintWriter(OutputStream out, boolean autoFlush)
+ {
+ this(new BufferedWriter(new OutputStreamWriter(out)),autoFlush);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean checkError()
+ {
+ return _ioException!=null || super.checkError();
+ }
+
+ /* ------------------------------------------------------------ */
+ private void setError(Throwable th)
+ {
+
+ super.setError();
+
+ if (th instanceof IOException)
+ _ioException=(IOException)th;
+ else
+ {
+ _ioException=new IOException(String.valueOf(th));
+ _ioException.initCause(th);
+ }
+
+ LOG.debug(th);
+ }
+
+
+ @Override
+ protected void setError()
+ {
+ setError(new IOException());
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Check to make sure that the stream has not been closed */
+ private void isOpen() throws IOException
+ {
+ if (_ioException!=null)
+ throw new RuntimeIOException(_ioException);
+
+ if (_isClosed)
+ throw new IOException("Stream closed");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Flush the stream.
+ */
+ @Override
+ public void flush()
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.flush();
+ }
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Close the stream.
+ */
+ @Override
+ public void close()
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ out.close();
+ _isClosed = true;
+ }
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write a single character.
+ *
+ * @param c
+ * int specifying a character to be written.
+ */
+ @Override
+ public void write(int c)
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.write(c);
+ }
+ }
+ catch (InterruptedIOException x)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write a portion of an array of characters.
+ *
+ * @param buf
+ * Array of characters
+ * @param off
+ * Offset from which to start writing characters
+ * @param len
+ * Number of characters to write
+ */
+ @Override
+ public void write(char buf[], int off, int len)
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.write(buf,off,len);
+ }
+ }
+ catch (InterruptedIOException x)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write an array of characters. This method cannot be inherited from the
+ * Writer class because it must suppress I/O exceptions.
+ *
+ * @param buf
+ * Array of characters to be written
+ */
+ @Override
+ public void write(char buf[])
+ {
+ this.write(buf,0,buf.length);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write a portion of a string.
+ *
+ * @param s
+ * A String
+ * @param off
+ * Offset from which to start writing characters
+ * @param len
+ * Number of characters to write
+ */
+ @Override
+ public void write(String s, int off, int len)
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.write(s,off,len);
+ }
+ }
+ catch (InterruptedIOException x)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Write a string. This method cannot be inherited from the Writer class
+ * because it must suppress I/O exceptions.
+ *
+ * @param s
+ * String to be written
+ */
+ @Override
+ public void write(String s)
+ {
+ this.write(s,0,s.length());
+ }
+
+ private void newLine()
+ {
+ try
+ {
+ synchronized (lock)
+ {
+ isOpen();
+ out.write(_lineSeparator);
+ if (_autoFlush)
+ out.flush();
+ }
+ }
+ catch (InterruptedIOException x)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (IOException ex)
+ {
+ setError(ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a boolean value. The string produced by <code>{@link
+ * java.lang.String#valueOf(boolean)}</code> is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the <code>{@link
+ * #write(int)}</code> method.
+ *
+ * @param b
+ * The <code>boolean</code> to be printed
+ */
+ @Override
+ public void print(boolean b)
+ {
+ this.write(b?"true":"false");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a character. The character is translated into one or more bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the <code>{@link
+ * #write(int)}</code> method.
+ *
+ * @param c
+ * The <code>char</code> to be printed
+ */
+ @Override
+ public void print(char c)
+ {
+ this.write(c);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an integer. The string produced by <code>{@link
+ * java.lang.String#valueOf(int)}</code> is translated into bytes according
+ * to the platform's default character encoding, and these bytes are written
+ * in exactly the manner of the <code>{@link #write(int)}</code> method.
+ *
+ * @param i
+ * The <code>int</code> to be printed
+ * @see java.lang.Integer#toString(int)
+ */
+ @Override
+ public void print(int i)
+ {
+ this.write(String.valueOf(i));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a long integer. The string produced by <code>{@link
+ * java.lang.String#valueOf(long)}</code> is translated into bytes according
+ * to the platform's default character encoding, and these bytes are written
+ * in exactly the manner of the <code>{@link #write(int)}</code> method.
+ *
+ * @param l
+ * The <code>long</code> to be printed
+ * @see java.lang.Long#toString(long)
+ */
+ @Override
+ public void print(long l)
+ {
+ this.write(String.valueOf(l));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a floating-point number. The string produced by <code>{@link
+ * java.lang.String#valueOf(float)}</code> is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the <code>{@link #write(int)}</code>
+ * method.
+ *
+ * @param f
+ * The <code>float</code> to be printed
+ * @see java.lang.Float#toString(float)
+ */
+ @Override
+ public void print(float f)
+ {
+ this.write(String.valueOf(f));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a double-precision floating-point number. The string produced by
+ * <code>{@link java.lang.String#valueOf(double)}</code> is translated into
+ * bytes according to the platform's default character encoding, and these
+ * bytes are written in exactly the manner of the <code>{@link
+ * #write(int)}</code> method.
+ *
+ * @param d
+ * The <code>double</code> to be printed
+ * @see java.lang.Double#toString(double)
+ */
+ @Override
+ public void print(double d)
+ {
+ this.write(String.valueOf(d));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an array of characters. The characters are converted into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the <code>{@link #write(int)}</code>
+ * method.
+ *
+ * @param s
+ * The array of chars to be printed
+ *
+ * @throws NullPointerException
+ * If <code>s</code> is <code>null</code>
+ */
+ @Override
+ public void print(char s[])
+ {
+ this.write(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a string. If the argument is <code>null</code> then the string
+ * <code>"null"</code> is printed. Otherwise, the string's characters are
+ * converted into bytes according to the platform's default character
+ * encoding, and these bytes are written in exactly the manner of the
+ * <code>{@link #write(int)}</code> method.
+ *
+ * @param s
+ * The <code>String</code> to be printed
+ */
+ @Override
+ public void print(String s)
+ {
+ if (s == null)
+ {
+ s = "null";
+ }
+ this.write(s);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an object. The string produced by the <code>{@link
+ * java.lang.String#valueOf(Object)}</code> method is translated into bytes
+ * according to the platform's default character encoding, and these bytes
+ * are written in exactly the manner of the <code>{@link #write(int)}</code>
+ * method.
+ *
+ * @param obj
+ * The <code>Object</code> to be printed
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public void print(Object obj)
+ {
+ this.write(String.valueOf(obj));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Terminate the current line by writing the line separator string. The line
+ * separator string is defined by the system property
+ * <code>line.separator</code>, and is not necessarily a single newline
+ * character (<code>'\n'</code>).
+ */
+ @Override
+ public void println()
+ {
+ this.newLine();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a boolean value and then terminate the line. This method behaves as
+ * though it invokes <code>{@link #print(boolean)}</code> and then
+ * <code>{@link #println()}</code>.
+ *
+ * @param x
+ * the <code>boolean</code> value to be printed
+ */
+ @Override
+ public void println(boolean x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a character and then terminate the line. This method behaves as
+ * though it invokes <code>{@link #print(char)}</code> and then <code>{@link
+ * #println()}</code>.
+ *
+ * @param x
+ * the <code>char</code> value to be printed
+ */
+ @Override
+ public void println(char x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an integer and then terminate the line. This method behaves as
+ * though it invokes <code>{@link #print(int)}</code> and then <code>{@link
+ * #println()}</code>.
+ *
+ * @param x
+ * the <code>int</code> value to be printed
+ */
+ @Override
+ public void println(int x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a long integer and then terminate the line. This method behaves as
+ * though it invokes <code>{@link #print(long)}</code> and then
+ * <code>{@link #println()}</code>.
+ *
+ * @param x
+ * the <code>long</code> value to be printed
+ */
+ @Override
+ public void println(long x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a floating-point number and then terminate the line. This method
+ * behaves as though it invokes <code>{@link #print(float)}</code> and then
+ * <code>{@link #println()}</code>.
+ *
+ * @param x
+ * the <code>float</code> value to be printed
+ */
+ @Override
+ public void println(float x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a double-precision floating-point number and then terminate the
+ * line. This method behaves as though it invokes <code>{@link
+ * #print(double)}</code> and then <code>{@link #println()}</code>.
+ *
+ * @param x
+ * the <code>double</code> value to be printed
+ */
+ /* ------------------------------------------------------------ */
+ @Override
+ public void println(double x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an array of characters and then terminate the line. This method
+ * behaves as though it invokes <code>{@link #print(char[])}</code> and then
+ * <code>{@link #println()}</code>.
+ *
+ * @param x
+ * the array of <code>char</code> values to be printed
+ */
+ @Override
+ public void println(char x[])
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print a String and then terminate the line. This method behaves as though
+ * it invokes <code>{@link #print(String)}</code> and then
+ * <code>{@link #println()}</code>.
+ *
+ * @param x
+ * the <code>String</code> value to be printed
+ */
+ @Override
+ public void println(String x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Print an Object and then terminate the line. This method behaves as
+ * though it invokes <code>{@link #print(Object)}</code> and then
+ * <code>{@link #println()}</code>.
+ *
+ * @param x
+ * the <code>Object</code> value to be printed
+ */
+ @Override
+ public void println(Object x)
+ {
+ synchronized (lock)
+ {
+ this.print(x);
+ this.println();
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.WritePendingException;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * A Utility class to help implement {@link EndPoint#write(Callback, ByteBuffer...)} by calling
+ * {@link EndPoint#flush(ByteBuffer...)} until all content is written.
+ * The abstract method {@link #onIncompleteFlushed()} is called when not all content has been written after a call to
+ * flush and should organise for the {@link #completeWrite()} method to be called when a subsequent call to flush
+ * should be able to make more progress.
+ * <p>
+ */
+abstract public class WriteFlusher
+{
+ private static final Logger LOG = Log.getLogger(WriteFlusher.class);
+ private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+ private static final ByteBuffer[] EMPTY_BUFFERS = new ByteBuffer[0];
+ private static final EnumMap<StateType, Set<StateType>> __stateTransitions = new EnumMap<>(StateType.class);
+ private static final State __IDLE = new IdleState();
+ private static final State __WRITING = new WritingState();
+ private static final State __COMPLETING = new CompletingState();
+ private final EndPoint _endPoint;
+ private final AtomicReference<State> _state = new AtomicReference<>();
+
+ static
+ {
+ // fill the state machine
+ __stateTransitions.put(StateType.IDLE, EnumSet.of(StateType.WRITING));
+ __stateTransitions.put(StateType.WRITING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+ __stateTransitions.put(StateType.PENDING, EnumSet.of(StateType.COMPLETING,StateType.IDLE));
+ __stateTransitions.put(StateType.COMPLETING, EnumSet.of(StateType.IDLE, StateType.PENDING, StateType.FAILED));
+ __stateTransitions.put(StateType.FAILED, EnumSet.of(StateType.IDLE));
+ }
+
+ // A write operation may either complete immediately:
+ // IDLE-->WRITING-->IDLE
+ // Or it may not completely flush and go via the PENDING state
+ // IDLE-->WRITING-->PENDING-->COMPLETING-->IDLE
+ // Or it may take several cycles to complete
+ // IDLE-->WRITING-->PENDING-->COMPLETING-->PENDING-->COMPLETING-->IDLE
+ //
+ // If a failure happens while in IDLE, it is a noop since there is no operation to tell of the failure.
+ // If a failure happens while in WRITING, but the the write has finished successfully or with an IOExceptions,
+ // the callback's complete or respectively failed methods will be called.
+ // If a failure happens in PENDING state, then the fail method calls the pending callback and moves to IDLE state
+ //
+ // IDLE--(fail)-->IDLE
+ // IDLE-->WRITING--(fail)-->FAILED-->IDLE
+ // IDLE-->WRITING-->PENDING--(fail)-->IDLE
+ // IDLE-->WRITING-->PENDING-->COMPLETING--(fail)-->FAILED-->IDLE
+ //
+ // So a call to fail in the PENDING state will be directly handled and the state changed to IDLE
+ // A call to fail in the WRITING or COMPLETING states will just set the state to FAILED and the failure will be
+ // handled with the write or completeWrite methods try to move the state from what they thought it was.
+ //
+
+ protected WriteFlusher(EndPoint endPoint)
+ {
+ _state.set(__IDLE);
+ _endPoint = endPoint;
+ }
+
+ private enum StateType
+ {
+ IDLE,
+ WRITING,
+ PENDING,
+ COMPLETING,
+ FAILED
+ }
+
+ /**
+ * Tries to update the current state to the given new state.
+ * @param previous the expected current state
+ * @param next the desired new state
+ * @return the previous state or null if the state transition failed
+ * @throws WritePendingException if currentState is WRITING and new state is WRITING (api usage error)
+ */
+ private boolean updateState(State previous,State next)
+ {
+ if (!isTransitionAllowed(previous,next))
+ throw new IllegalStateException();
+
+ boolean updated = _state.compareAndSet(previous, next);
+ if (DEBUG)
+ LOG.debug("update {}:{}{}{}", this, previous, updated?"-->":"!->",next);
+ return updated;
+ }
+
+ private void fail(PendingState pending)
+ {
+ State current = _state.get();
+ if (current.getType()==StateType.FAILED)
+ {
+ FailedState failed=(FailedState)current;
+ if (updateState(failed,__IDLE))
+ {
+ pending.fail(failed.getCause());
+ return;
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ private void ignoreFail()
+ {
+ State current = _state.get();
+ while (current.getType()==StateType.FAILED)
+ {
+ if (updateState(current,__IDLE))
+ return;
+ current = _state.get();
+ }
+ }
+
+ private boolean isTransitionAllowed(State currentState, State newState)
+ {
+ Set<StateType> allowedNewStateTypes = __stateTransitions.get(currentState.getType());
+ if (!allowedNewStateTypes.contains(newState.getType()))
+ {
+ LOG.warn("{}: {} -> {} not allowed", this, currentState, newState);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * State represents a State of WriteFlusher.
+ */
+ private static class State
+ {
+ private final StateType _type;
+
+ private State(StateType stateType)
+ {
+ _type = stateType;
+ }
+
+ public StateType getType()
+ {
+ return _type;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s", _type);
+ }
+ }
+
+ /**
+ * In IdleState WriteFlusher is idle and accepts new writes
+ */
+ private static class IdleState extends State
+ {
+ private IdleState()
+ {
+ super(StateType.IDLE);
+ }
+ }
+
+ /**
+ * In WritingState WriteFlusher is currently writing.
+ */
+ private static class WritingState extends State
+ {
+ private WritingState()
+ {
+ super(StateType.WRITING);
+ }
+ }
+
+ /**
+ * In FailedState no more operations are allowed. The current implementation will never recover from this state.
+ */
+ private static class FailedState extends State
+ {
+ private final Throwable _cause;
+ private FailedState(Throwable cause)
+ {
+ super(StateType.FAILED);
+ _cause=cause;
+ }
+
+ public Throwable getCause()
+ {
+ return _cause;
+ }
+ }
+
+ /**
+ * In CompletingState WriteFlusher is flushing buffers that have not been fully written in write(). If write()
+ * didn't flush all buffers in one go, it'll switch the State to PendingState. completeWrite() will then switch to
+ * this state and try to flush the remaining buffers.
+ */
+ private static class CompletingState extends State
+ {
+ private CompletingState()
+ {
+ super(StateType.COMPLETING);
+ }
+ }
+
+ /**
+ * In PendingState not all buffers could be written in one go. Then write() will switch to PendingState() and
+ * preserve the state by creating a new PendingState object with the given parameters.
+ */
+ private class PendingState extends State
+ {
+ private final Callback _callback;
+ private final ByteBuffer[] _buffers;
+
+ private PendingState(ByteBuffer[] buffers, Callback callback)
+ {
+ super(StateType.PENDING);
+ _buffers = compact(buffers);
+ _callback = callback;
+ }
+
+ public ByteBuffer[] getBuffers()
+ {
+ return _buffers;
+ }
+
+ protected boolean fail(Throwable cause)
+ {
+ if (_callback!=null)
+ {
+ _callback.failed(cause);
+ return true;
+ }
+ return false;
+ }
+
+ protected void complete()
+ {
+ if (_callback!=null)
+ _callback.succeeded();
+ }
+
+ /**
+ * Compacting the buffers is needed because the semantic of WriteFlusher is
+ * to write the buffers and if the caller sees that the buffer is consumed,
+ * then it can recycle it.
+ * If we do not compact, then it is possible that we store a consumed buffer,
+ * which is then recycled and refilled; when the WriteFlusher is invoked to
+ * complete the write, it will write the refilled bytes, garbling the content.
+ *
+ * @param buffers the buffers to compact
+ * @return the compacted buffers
+ */
+ private ByteBuffer[] compact(ByteBuffer[] buffers)
+ {
+ int length = buffers.length;
+
+ // Just one element, no need to compact
+ if (length < 2)
+ return buffers;
+
+ // How many still have content ?
+ int consumed = 0;
+ while (consumed < length && BufferUtil.isEmpty(buffers[consumed]))
+ ++consumed;
+
+ // All of them still have content, no need to compact
+ if (consumed == 0)
+ return buffers;
+
+ // None has content, return empty
+ if (consumed == length)
+ return EMPTY_BUFFERS;
+
+ return Arrays.copyOfRange(buffers,consumed,length);
+ }
+ }
+
+ /**
+ * Abstract call to be implemented by specific WriteFlushers. It should schedule a call to {@link #completeWrite()}
+ * or {@link #onFail(Throwable)} when appropriate.
+ */
+ abstract protected void onIncompleteFlushed();
+
+ /**
+ * Tries to switch state to WRITING. If successful it writes the given buffers to the EndPoint. If state transition
+ * fails it'll fail the callback.
+ *
+ * If not all buffers can be written in one go it creates a new <code>PendingState</code> object to preserve the state
+ * and then calls {@link #onIncompleteFlushed()}. The remaining buffers will be written in {@link #completeWrite()}.
+ *
+ * If all buffers have been written it calls callback.complete().
+ *
+ * @param callback the callback to call on either failed or complete
+ * @param buffers the buffers to flush to the endpoint
+ */
+ public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ {
+ if (DEBUG)
+ LOG.debug("write: {} {}", this, BufferUtil.toDetailString(buffers));
+
+ if (!updateState(__IDLE,__WRITING))
+ throw new WritePendingException();
+
+ try
+ {
+ boolean flushed=_endPoint.flush(buffers);
+ if (DEBUG)
+ LOG.debug("flushed {}", flushed);
+
+ // Are we complete?
+ for (ByteBuffer b : buffers)
+ {
+ if (!flushed||BufferUtil.hasContent(b))
+ {
+ PendingState pending=new PendingState(buffers, callback);
+ if (updateState(__WRITING,pending))
+ onIncompleteFlushed();
+ else
+ fail(pending);
+ return;
+ }
+ }
+
+ // If updateState didn't succeed, we don't care as our buffers have been written
+ if (!updateState(__WRITING,__IDLE))
+ ignoreFail();
+ if (callback!=null)
+ callback.succeeded();
+ }
+ catch (IOException e)
+ {
+ if (DEBUG)
+ LOG.debug("write exception", e);
+ if (updateState(__WRITING,__IDLE))
+ {
+ if (callback!=null)
+ callback.failed(e);
+ }
+ else
+ fail(new PendingState(buffers, callback));
+ }
+ }
+
+
+ /**
+ * Complete a write that has not completed and that called {@link #onIncompleteFlushed()} to request a call to this
+ * method when a call to {@link EndPoint#flush(ByteBuffer...)} is likely to be able to progress.
+ *
+ * It tries to switch from PENDING to COMPLETING. If state transition fails, then it does nothing as the callback
+ * should have been already failed. That's because the only way to switch from PENDING outside this method is
+ * {@link #onFail(Throwable)} or {@link #onClose()}
+ */
+ public void completeWrite()
+ {
+ if (DEBUG)
+ LOG.debug("completeWrite: {}", this);
+
+ State previous = _state.get();
+
+ if (previous.getType()!=StateType.PENDING)
+ return; // failure already handled.
+
+ PendingState pending = (PendingState)previous;
+ if (!updateState(pending,__COMPLETING))
+ return; // failure already handled.
+
+ try
+ {
+ ByteBuffer[] buffers = pending.getBuffers();
+
+ boolean flushed=_endPoint.flush(buffers);
+ if (DEBUG)
+ LOG.debug("flushed {}", flushed);
+
+ // Are we complete?
+ for (ByteBuffer b : buffers)
+ {
+ if (!flushed || BufferUtil.hasContent(b))
+ {
+ if (updateState(__COMPLETING,pending))
+ onIncompleteFlushed();
+ else
+ fail(pending);
+ return;
+ }
+ }
+
+ // If updateState didn't succeed, we don't care as our buffers have been written
+ if (!updateState(__COMPLETING,__IDLE))
+ ignoreFail();
+ pending.complete();
+ }
+ catch (IOException e)
+ {
+ if (DEBUG)
+ LOG.debug("completeWrite exception", e);
+ if(updateState(__COMPLETING,__IDLE))
+ pending.fail(e);
+ else
+ fail(pending);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Notify the flusher of a failure
+ * @param cause The cause of the failure
+ * @return true if the flusher passed the failure to a {@link Callback} instance
+ */
+ public boolean onFail(Throwable cause)
+ {
+ // Keep trying to handle the failure until we get to IDLE or FAILED state
+ while(true)
+ {
+ State current=_state.get();
+ switch(current.getType())
+ {
+ case IDLE:
+ case FAILED:
+ if (DEBUG)
+ LOG.debug("ignored: {} {}", this, cause);
+ return false;
+
+ case PENDING:
+ if (DEBUG)
+ LOG.debug("failed: {} {}", this, cause);
+
+ PendingState pending = (PendingState)current;
+ if (updateState(pending,__IDLE))
+ return pending.fail(cause);
+ break;
+
+ default:
+ if (DEBUG)
+ LOG.debug("failed: {} {}", this, cause);
+
+ if (updateState(current,new FailedState(cause)))
+ return false;
+ break;
+ }
+ }
+ }
+
+ public void onClose()
+ {
+ if (_state.get()==__IDLE)
+ return;
+ onFail(new ClosedChannelException());
+ }
+
+ boolean isIdle()
+ {
+ return _state.get().getType() == StateType.IDLE;
+ }
+
+ public boolean isInProgress()
+ {
+ switch(_state.get().getType())
+ {
+ case WRITING:
+ case PENDING:
+ case COMPLETING:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("WriteFlusher@%x{%s}", hashCode(), _state.get());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+
+/* ------------------------------------------------------------ */
+/** Wrap a Writer as an OutputStream.
+ * When all you have is a Writer and only an OutputStream will do.
+ * Try not to use this as it indicates that your design is a dogs
+ * breakfast (JSP made me write it).
+ *
+ */
+public class WriterOutputStream extends OutputStream
+{
+ protected final Writer _writer;
+ protected final Charset _encoding;
+ private final byte[] _buf=new byte[1];
+
+ /* ------------------------------------------------------------ */
+ public WriterOutputStream(Writer writer, String encoding)
+ {
+ _writer=writer;
+ _encoding=encoding==null?null:Charset.forName(encoding);
+ }
+
+ /* ------------------------------------------------------------ */
+ public WriterOutputStream(Writer writer)
+ {
+ _writer=writer;
+ _encoding=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void close()
+ throws IOException
+ {
+ _writer.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void flush()
+ throws IOException
+ {
+ _writer.flush();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(byte[] b)
+ throws IOException
+ {
+ if (_encoding==null)
+ _writer.write(new String(b));
+ else
+ _writer.write(new String(b,_encoding));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(byte[] b, int off, int len)
+ throws IOException
+ {
+ if (_encoding==null)
+ _writer.write(new String(b,off,len));
+ else
+ _writer.write(new String(b,off,len,_encoding));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public synchronized void write(int b)
+ throws IOException
+ {
+ _buf[0]=(byte)b;
+ write(_buf);
+ }
+}
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty IO : Core classes for Jetty IO subsystem
+ */
+package org.eclipse.jetty.io;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io.ssl;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslClientConnectionFactory implements ClientConnectionFactory
+{
+ public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
+ public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
+ public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
+
+ private final SslContextFactory sslContextFactory;
+ private final ByteBufferPool byteBufferPool;
+ private final Executor executor;
+ private final ClientConnectionFactory connectionFactory;
+
+ public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
+ {
+ this.sslContextFactory = sslContextFactory;
+ this.byteBufferPool = byteBufferPool;
+ this.executor = executor;
+ this.connectionFactory = connectionFactory;
+ }
+
+ @Override
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
+ {
+ String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
+ int port = (Integer)context.get(SSL_PEER_PORT_CONTEXT_KEY);
+ SSLEngine engine = sslContextFactory.newSSLEngine(host, port);
+ engine.setUseClientMode(true);
+ context.put(SSL_ENGINE_CONTEXT_KEY, engine);
+
+ SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine);
+ sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
+ endPoint.setConnection(sslConnection);
+ EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
+ appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
+
+ return sslConnection;
+ }
+
+ protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
+ {
+ return new SslConnection(byteBufferPool, executor, endPoint, engine);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.io.ssl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AbstractEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.FillInterest;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.WriteFlusher;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A Connection that acts as an interceptor between an EndPoint providing SSL encrypted data
+ * and another consumer of an EndPoint (typically an {@link Connection} like HttpConnection) that
+ * wants unencrypted data.
+ * <p>
+ * The connector uses an {@link EndPoint} (typically {@link SelectChannelEndPoint}) as
+ * it's source/sink of encrypted data. It then provides an endpoint via {@link #getDecryptedEndPoint()} to
+ * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
+ * <p>
+ * The design of this class is based on a clear separation between the passive methods, which do not block nor schedule any
+ * asynchronous callbacks, and active methods that do schedule asynchronous callbacks.
+ * <p>
+ * The passive methods are {@link DecryptedEndPoint#fill(ByteBuffer)} and {@link DecryptedEndPoint#flush(ByteBuffer...)}. They make best
+ * effort attempts to progress the connection using only calls to the encrypted {@link EndPoint#fill(ByteBuffer)} and {@link EndPoint#flush(ByteBuffer...)}
+ * methods. They will never block nor schedule any readInterest or write callbacks. If a fill/flush cannot progress either because
+ * of network congestion or waiting for an SSL handshake message, then the fill/flush will simply return with zero bytes filled/flushed.
+ * Specifically, if a flush cannot proceed because it needs to receive a handshake message, then the flush will attempt to fill bytes from the
+ * encrypted endpoint, but if insufficient bytes are read it will NOT call {@link EndPoint#fillInterested(Callback)}.
+ * <p>
+ * It is only the active methods : {@link DecryptedEndPoint#fillInterested(Callback)} and
+ * {@link DecryptedEndPoint#write(Callback, ByteBuffer...)} that may schedule callbacks by calling the encrypted
+ * {@link EndPoint#fillInterested(Callback)} and {@link EndPoint#write(Callback, ByteBuffer...)}
+ * methods. For normal data handling, the decrypted fillInterest method will result in an encrypted fillInterest and a decrypted
+ * write will result in an encrypted write. However, due to SSL handshaking requirements, it is also possible for a decrypted fill
+ * to call the encrypted write and for the decrypted flush to call the encrypted fillInterested methods.
+ * <p>
+ * MOST IMPORTANTLY, the encrypted callbacks from the active methods (#onFillable() and WriteFlusher#completeWrite()) do no filling or flushing
+ * themselves. Instead they simple make the callbacks to the decrypted callbacks, so that the passive encrypted fill/flush will
+ * be called again and make another best effort attempt to progress the connection.
+ *
+ */
+public class SslConnection extends AbstractConnection
+{
+ private static final Logger LOG = Log.getLogger(SslConnection.class);
+ private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
+ private static final ByteBuffer __FILL_CALLED_FLUSH= BufferUtil.allocate(0);
+ private static final ByteBuffer __FLUSH_CALLED_FILL= BufferUtil.allocate(0);
+ private final ByteBufferPool _bufferPool;
+ private final SSLEngine _sslEngine;
+ private final DecryptedEndPoint _decryptedEndPoint;
+ private ByteBuffer _decryptedInput;
+ private ByteBuffer _encryptedInput;
+ private ByteBuffer _encryptedOutput;
+ private final boolean _encryptedDirectBuffers = false;
+ private final boolean _decryptedDirectBuffers = false;
+ private final Runnable _runCompletWrite = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ _decryptedEndPoint.getWriteFlusher().completeWrite();
+ }
+ };
+ private boolean _renegotiationAllowed;
+
+ public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
+ {
+ // This connection does not execute calls to onfillable, so they will be called by the selector thread.
+ // onfillable does not block and will only wakeup another thread to do the actual reading and handling.
+ super(endPoint, executor, !EXECUTE_ONFILLABLE);
+ this._bufferPool = byteBufferPool;
+ this._sslEngine = sslEngine;
+ this._decryptedEndPoint = newDecryptedEndPoint();
+ }
+
+ protected DecryptedEndPoint newDecryptedEndPoint()
+ {
+ return new DecryptedEndPoint();
+ }
+
+ public SSLEngine getSSLEngine()
+ {
+ return _sslEngine;
+ }
+
+ public DecryptedEndPoint getDecryptedEndPoint()
+ {
+ return _decryptedEndPoint;
+ }
+
+ public boolean isRenegotiationAllowed()
+ {
+ return _renegotiationAllowed;
+ }
+
+ public void setRenegotiationAllowed(boolean renegotiationAllowed)
+ {
+ this._renegotiationAllowed = renegotiationAllowed;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ try
+ {
+ // Begin the handshake
+ _sslEngine.beginHandshake();
+ super.onOpen();
+ getDecryptedEndPoint().getConnection().onOpen();
+ }
+ catch (SSLException x)
+ {
+ getEndPoint().close();
+ throw new RuntimeIOException(x);
+ }
+ }
+
+ @Override
+ public void onClose()
+ {
+ _decryptedEndPoint.getConnection().onClose();
+ super.onClose();
+ }
+
+ @Override
+ public void close()
+ {
+ getDecryptedEndPoint().getConnection().close();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ // onFillable means that there are encrypted bytes ready to be filled.
+ // however we do not fill them here on this callback, but instead wakeup
+ // the decrypted readInterest and/or writeFlusher so that they will attempt
+ // to do the fill and/or flush again and these calls will do the actually
+ // filling.
+
+ if (DEBUG)
+ LOG.debug("onFillable enter {}", _decryptedEndPoint);
+
+ // We have received a close handshake, close the end point to send FIN.
+ if (_decryptedEndPoint.isInputShutdown())
+ _decryptedEndPoint.close();
+
+ // wake up whoever is doing the fill or the flush so they can
+ // do all the filling, unwrapping, wrapping and flushing
+ _decryptedEndPoint.getFillInterest().fillable();
+
+ // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+ synchronized(_decryptedEndPoint)
+ {
+ if (_decryptedEndPoint._flushRequiresFillToProgress)
+ {
+ _decryptedEndPoint._flushRequiresFillToProgress = false;
+ getExecutor().execute(_runCompletWrite);
+ }
+ }
+
+ if (DEBUG)
+ LOG.debug("onFillable exit {}", _decryptedEndPoint);
+ }
+
+ @Override
+ public void onFillInterestedFailed(Throwable cause)
+ {
+ // this means that the fill interest in encrypted bytes has failed.
+ // However we do not handle that here on this callback, but instead wakeup
+ // the decrypted readInterest and/or writeFlusher so that they will attempt
+ // to do the fill and/or flush again and these calls will do the actually
+ // handle the cause.
+ _decryptedEndPoint.getFillInterest().onFail(cause);
+
+ boolean failFlusher = false;
+ synchronized(_decryptedEndPoint)
+ {
+ if (_decryptedEndPoint._flushRequiresFillToProgress)
+ {
+ _decryptedEndPoint._flushRequiresFillToProgress = false;
+ failFlusher = true;
+ }
+ }
+ if (failFlusher)
+ _decryptedEndPoint.getWriteFlusher().onFail(cause);
+ }
+
+ @Override
+ public String toString()
+ {
+ ByteBuffer b = _encryptedInput;
+ int ei=b==null?-1:b.remaining();
+ b = _encryptedOutput;
+ int eo=b==null?-1:b.remaining();
+ b = _decryptedInput;
+ int di=b==null?-1:b.remaining();
+
+ return String.format("SslConnection@%x{%s,eio=%d/%d,di=%d} -> %s",
+ hashCode(),
+ _sslEngine.getHandshakeStatus(),
+ ei,eo,di,
+ _decryptedEndPoint.getConnection());
+ }
+
+ public class DecryptedEndPoint extends AbstractEndPoint
+ {
+ private boolean _fillRequiresFlushToProgress;
+ private boolean _flushRequiresFillToProgress;
+ private boolean _cannotAcceptMoreAppDataToFlush;
+ private boolean _handshaken;
+ private boolean _underFlown;
+
+ private final Callback _writeCallback = new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ // This means that a write of encrypted data has completed. Writes are done
+ // only if there is a pending writeflusher or a read needed to write
+ // data. In either case the appropriate callback is passed on.
+ boolean fillable = false;
+ synchronized (DecryptedEndPoint.this)
+ {
+ if (DEBUG)
+ LOG.debug("write.complete {}", SslConnection.this.getEndPoint());
+
+ releaseEncryptedOutputBuffer();
+
+ _cannotAcceptMoreAppDataToFlush = false;
+
+ if (_fillRequiresFlushToProgress)
+ {
+ _fillRequiresFlushToProgress = false;
+ fillable = true;
+ }
+ }
+ if (fillable)
+ getFillInterest().fillable();
+ getExecutor().execute(_runCompletWrite);
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ // This means that a write of data has failed. Writes are done
+ // only if there is an active writeflusher or a read needed to write
+ // data. In either case the appropriate callback is passed on.
+ boolean fail_filler = false;
+ synchronized (DecryptedEndPoint.this)
+ {
+ if (DEBUG)
+ LOG.debug("{} write.failed", SslConnection.this, x);
+ BufferUtil.clear(_encryptedOutput);
+ releaseEncryptedOutputBuffer();
+
+ _cannotAcceptMoreAppDataToFlush = false;
+
+ if (_fillRequiresFlushToProgress)
+ {
+ _fillRequiresFlushToProgress = false;
+ fail_filler = true;
+ }
+ }
+
+ final boolean filler_failed=fail_filler;
+
+ failedCallback(new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ if (filler_failed)
+ getFillInterest().onFail(x);
+ getWriteFlusher().onFail(x);
+ }
+
+ },x);
+ }
+ };
+
+ public DecryptedEndPoint()
+ {
+ super(null,getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
+ setIdleTimeout(getEndPoint().getIdleTimeout());
+ }
+
+ @Override
+ protected FillInterest getFillInterest()
+ {
+ return super.getFillInterest();
+ }
+
+ @Override
+ public void setIdleTimeout(long idleTimeout)
+ {
+ super.setIdleTimeout(idleTimeout);
+ getEndPoint().setIdleTimeout(idleTimeout);
+ }
+
+ @Override
+ protected WriteFlusher getWriteFlusher()
+ {
+ return super.getWriteFlusher();
+ }
+
+ @Override
+ protected void onIncompleteFlush()
+ {
+ // This means that the decrypted endpoint write method was called and not
+ // all data could be wrapped. So either we need to write some encrypted data,
+ // OR if we are handshaking we need to read some encrypted data OR
+ // if neither then we should just try the flush again.
+ boolean flush = false;
+ synchronized (DecryptedEndPoint.this)
+ {
+ if (DEBUG)
+ LOG.debug("onIncompleteFlush {}", getEndPoint());
+ // If we have pending output data,
+ if (BufferUtil.hasContent(_encryptedOutput))
+ {
+ // write it
+ _cannotAcceptMoreAppDataToFlush = true;
+ getEndPoint().write(_writeCallback, _encryptedOutput);
+ }
+ // If we are handshaking and need to read,
+ else if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP)
+ {
+ // check if we are actually read blocked in order to write
+ _flushRequiresFillToProgress = true;
+ SslConnection.this.fillInterested();
+ }
+ else
+ {
+ flush = true;
+ }
+ }
+ if (flush)
+ {
+ // If the output is closed,
+ if (isOutputShutdown())
+ {
+ // don't bother writing, just notify of close
+ getWriteFlusher().onClose();
+ }
+ // Else,
+ else
+ {
+ // try to flush what is pending
+ getWriteFlusher().completeWrite();
+ }
+ }
+ }
+
+ @Override
+ protected boolean needsFill() throws IOException
+ {
+ // This means that the decrypted data consumer has called the fillInterested
+ // method on the DecryptedEndPoint, so we have to work out if there is
+ // decrypted data to be filled or what callbacks to setup to be told when there
+ // might be more encrypted data available to attempt another call to fill
+
+ synchronized (DecryptedEndPoint.this)
+ {
+ // Do we already have some app data, then app can fill now so return true
+ if (BufferUtil.hasContent(_decryptedInput))
+ return true;
+
+ // If we have no encrypted data to decrypt OR we have some, but it is not enough
+ if (BufferUtil.isEmpty(_encryptedInput) || _underFlown)
+ {
+ // We are not ready to read data
+
+ // Are we actually write blocked?
+ if (_fillRequiresFlushToProgress)
+ {
+ // we must be blocked trying to write before we can read
+
+ // Do we have data to write
+ if (BufferUtil.hasContent(_encryptedOutput))
+ {
+ // write it
+ _cannotAcceptMoreAppDataToFlush = true;
+ getEndPoint().write(_writeCallback, _encryptedOutput);
+ }
+ else
+ {
+ // we have already written the net data
+ // pretend we are readable so the wrap is done by next readable callback
+ _fillRequiresFlushToProgress = false;
+ return true;
+ }
+ }
+ else
+ {
+ // Normal readable callback
+ // Get called back on onfillable when then is more data to fill
+ SslConnection.this.fillInterested();
+ }
+
+ return false;
+ }
+ else
+ {
+ // We are ready to read data
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public void setConnection(Connection connection)
+ {
+ if (connection instanceof AbstractConnection)
+ {
+ AbstractConnection a = (AbstractConnection)connection;
+ if (a.getInputBufferSize()<_sslEngine.getSession().getApplicationBufferSize())
+ a.setInputBufferSize(_sslEngine.getSession().getApplicationBufferSize());
+ }
+ super.setConnection(connection);
+ }
+
+ public SslConnection getSslConnection()
+ {
+ return SslConnection.this;
+ }
+
+ @Override
+ public synchronized int fill(ByteBuffer buffer) throws IOException
+ {
+ if (DEBUG)
+ LOG.debug("{} fill enter", SslConnection.this);
+ try
+ {
+ // Do we already have some decrypted data?
+ if (BufferUtil.hasContent(_decryptedInput))
+ return BufferUtil.append(buffer,_decryptedInput);
+
+ // We will need a network buffer
+ if (_encryptedInput == null)
+ _encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+ else
+ BufferUtil.compact(_encryptedInput);
+
+ // We also need an app buffer, but can use the passed buffer if it is big enough
+ ByteBuffer app_in;
+ if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize())
+ app_in = buffer;
+ else if (_decryptedInput == null)
+ app_in = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers);
+ else
+ app_in = _decryptedInput;
+
+ // loop filling and unwrapping until we have something
+ while (true)
+ {
+ // Let's try reading some encrypted data... even if we have some already.
+ int net_filled = getEndPoint().fill(_encryptedInput);
+ if (DEBUG)
+ LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled);
+
+ decryption: while (true)
+ {
+ // Let's unwrap even if we have no net data because in that
+ // case we want to fall through to the handshake handling
+ int pos = BufferUtil.flipToFill(app_in);
+ SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
+ BufferUtil.flipToFlush(app_in, pos);
+ if (DEBUG)
+ LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
+
+ HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+ HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
+ Status unwrapResultStatus = unwrapResult.getStatus();
+
+ _underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW;
+
+ if (_underFlown)
+ {
+ if (net_filled < 0)
+ closeInbound();
+ if (net_filled <= 0)
+ return net_filled;
+ }
+
+ switch (unwrapResultStatus)
+ {
+ case CLOSED:
+ {
+ switch (handshakeStatus)
+ {
+ case NOT_HANDSHAKING:
+ {
+ // We were not handshaking, so just tell the app we are closed
+ return -1;
+ }
+ case NEED_TASK:
+ {
+ _sslEngine.getDelegatedTask().run();
+ continue;
+ }
+ case NEED_WRAP:
+ {
+ // We need to send some handshake data (probably the close handshake).
+ // We return -1 so that the application can drive the close by flushing
+ // or shutting down the output.
+ return -1;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ case BUFFER_UNDERFLOW:
+ case OK:
+ {
+ if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
+ {
+ _handshaken = true;
+ if (DEBUG)
+ LOG.debug("{} {} handshake completed", SslConnection.this,
+ _sslEngine.getUseClientMode() ? "client-side" : "resumed session server-side");
+ }
+
+ // Check whether renegotiation is allowed
+ if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+ {
+ if (DEBUG)
+ LOG.debug("{} renegotiation denied", SslConnection.this);
+ closeInbound();
+ return -1;
+ }
+
+ // If bytes were produced, don't bother with the handshake status;
+ // pass the decrypted data to the application, which will perform
+ // another call to fill() or flush().
+ if (unwrapResult.bytesProduced() > 0)
+ {
+ if (app_in == buffer)
+ return unwrapResult.bytesProduced();
+ return BufferUtil.append(buffer,_decryptedInput);
+ }
+
+ switch (handshakeStatus)
+ {
+ case NOT_HANDSHAKING:
+ {
+ if (_underFlown)
+ break decryption;
+ continue;
+ }
+ case NEED_TASK:
+ {
+ _sslEngine.getDelegatedTask().run();
+ continue;
+ }
+ case NEED_WRAP:
+ {
+ // If we are called from flush()
+ // return to let it do the wrapping.
+ if (buffer == __FLUSH_CALLED_FILL)
+ return 0;
+
+ _fillRequiresFlushToProgress = true;
+ flush(__FILL_CALLED_FLUSH);
+ if (BufferUtil.isEmpty(_encryptedOutput))
+ {
+ // The flush wrote all the encrypted bytes so continue to fill
+ _fillRequiresFlushToProgress = false;
+ continue;
+ }
+ else
+ {
+ // The flush did not complete, return from fill()
+ // and let the write completion mechanism to kick in.
+ return 0;
+ }
+ }
+ case NEED_UNWRAP:
+ {
+ if (_underFlown)
+ break decryption;
+ continue;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ getEndPoint().close();
+ throw e;
+ }
+ finally
+ {
+ // If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
+ if (_flushRequiresFillToProgress)
+ {
+ _flushRequiresFillToProgress = false;
+ getExecutor().execute(_runCompletWrite);
+ }
+
+ if (_encryptedInput != null && !_encryptedInput.hasRemaining())
+ {
+ _bufferPool.release(_encryptedInput);
+ _encryptedInput = null;
+ }
+ if (_decryptedInput != null && !_decryptedInput.hasRemaining())
+ {
+ _bufferPool.release(_decryptedInput);
+ _decryptedInput = null;
+ }
+ if (DEBUG)
+ LOG.debug("{} fill exit", SslConnection.this);
+ }
+ }
+
+ private void closeInbound()
+ {
+ try
+ {
+ _sslEngine.closeInbound();
+ }
+ catch (SSLException x)
+ {
+ LOG.ignore(x);
+ }
+ }
+
+ @Override
+ public synchronized boolean flush(ByteBuffer... appOuts) throws IOException
+ {
+ // The contract for flush does not require that all appOuts bytes are written
+ // or even that any appOut bytes are written! If the connection is write block
+ // or busy handshaking, then zero bytes may be taken from appOuts and this method
+ // will return 0 (even if some handshake bytes were flushed and filled).
+ // it is the applications responsibility to call flush again - either in a busy loop
+ // or better yet by using EndPoint#write to do the flushing.
+
+ if (DEBUG)
+ LOG.debug("{} flush enter {}", SslConnection.this, Arrays.toString(appOuts));
+ int consumed=0;
+ try
+ {
+ if (_cannotAcceptMoreAppDataToFlush)
+ {
+ if (_sslEngine.isOutboundDone())
+ throw new EofException(new ClosedChannelException());
+ return false;
+ }
+
+ // We will need a network buffer
+ if (_encryptedOutput == null)
+ _encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
+
+ while (true)
+ {
+ // We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
+ BufferUtil.compact(_encryptedOutput);
+ int pos = BufferUtil.flipToFill(_encryptedOutput);
+ SSLEngineResult wrapResult = _sslEngine.wrap(appOuts, _encryptedOutput);
+ if (DEBUG)
+ LOG.debug("{} wrap {}", SslConnection.this, wrapResult);
+ BufferUtil.flipToFlush(_encryptedOutput, pos);
+ if (wrapResult.bytesConsumed()>0)
+ consumed+=wrapResult.bytesConsumed();
+
+ boolean allConsumed=true;
+ // clear empty buffers to prevent position creeping up the buffer
+ for (ByteBuffer b : appOuts)
+ {
+ if (BufferUtil.isEmpty(b))
+ BufferUtil.clear(b);
+ else
+ allConsumed=false;
+ }
+
+ Status wrapResultStatus = wrapResult.getStatus();
+
+ // and deal with the results returned from the sslEngineWrap
+ switch (wrapResultStatus)
+ {
+ case CLOSED:
+ // The SSL engine has close, but there may be close handshake that needs to be written
+ if (BufferUtil.hasContent(_encryptedOutput))
+ {
+ _cannotAcceptMoreAppDataToFlush = true;
+ getEndPoint().flush(_encryptedOutput);
+ getEndPoint().shutdownOutput();
+ // If we failed to flush the close handshake then we will just pretend that
+ // the write has progressed normally and let a subsequent call to flush
+ // (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
+ // The caller will find out about the close on a subsequent flush or fill.
+ if (BufferUtil.hasContent(_encryptedOutput))
+ return false;
+ }
+ // otherwise we have written, and the caller will close the underlying connection
+ else
+ {
+ getEndPoint().shutdownOutput();
+ }
+ return allConsumed;
+
+ case BUFFER_UNDERFLOW:
+ throw new IllegalStateException();
+
+ default:
+ if (DEBUG)
+ LOG.debug("{} {} {}", this, wrapResultStatus, BufferUtil.toDetailString(_encryptedOutput));
+
+ if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
+ {
+ _handshaken = true;
+ if (DEBUG)
+ LOG.debug("{} {} handshake completed", SslConnection.this, "server-side");
+ }
+
+ HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
+
+ // Check whether renegotiation is allowed
+ if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
+ {
+ if (DEBUG)
+ LOG.debug("{} renegotiation denied", SslConnection.this);
+ shutdownOutput();
+ return allConsumed;
+ }
+
+ // if we have net bytes, let's try to flush them
+ if (BufferUtil.hasContent(_encryptedOutput))
+ getEndPoint().flush(_encryptedOutput);
+
+ // But we also might have more to do for the handshaking state.
+ switch (handshakeStatus)
+ {
+ case NOT_HANDSHAKING:
+ // Return with the number of bytes consumed (which may be 0)
+ return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+ case NEED_TASK:
+ // run the task and continue
+ _sslEngine.getDelegatedTask().run();
+ continue;
+
+ case NEED_WRAP:
+ // Hey we just wrapped! Oh well who knows what the sslEngine is thinking, so continue and we will wrap again
+ continue;
+
+ case NEED_UNWRAP:
+ // Ah we need to fill some data so we can write.
+ // So if we were not called from fill and the app is not reading anyway
+ if (appOuts[0]!=__FILL_CALLED_FLUSH && !getFillInterest().isInterested())
+ {
+ // Tell the onFillable method that there might be a write to complete
+ _flushRequiresFillToProgress = true;
+ fill(__FLUSH_CALLED_FILL);
+ // Check if after the fill() we need to wrap again
+ if (handshakeStatus == HandshakeStatus.NEED_WRAP)
+ continue;
+ }
+ return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
+
+ case FINISHED:
+ throw new IllegalStateException();
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ getEndPoint().close();
+ throw e;
+ }
+ finally
+ {
+ if (DEBUG)
+ LOG.debug("{} flush exit, consumed {}", SslConnection.this, consumed);
+ releaseEncryptedOutputBuffer();
+ }
+ }
+
+ private void releaseEncryptedOutputBuffer()
+ {
+ if (!Thread.holdsLock(DecryptedEndPoint.this))
+ throw new IllegalStateException();
+ if (_encryptedOutput != null && !_encryptedOutput.hasRemaining())
+ {
+ _bufferPool.release(_encryptedOutput);
+ _encryptedOutput = null;
+ }
+ }
+
+ @Override
+ public void shutdownOutput()
+ {
+ boolean ishut = isInputShutdown();
+ boolean oshut = isOutputShutdown();
+ if (DEBUG)
+ LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, oshut, ishut);
+ if (ishut)
+ {
+ // Aggressively close, since inbound close alert has already been processed
+ // and the TLS specification allows to close the connection directly, which
+ // is what most other implementations expect: a FIN rather than a TLS close
+ // reply. If a TLS close reply is sent, most implementations send a RST.
+ getEndPoint().close();
+ }
+ else if (!oshut)
+ {
+ try
+ {
+ _sslEngine.closeOutbound();
+ flush(BufferUtil.EMPTY_BUFFER); // Send close handshake
+ SslConnection.this.fillInterested(); // seek reply FIN or RST or close handshake
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ getEndPoint().close();
+ }
+ }
+ }
+
+ @Override
+ public boolean isOutputShutdown()
+ {
+ return _sslEngine.isOutboundDone() || getEndPoint().isOutputShutdown();
+ }
+
+ @Override
+ public void close()
+ {
+ super.close();
+ // First send the TLS Close Alert, then the FIN
+ shutdownOutput();
+ getEndPoint().close();
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return getEndPoint().isOpen();
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return getEndPoint();
+ }
+
+ @Override
+ public boolean isInputShutdown()
+ {
+ return _sslEngine.isInboundDone();
+ }
+
+ @Override
+ public String toString()
+ {
+ return super.toString()+"->"+getEndPoint().toString();
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty IO : Core SSL Support
+ */
+package org.eclipse.jetty.io.ssl;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.UserIdentity.Scope;
+
+/**
+ * AbstractUserAuthentication
+ *
+ *
+ * Base class for representing an authenticated user.
+ */
+public abstract class AbstractUserAuthentication implements User, Serializable
+{
+ private static final long serialVersionUID = -6290411814232723403L;
+ protected String _method;
+ protected transient UserIdentity _userIdentity;
+
+
+
+ public AbstractUserAuthentication(String method, UserIdentity userIdentity)
+ {
+ _method = method;
+ _userIdentity = userIdentity;
+ }
+
+
+ @Override
+ public String getAuthMethod()
+ {
+ return _method;
+ }
+
+ @Override
+ public UserIdentity getUserIdentity()
+ {
+ return _userIdentity;
+ }
+
+ @Override
+ public boolean isUserInRole(Scope scope, String role)
+ {
+ String roleToTest = null;
+ if (scope!=null && scope.getRoleRefMap()!=null)
+ roleToTest=scope.getRoleRefMap().get(role);
+ if (roleToTest==null)
+ roleToTest=role;
+ //Servlet Spec 3.1 pg 125 if testing special role **
+ if ("**".equals(roleToTest.trim()))
+ {
+ //if ** is NOT a declared role name, the we return true
+ //as the user is authenticated. If ** HAS been declared as a
+ //role name, then we have to check if the user has that role
+ if (!declaredRolesContains("**"))
+ return true;
+ else
+ return _userIdentity.isUserInRole(role, scope);
+ }
+
+ return _userIdentity.isUserInRole(role, scope);
+ }
+
+ public boolean declaredRolesContains(String roleName)
+ {
+ SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+ if (security==null)
+ return false;
+
+ if (security instanceof ConstraintAware)
+ {
+ Set<String> declaredRoles = ((ConstraintAware)security).getRoles();
+ return (declaredRoles != null) && declaredRoles.contains(roleName);
+ }
+
+ return false;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Server;
+
+/**
+ * Authenticator Interface
+ * <p>
+ * An Authenticator is responsible for checking requests and sending
+ * response challenges in order to authenticate a request.
+ * Various types of {@link Authentication} are returned in order to
+ * signal the next step in authentication.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public interface Authenticator
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Configure the Authenticator
+ * @param configuration
+ */
+ void setConfiguration(AuthConfiguration configuration);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The name of the authentication method
+ */
+ String getAuthMethod();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Called prior to validateRequest. The authenticator can
+ * manipulate the request to update it with information that
+ * can be inspected prior to validateRequest being called.
+ * The primary purpose of this method is to satisfy the Servlet
+ * Spec 3.1 section 13.6.3 on handling Form authentication
+ * where the http method of the original request causing authentication
+ * is not the same as the http method resulting from the redirect
+ * after authentication.
+ * @param request
+ */
+ void prepareRequest(ServletRequest request);
+
+
+ /* ------------------------------------------------------------ */
+ /** Validate a request
+ * @param request The request
+ * @param response The response
+ * @param mandatory True if authentication is mandatory.
+ * @return An Authentication. If Authentication is successful, this will be a {@link org.eclipse.jetty.server.Authentication.User}. If a response has
+ * been sent by the Authenticator (which can be done for both successful and unsuccessful authentications), then the result will
+ * implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}. If Authentication is not manditory, then a
+ * {@link org.eclipse.jetty.server.Authentication.Deferred} may be returned.
+ *
+ * @throws ServerAuthException
+ */
+ Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param request
+ * @param response
+ * @param mandatory
+ * @param validatedUser
+ * @return true if response is secure
+ * @throws ServerAuthException
+ */
+ boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException;
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * Authenticator Configuration
+ */
+ interface AuthConfiguration
+ {
+ String getAuthMethod();
+ String getRealmName();
+
+ /** Get a SecurityHandler init parameter
+ * @see SecurityHandler#getInitParameter(String)
+ * @param param parameter name
+ * @return Parameter value or null
+ */
+ String getInitParameter(String param);
+
+ /* ------------------------------------------------------------ */
+ /** Get a SecurityHandler init parameter names
+ * @see SecurityHandler#getInitParameterNames()
+ * @return Set of parameter names
+ */
+ Set<String> getInitParameterNames();
+
+ LoginService getLoginService();
+ IdentityService getIdentityService();
+ boolean isSessionRenewedOnAuthentication();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * Authenticator Factory
+ */
+ interface Factory
+ {
+ Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public interface ConstraintAware
+{
+ List<ConstraintMapping> getConstraintMappings();
+ Set<String> getRoles();
+
+ /* ------------------------------------------------------------ */
+ /** Set Constraint Mappings and roles.
+ * Can only be called during initialization.
+ * @param constraintMappings
+ * @param roles
+ */
+ void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles);
+
+ /* ------------------------------------------------------------ */
+ /** Add a Constraint Mapping.
+ * May be called for running webapplication as an annotated servlet is instantiated.
+ * @param mapping
+ */
+ void addConstraintMapping(ConstraintMapping mapping);
+
+
+ /* ------------------------------------------------------------ */
+ /** Add a Role definition.
+ * May be called on running webapplication as an annotated servlet is instantiated.
+ * @param role
+ */
+ void addRole(String role);
+
+ /**
+ * See Servlet Spec 31, sec 13.8.4, pg 145
+ * When true, requests with http methods not explicitly covered either by inclusion or omissions
+ * in constraints, will have access denied.
+ * @param deny
+ */
+ void setDenyUncoveredHttpMethods(boolean deny);
+
+ boolean isDenyUncoveredHttpMethods();
+
+ /**
+ * See Servlet Spec 31, sec 13.8.4, pg 145
+ * Container must check if there are urls with uncovered http methods
+ */
+ boolean checkPathsWithUncoveredHttpMethods();
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import org.eclipse.jetty.util.security.Constraint;
+
+public class ConstraintMapping
+{
+ String _method;
+ String[] _methodOmissions;
+
+ String _pathSpec;
+
+ Constraint _constraint;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the constraint.
+ */
+ public Constraint getConstraint()
+ {
+ return _constraint;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param constraint The constraint to set.
+ */
+ public void setConstraint(Constraint constraint)
+ {
+ this._constraint = constraint;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the method.
+ */
+ public String getMethod()
+ {
+ return _method;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param method The method to set.
+ */
+ public void setMethod(String method)
+ {
+ this._method = method;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the pathSpec.
+ */
+ public String getPathSpec()
+ {
+ return _pathSpec;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathSpec The pathSpec to set.
+ */
+ public void setPathSpec(String pathSpec)
+ {
+ this._pathSpec = pathSpec;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param omissions The http-method-omission
+ */
+ public void setMethodOmissions(String[] omissions)
+ {
+ _methodOmissions = omissions;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String[] getMethodOmissions()
+ {
+ return _methodOmissions;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import javax.servlet.HttpConstraintElement;
+import javax.servlet.HttpMethodConstraintElement;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * ConstraintSecurityHandler
+ *
+ * Handler to enforce SecurityConstraints. This implementation is servlet spec
+ * 3.1 compliant and pre-computes the constraint combinations for runtime
+ * efficiency.
+ *
+ */
+public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
+{
+ private static final Logger LOG = Log.getLogger(SecurityHandler.class); //use same as SecurityHandler
+
+ private static final String OMISSION_SUFFIX = ".omission";
+ private static final String ALL_METHODS = "*";
+ private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
+ private final Set<String> _roles = new CopyOnWriteArraySet<>();
+ private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
+ private boolean _denyUncoveredMethods = false;
+
+
+ /* ------------------------------------------------------------ */
+ public static Constraint createConstraint()
+ {
+ return new Constraint();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param constraint
+ */
+ public static Constraint createConstraint(Constraint constraint)
+ {
+ try
+ {
+ return (Constraint)constraint.clone();
+ }
+ catch (CloneNotSupportedException e)
+ {
+ throw new IllegalStateException (e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a security constraint
+ *
+ * @param name
+ * @param authenticate
+ * @param roles
+ * @param dataConstraint
+ */
+ public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
+ {
+ Constraint constraint = createConstraint();
+ if (name != null)
+ constraint.setName(name);
+ constraint.setAuthenticate(authenticate);
+ constraint.setRoles(roles);
+ constraint.setDataConstraint(dataConstraint);
+ return constraint;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param name
+ * @param element
+ */
+ public static Constraint createConstraint (String name, HttpConstraintElement element)
+ {
+ return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param name
+ * @param rolesAllowed
+ * @param permitOrDeny
+ * @param transport
+ */
+ public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
+ {
+ Constraint constraint = createConstraint();
+
+ if (rolesAllowed == null || rolesAllowed.length==0)
+ {
+ if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
+ {
+ //Equivalent to <auth-constraint> with no roles
+ constraint.setName(name+"-Deny");
+ constraint.setAuthenticate(true);
+ }
+ else
+ {
+ //Equivalent to no <auth-constraint>
+ constraint.setName(name+"-Permit");
+ constraint.setAuthenticate(false);
+ }
+ }
+ else
+ {
+ //Equivalent to <auth-constraint> with list of <security-role-name>s
+ constraint.setAuthenticate(true);
+ constraint.setRoles(rolesAllowed);
+ constraint.setName(name+"-RolesAllowed");
+ }
+
+ //Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
+ constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
+ return constraint;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathSpec
+ * @param constraintMappings
+ */
+ public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
+ {
+ if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+ return Collections.emptyList();
+
+ List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+ for (ConstraintMapping mapping:constraintMappings)
+ {
+ if (pathSpec.equals(mapping.getPathSpec()))
+ {
+ mappings.add(mapping);
+ }
+ }
+ return mappings;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Take out of the constraint mappings those that match the
+ * given path.
+ *
+ * @param pathSpec
+ * @param constraintMappings a new list minus the matching constraints
+ */
+ public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
+ {
+ if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+ return Collections.emptyList();
+
+ List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+ for (ConstraintMapping mapping:constraintMappings)
+ {
+ //Remove the matching mappings by only copying in non-matching mappings
+ if (!pathSpec.equals(mapping.getPathSpec()))
+ {
+ mappings.add(mapping);
+ }
+ }
+ return mappings;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
+ *
+ * @param name
+ * @param pathSpec
+ * @param securityElement
+ * @return
+ */
+ public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
+ {
+ List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+
+ //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
+ Constraint httpConstraint = null;
+ ConstraintMapping httpConstraintMapping = null;
+
+ if (securityElement.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT ||
+ securityElement.getRolesAllowed().length != 0 ||
+ securityElement.getTransportGuarantee() != TransportGuarantee.NONE)
+ {
+ httpConstraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
+
+ //Create a mapping for the pathSpec for the default case
+ httpConstraintMapping = new ConstraintMapping();
+ httpConstraintMapping.setPathSpec(pathSpec);
+ httpConstraintMapping.setConstraint(httpConstraint);
+ mappings.add(httpConstraintMapping);
+ }
+
+
+ //See Spec 13.4.1.2 p127
+ List<String> methodOmissions = new ArrayList<String>();
+
+ //make constraint mappings for this url for each of the HttpMethodConstraintElements
+ Collection<HttpMethodConstraintElement> methodConstraintElements = securityElement.getHttpMethodConstraints();
+ if (methodConstraintElements != null)
+ {
+ for (HttpMethodConstraintElement methodConstraintElement:methodConstraintElements)
+ {
+ //Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
+ Constraint methodConstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraintElement);
+ ConstraintMapping mapping = new ConstraintMapping();
+ mapping.setConstraint(methodConstraint);
+ mapping.setPathSpec(pathSpec);
+ if (methodConstraintElement.getMethodName() != null)
+ {
+ mapping.setMethod(methodConstraintElement.getMethodName());
+ //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+ methodOmissions.add(methodConstraintElement.getMethodName());
+ }
+ mappings.add(mapping);
+ }
+ }
+ //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+ //UNLESS the default constraint contains all default values. In that case, we won't add it. See Servlet Spec 3.1 pg 129
+ if (methodOmissions.size() > 0 && httpConstraintMapping != null)
+ httpConstraintMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
+
+ return mappings;
+ }
+
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the constraintMappings.
+ */
+ @Override
+ public List<ConstraintMapping> getConstraintMappings()
+ {
+ return _constraintMappings;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Set<String> getRoles()
+ {
+ return _roles;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Process the constraints following the combining rules in Servlet 3.0 EA
+ * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+ *
+ * @param constraintMappings
+ * The constraintMappings to set, from which the set of known roles
+ * is determined.
+ */
+ public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
+ {
+ setConstraintMappings(constraintMappings,null);
+ }
+
+ /**
+ * Process the constraints following the combining rules in Servlet 3.0 EA
+ * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+ *
+ * @param constraintMappings
+ * The constraintMappings to set as array, from which the set of known roles
+ * is determined. Needed to retain API compatibility for 7.x
+ */
+ public void setConstraintMappings( ConstraintMapping[] constraintMappings )
+ {
+ setConstraintMappings( Arrays.asList(constraintMappings), null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Process the constraints following the combining rules in Servlet 3.0 EA
+ * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+ *
+ * @param constraintMappings
+ * The constraintMappings to set.
+ * @param roles The known roles (or null to determine them from the mappings)
+ */
+ @Override
+ public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
+ {
+ _constraintMappings.clear();
+ _constraintMappings.addAll(constraintMappings);
+
+ if (roles==null)
+ {
+ roles = new HashSet<>();
+ for (ConstraintMapping cm : constraintMappings)
+ {
+ String[] cmr = cm.getConstraint().getRoles();
+ if (cmr!=null)
+ {
+ for (String r : cmr)
+ if (!ALL_METHODS.equals(r))
+ roles.add(r);
+ }
+ }
+ }
+ setRoles(roles);
+
+ if (isStarted())
+ {
+ for (ConstraintMapping mapping : _constraintMappings)
+ {
+ processConstraintMapping(mapping);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the known roles.
+ * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
+ * {@link #setConstraintMappings(List, Set)}.
+ * @param roles The known roles (or null to determine them from the mappings)
+ */
+ public void setRoles(Set<String> roles)
+ {
+ _roles.clear();
+ _roles.addAll(roles);
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
+ */
+ @Override
+ public void addConstraintMapping(ConstraintMapping mapping)
+ {
+ _constraintMappings.add(mapping);
+ if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
+ {
+ //allow for lazy role naming: if a role is named in a security constraint, try and
+ //add it to the list of declared roles (ie as if it was declared with a security-role
+ for (String role : mapping.getConstraint().getRoles())
+ {
+ if ("*".equals(role) || "**".equals(role))
+ continue;
+ addRole(role);
+ }
+ }
+
+ if (isStarted())
+ {
+ processConstraintMapping(mapping);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
+ */
+ @Override
+ public void addRole(String role)
+ {
+ //add to list of declared roles
+ boolean modified = _roles.add(role);
+ if (isStarted() && modified)
+ {
+ // Add the new role to currently defined any role role infos
+ for (Map<String,RoleInfo> map : _constraintMap.values())
+ {
+ for (RoleInfo info : map.values())
+ {
+ if (info.isAnyRole())
+ info.addRole(role);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.SecurityHandler#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ _constraintMap.clear();
+ if (_constraintMappings!=null)
+ {
+ for (ConstraintMapping mapping : _constraintMappings)
+ {
+ processConstraintMapping(mapping);
+ }
+ }
+
+ //Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
+ checkPathsWithUncoveredHttpMethods();
+
+ super.doStart();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ _constraintMap.clear();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create and combine the constraint with the existing processed
+ * constraints.
+ *
+ * @param mapping
+ */
+ protected void processConstraintMapping(ConstraintMapping mapping)
+ {
+ Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
+ if (mappings == null)
+ {
+ mappings = new HashMap<String,RoleInfo>();
+ _constraintMap.put(mapping.getPathSpec(),mappings);
+ }
+ RoleInfo allMethodsRoleInfo = mappings.get(ALL_METHODS);
+ if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
+ return;
+
+ if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
+ {
+ processConstraintMappingWithMethodOmissions(mapping, mappings);
+ return;
+ }
+
+ String httpMethod = mapping.getMethod();
+ if (httpMethod==null)
+ httpMethod=ALL_METHODS;
+ RoleInfo roleInfo = mappings.get(httpMethod);
+ if (roleInfo == null)
+ {
+ roleInfo = new RoleInfo();
+ mappings.put(httpMethod,roleInfo);
+ if (allMethodsRoleInfo != null)
+ {
+ roleInfo.combine(allMethodsRoleInfo);
+ }
+ }
+ if (roleInfo.isForbidden())
+ return;
+
+ //add in info from the constraint
+ configureRoleInfo(roleInfo, mapping);
+
+ if (roleInfo.isForbidden())
+ {
+ if (httpMethod.equals(ALL_METHODS))
+ {
+ mappings.clear();
+ mappings.put(ALL_METHODS,roleInfo);
+ }
+ }
+ else
+ {
+ //combine with any entry that covers all methods
+ if (httpMethod == null)
+ {
+ for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
+ {
+ if (entry.getKey() != null)
+ {
+ RoleInfo specific = entry.getValue();
+ specific.combine(roleInfo);
+ }
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Constraints that name method omissions are dealt with differently.
+ * We create an entry in the mappings with key "<method>.omission". This entry
+ * is only ever combined with other omissions for the same method to produce a
+ * consolidated RoleInfo. Then, when we wish to find the relevant constraints for
+ * a given Request (in prepareConstraintInfo()), we consult 3 types of entries in
+ * the mappings: an entry that names the method of the Request specifically, an
+ * entry that names constraints that apply to all methods, entries of the form
+ * <method>.omission, where the method of the Request is not named in the omission.
+ * @param mapping
+ * @param mappings
+ */
+ protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
+ {
+ String[] omissions = mapping.getMethodOmissions();
+ StringBuilder sb = new StringBuilder();
+ for (int i=0; i<omissions.length; i++)
+ {
+ if (i > 0)
+ sb.append(".");
+ sb.append(omissions[i]);
+ }
+ sb.append(OMISSION_SUFFIX);
+ RoleInfo ri = new RoleInfo();
+ mappings.put(sb.toString(), ri);
+ configureRoleInfo(ri, mapping);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Initialize or update the RoleInfo from the constraint
+ * @param ri
+ * @param mapping
+ */
+ protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
+ {
+ Constraint constraint = mapping.getConstraint();
+ boolean forbidden = constraint.isForbidden();
+ ri.setForbidden(forbidden);
+
+ //set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
+ //which we need in order to do combining of omissions in prepareConstraintInfo
+ UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
+ ri.setUserDataConstraint(userDataConstraint);
+
+ //if forbidden, no point setting up roles
+ if (!ri.isForbidden())
+ {
+ //add in the roles
+ boolean checked = mapping.getConstraint().getAuthenticate();
+ ri.setChecked(checked);
+
+ if (ri.isChecked())
+ {
+ if (mapping.getConstraint().isAnyRole())
+ {
+ // * means matches any defined role
+ for (String role : _roles)
+ ri.addRole(role);
+ ri.setAnyRole(true);
+ }
+ else if (mapping.getConstraint().isAnyAuth())
+ {
+ //being authenticated is sufficient, not necessary to check roles
+ ri.setAnyAuth(true);
+ }
+ else
+ {
+ //user must be in one of the named roles
+ String[] newRoles = mapping.getConstraint().getRoles();
+ for (String role : newRoles)
+ {
+ //check role has been defined
+ if (!_roles.contains(role))
+ throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
+ ri.addRole(role);
+ }
+ }
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Find constraints that apply to the given path.
+ * In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
+ * represents a merged set of user data constraints, roles etc -:
+ * <ol>
+ * <li>A mapping of an exact method name </li>
+ * <li>A mapping with key * that matches every method name</li>
+ * <li>Mappings with keys of the form "<method>.<method>.<method>.omission" that indicates it will match every method name EXCEPT those given</li>
+ * </ol>
+ *
+ * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
+ */
+ @Override
+ protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
+ {
+ Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
+
+ if (mappings != null)
+ {
+ String httpMethod = request.getMethod();
+ RoleInfo roleInfo = mappings.get(httpMethod);
+ if (roleInfo == null)
+ {
+ //No specific http-method names matched
+ List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
+
+ //Get info for constraint that matches all methods if it exists
+ RoleInfo all = mappings.get(ALL_METHODS);
+ if (all != null)
+ applicableConstraints.add(all);
+
+
+ //Get info for constraints that name method omissions where target method name is not omitted
+ //(ie matches because target method is not omitted, hence considered covered by the constraint)
+ for (Entry<String, RoleInfo> entry: mappings.entrySet())
+ {
+ if (entry.getKey() != null && entry.getKey().endsWith(OMISSION_SUFFIX) && ! entry.getKey().contains(httpMethod))
+ applicableConstraints.add(entry.getValue());
+ }
+
+ if (applicableConstraints.size() == 0 && isDenyUncoveredHttpMethods())
+ {
+ roleInfo = new RoleInfo();
+ roleInfo.setForbidden(true);
+ }
+ else if (applicableConstraints.size() == 1)
+ roleInfo = applicableConstraints.get(0);
+ else
+ {
+ roleInfo = new RoleInfo();
+ roleInfo.setUserDataConstraint(UserDataConstraint.None);
+
+ for (RoleInfo r:applicableConstraints)
+ roleInfo.combine(r);
+ }
+
+ }
+
+ return roleInfo;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo roleInfo) throws IOException
+ {
+ if (roleInfo == null)
+ return true;
+
+ if (roleInfo.isForbidden())
+ return false;
+
+ UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
+ if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
+ return true;
+
+ HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration();
+
+ if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral)
+ {
+ if (request.isSecure())
+ return true;
+
+ if (httpConfig.getSecurePort() > 0)
+ {
+ String scheme = httpConfig.getSecureScheme();
+ int port = httpConfig.getSecurePort();
+ String url = ("https".equalsIgnoreCase(scheme) && port==443)
+ ? "https://"+request.getServerName()+request.getRequestURI()
+ : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();
+ if (request.getQueryString() != null)
+ url += "?" + request.getQueryString();
+ response.setContentLength(0);
+ response.sendRedirect(url);
+ }
+ else
+ response.sendError(HttpStatus.FORBIDDEN_403,"!Secure");
+
+ request.setHandled(true);
+ return false;
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
+ }
+
+ }
+
+ @Override
+ protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
+ {
+ return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity)
+ */
+ @Override
+ protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
+ throws IOException
+ {
+ if (constraintInfo == null)
+ {
+ return true;
+ }
+ RoleInfo roleInfo = (RoleInfo)constraintInfo;
+
+ if (!roleInfo.isChecked())
+ {
+ return true;
+ }
+
+ //handle ** role constraint
+ if (roleInfo.isAnyAuth() && request.getUserPrincipal() != null)
+ {
+ return true;
+ }
+
+ //check if user is any of the allowed roles
+ boolean isUserInRole = false;
+ for (String role : roleInfo.getRoles())
+ {
+ if (userIdentity.isUserInRole(role, null))
+ {
+ isUserInRole = true;
+ break;
+ }
+ }
+
+ //handle * role constraint
+ if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
+ {
+ return true;
+ }
+
+ //normal role check
+ if (isUserInRole)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void dump(Appendable out,String indent) throws IOException
+ {
+ // TODO these should all be beans
+ dumpBeans(out,indent,
+ Collections.singleton(getLoginService()),
+ Collections.singleton(getIdentityService()),
+ Collections.singleton(getAuthenticator()),
+ Collections.singleton(_roles),
+ _constraintMap.entrySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.ConstraintAware#setDenyUncoveredHttpMethods(boolean)
+ */
+ @Override
+ public void setDenyUncoveredHttpMethods(boolean deny)
+ {
+ _denyUncoveredMethods = deny;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isDenyUncoveredHttpMethods()
+ {
+ return _denyUncoveredMethods;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Servlet spec 3.1 pg. 147.
+ */
+ @Override
+ public boolean checkPathsWithUncoveredHttpMethods()
+ {
+ Set<String> paths = getPathsWithUncoveredHttpMethods();
+ if (paths != null && !paths.isEmpty())
+ {
+ for (String p:paths)
+ LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p);
+ if (LOG.isDebugEnabled())
+ LOG.debug(new Throwable());
+ return true;
+ }
+ return false;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Servlet spec 3.1 pg. 147.
+ * The container must check all the combined security constraint
+ * information and log any methods that are not protected and the
+ * urls at which they are not protected
+ *
+ * @return list of paths for which there are uncovered methods
+ */
+ public Set<String> getPathsWithUncoveredHttpMethods ()
+ {
+ //if automatically denying uncovered methods, there are no uncovered methods
+ if (_denyUncoveredMethods)
+ return Collections.emptySet();
+
+ Set<String> uncoveredPaths = new HashSet<String>();
+
+ for (String path:_constraintMap.keySet())
+ {
+ Map<String, RoleInfo> methodMappings = _constraintMap.get(path);
+ //Each key is either:
+ // : an exact method name
+ // : * which means that the constraint applies to every method
+ // : a name of the form <method>.<method>.<method>.omission, which means it applies to every method EXCEPT those named
+ if (methodMappings.get(ALL_METHODS) != null)
+ continue; //can't be any uncovered methods for this url path
+
+ boolean hasOmissions = omissionsExist(path, methodMappings);
+
+ for (String method:methodMappings.keySet())
+ {
+ if (method.endsWith(OMISSION_SUFFIX))
+ {
+ Set<String> omittedMethods = getOmittedMethods(method);
+ for (String m:omittedMethods)
+ {
+ if (!methodMappings.containsKey(m))
+ uncoveredPaths.add(path);
+ }
+ }
+ else
+ {
+ //an exact method name
+ if (!hasOmissions)
+ //a http-method does not have http-method-omission to cover the other method names
+ uncoveredPaths.add(path);
+ }
+
+ }
+ }
+ return uncoveredPaths;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Check if any http method omissions exist in the list of method
+ * to auth info mappings.
+ *
+ * @param path
+ * @param methodMappings
+ * @return
+ */
+ protected boolean omissionsExist (String path, Map<String, RoleInfo> methodMappings)
+ {
+ if (methodMappings == null)
+ return false;
+ boolean hasOmissions = false;
+ for (String m:methodMappings.keySet())
+ {
+ if (m.endsWith(OMISSION_SUFFIX))
+ hasOmissions = true;
+ }
+ return hasOmissions;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Given a string of the form <method>.<method>.omission
+ * split out the individual method names.
+ *
+ * @param omission
+ * @return
+ */
+ protected Set<String> getOmittedMethods (String omission)
+ {
+ if (omission == null || !omission.endsWith(OMISSION_SUFFIX))
+ return Collections.emptySet();
+
+ String[] strings = omission.split("\\.");
+ Set<String> methods = new HashSet<String>();
+ for (int i=0;i<strings.length-1;i++)
+ methods.add(strings[i]);
+ return methods;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public interface CrossContextPsuedoSession<T>
+{
+
+ T fetch(HttpServletRequest request);
+
+ void store(T data, HttpServletResponse response);
+
+ void clear(HttpServletRequest request);
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.security.Authenticator.AuthConfiguration;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.security.authentication.ClientCertAuthenticator;
+import org.eclipse.jetty.security.authentication.DigestAuthenticator;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.security.authentication.SpnegoAuthenticator;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * The Default Authenticator Factory.
+ * Uses the {@link AuthConfiguration#getAuthMethod()} to select an {@link Authenticator} from: <ul>
+ * <li>{@link org.eclipse.jetty.security.authentication.BasicAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.DigestAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.FormAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.ClientCertAuthenticator}</li>
+ * </ul>
+ * All authenticators derived from {@link org.eclipse.jetty.security.authentication.LoginAuthenticator} are
+ * wrapped with a {@link org.eclipse.jetty.security.authentication.DeferredAuthentication}
+ * instance, which is used if authentication is not mandatory.
+ *
+ * The Authentications from the {@link org.eclipse.jetty.security.authentication.FormAuthenticator} are always wrapped in a
+ * {@link org.eclipse.jetty.security.authentication.SessionAuthentication}
+ * <p>
+ * If a {@link LoginService} has not been set on this factory, then
+ * the service is selected by searching the {@link Server#getBeans(Class)} results for
+ * a service that matches the realm name, else the first LoginService found is used.
+ *
+ */
+public class DefaultAuthenticatorFactory implements Authenticator.Factory
+{
+ LoginService _loginService;
+
+ public Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService)
+ {
+ String auth=configuration.getAuthMethod();
+ Authenticator authenticator=null;
+
+ if (auth==null || Constraint.__BASIC_AUTH.equalsIgnoreCase(auth))
+ authenticator=new BasicAuthenticator();
+ else if (Constraint.__DIGEST_AUTH.equalsIgnoreCase(auth))
+ authenticator=new DigestAuthenticator();
+ else if (Constraint.__FORM_AUTH.equalsIgnoreCase(auth))
+ authenticator=new FormAuthenticator();
+ else if ( Constraint.__SPNEGO_AUTH.equalsIgnoreCase(auth) )
+ authenticator = new SpnegoAuthenticator();
+ else if ( Constraint.__NEGOTIATE_AUTH.equalsIgnoreCase(auth) ) // see Bug #377076
+ authenticator = new SpnegoAuthenticator(Constraint.__NEGOTIATE_AUTH);
+ if (Constraint.__CERT_AUTH.equalsIgnoreCase(auth)||Constraint.__CERT_AUTH2.equalsIgnoreCase(auth))
+ authenticator=new ClientCertAuthenticator();
+
+ return authenticator;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the loginService
+ */
+ public LoginService getLoginService()
+ {
+ return _loginService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param loginService the loginService to set
+ */
+ public void setLoginService(LoginService loginService)
+ {
+ _loginService = loginService;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Default Identity Service implementation.
+ * This service handles only role reference maps passed in an
+ * associated {@link org.eclipse.jetty.server.UserIdentity.Scope}. If there are roles
+ * refs present, then associate will wrap the UserIdentity with one
+ * that uses the role references in the
+ * {@link org.eclipse.jetty.server.UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+ * implementation. All other operations are effectively noops.
+ *
+ */
+public class DefaultIdentityService implements IdentityService
+{
+ /* ------------------------------------------------------------ */
+ public DefaultIdentityService()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * If there are roles refs present in the scope, then wrap the UserIdentity
+ * with one that uses the role references in the {@link UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+ */
+ public Object associate(UserIdentity user)
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void disassociate(Object previous)
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object setRunAs(UserIdentity user, RunAsToken token)
+ {
+ return token;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void unsetRunAs(Object lastToken)
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public RunAsToken newRunAsToken(String runAsName)
+ {
+ return new RoleRunAsToken(runAsName);
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity getSystemUserIdentity()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity newUserIdentity(final Subject subject, final Principal userPrincipal, final String[] roles)
+ {
+ return new DefaultUserIdentity(subject,userPrincipal,roles);
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * The default implementation of UserIdentity.
+ *
+ */
+public class DefaultUserIdentity implements UserIdentity
+{
+ private final Subject _subject;
+ private final Principal _userPrincipal;
+ private final String[] _roles;
+
+ public DefaultUserIdentity(Subject subject, Principal userPrincipal, String[] roles)
+ {
+ _subject=subject;
+ _userPrincipal=userPrincipal;
+ _roles=roles;
+ }
+
+ public Subject getSubject()
+ {
+ return _subject;
+ }
+
+ public Principal getUserPrincipal()
+ {
+ return _userPrincipal;
+ }
+
+ public boolean isUserInRole(String role, Scope scope)
+ {
+ //Servlet Spec 3.1, pg 125
+ if ("*".equals(role))
+ return false;
+
+ String roleToTest = null;
+ if (scope!=null && scope.getRoleRefMap()!=null)
+ roleToTest=scope.getRoleRefMap().get(role);
+
+ //Servlet Spec 3.1, pg 125
+ if (roleToTest == null)
+ roleToTest = role;
+
+ for (String r :_roles)
+ if (r.equals(roleToTest))
+ return true;
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ return DefaultUserIdentity.class.getSimpleName()+"('"+_userPrincipal+"')";
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $
+ */
+public class HashCrossContextPsuedoSession<T> implements CrossContextPsuedoSession<T>
+{
+ private final String _cookieName;
+
+ private final String _cookiePath;
+
+ private final Random _random = new SecureRandom();
+
+ private final Map<String, T> _data = new HashMap<String, T>();
+
+ public HashCrossContextPsuedoSession(String cookieName, String cookiePath)
+ {
+ this._cookieName = cookieName;
+ this._cookiePath = cookiePath == null ? "/" : cookiePath;
+ }
+
+ public T fetch(HttpServletRequest request)
+ {
+ Cookie[] cookies = request.getCookies();
+ if (cookies == null)
+ return null;
+
+ for (Cookie cookie : cookies)
+ {
+ if (_cookieName.equals(cookie.getName()))
+ {
+ String key = cookie.getValue();
+ return _data.get(key);
+ }
+ }
+ return null;
+ }
+
+ public void store(T datum, HttpServletResponse response)
+ {
+ String key;
+
+ synchronized (_data)
+ {
+ // Create new ID
+ while (true)
+ {
+ key = Long.toString(Math.abs(_random.nextLong()), 30 + (int) (System.currentTimeMillis() % 7));
+ if (!_data.containsKey(key)) break;
+ }
+
+ _data.put(key, datum);
+ }
+
+ Cookie cookie = new Cookie(_cookieName, key);
+ cookie.setPath(_cookiePath);
+ response.addCookie(cookie);
+ }
+
+ public void clear(HttpServletRequest request)
+ {
+ for (Cookie cookie : request.getCookies())
+ {
+ if (_cookieName.equals(cookie.getName()))
+ {
+ String key = cookie.getValue();
+ _data.remove(key);
+ break;
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.security.PropertyUserStore.UserListener;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * Properties User Realm.
+ *
+ * An implementation of UserRealm that stores users and roles in-memory in HashMaps.
+ * <P>
+ * Typically these maps are populated by calling the load() method or passing a properties resource to the constructor. The format of the properties file is:
+ *
+ * <PRE>
+ * username: password [,rolename ...]
+ * </PRE>
+ *
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ *
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class HashLoginService extends MappedLoginService implements UserListener
+{
+ private static final Logger LOG = Log.getLogger(HashLoginService.class);
+
+ private PropertyUserStore _propertyUserStore;
+ private String _config;
+ private Resource _configResource;
+ private Scanner _scanner;
+ private int _refreshInterval = 0;// default is not to reload
+
+ /* ------------------------------------------------------------ */
+ public HashLoginService()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public HashLoginService(String name)
+ {
+ setName(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ public HashLoginService(String name, String config)
+ {
+ setName(name);
+ setConfig(config);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getConfig()
+ {
+ return _config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void getConfig(String config)
+ {
+ _config = config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Resource getConfigResource()
+ {
+ return _configResource;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Load realm users from properties file. The property file maps usernames to password specs followed by an optional comma separated list of role names.
+ *
+ * @param config
+ * Filename or url of user properties file.
+ */
+ public void setConfig(String config)
+ {
+ _config = config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRefreshInterval(int msec)
+ {
+ _refreshInterval = msec;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getRefreshInterval()
+ {
+ return _refreshInterval;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected UserIdentity loadUser(String username)
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void loadUsers() throws IOException
+ {
+ // TODO: Consider refactoring MappedLoginService to not have to override with unused methods
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+
+ if (_propertyUserStore == null)
+ {
+ if(LOG.isDebugEnabled())
+ LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _config + " refreshInterval: " + _refreshInterval);
+
+ _propertyUserStore = new PropertyUserStore();
+ _propertyUserStore.setRefreshInterval(_refreshInterval);
+ _propertyUserStore.setConfig(_config);
+ _propertyUserStore.registerUserListener(this);
+ _propertyUserStore.start();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_scanner != null)
+ _scanner.stop();
+ _scanner = null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void update(String userName, Credential credential, String[] roleArray)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("update: " + userName + " Roles: " + roleArray.length);
+ putUser(userName,credential,roleArray);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void remove(String userName)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("remove: " + userName);
+ removeUser(userName);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+
+/* ------------------------------------------------------------ */
+/**
+ * Associates UserIdentities from with threads and UserIdentity.Contexts.
+ *
+ */
+public interface IdentityService
+{
+ final static String[] NO_ROLES = new String[]{};
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Associate a user identity with the current thread.
+ * This is called with as a thread enters the
+ * {@link SecurityHandler#handle(String, Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+ * method and then again with a null argument as that call exits.
+ * @param user The current user or null for no user to associated.
+ * @return an object representing the previous associated state
+ */
+ Object associate(UserIdentity user);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Disassociate the user identity from the current thread
+ * and restore previous identity.
+ * @param previous The opaque object returned from a call to {@link IdentityService#associate(UserIdentity)}
+ */
+ void disassociate(Object previous);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Associate a runas Token with the current user and thread.
+ * @param user The UserIdentity
+ * @param token The runAsToken to associate.
+ * @return The previous runAsToken or null.
+ */
+ Object setRunAs(UserIdentity user, RunAsToken token);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Disassociate the current runAsToken from the thread
+ * and reassociate the previous token.
+ * @param token RUNAS returned from previous associateRunAs call
+ */
+ void unsetRunAs(Object token);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new UserIdentity for use with this identity service.
+ * The UserIdentity should be immutable and able to be cached.
+ *
+ * @param subject Subject to include in UserIdentity
+ * @param userPrincipal Principal to include in UserIdentity. This will be returned from getUserPrincipal calls
+ * @param roles set of roles to include in UserIdentity.
+ * @return A new immutable UserIdententity
+ */
+ UserIdentity newUserIdentity(Subject subject, Principal userPrincipal, String[] roles);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new RunAsToken from a runAsName (normally a role).
+ * @param runAsName Normally a role name
+ * @return A new immutable RunAsToken
+ */
+ RunAsToken newRunAsToken(String runAsName);
+
+ /* ------------------------------------------------------------ */
+ UserIdentity getSystemUserIdentity();
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashMapped User Realm with JDBC as data source. JDBCLoginService extends
+ * HashULoginService and adds a method to fetch user information from database.
+ * The login() method checks the inherited Map for the user. If the user is not
+ * found, it will fetch details from the database and populate the inherited
+ * Map. It then calls the superclass login() method to perform the actual
+ * authentication. Periodically (controlled by configuration parameter),
+ * internal hashes are cleared. Caching can be disabled by setting cache refresh
+ * interval to zero. Uses one database connection that is initialized at
+ * startup. Reconnect on failures. authenticate() is 'synchronized'.
+ *
+ * An example properties file for configuration is in
+ * $JETTY_HOME/etc/jdbcRealm.properties
+ *
+ * @version $Id: JDBCLoginService.java 4792 2009-03-18 21:55:52Z gregw $
+ *
+ *
+ *
+ *
+ */
+
+public class JDBCLoginService extends MappedLoginService
+{
+ private static final Logger LOG = Log.getLogger(JDBCLoginService.class);
+
+ protected String _config;
+ protected String _jdbcDriver;
+ protected String _url;
+ protected String _userName;
+ protected String _password;
+ protected String _userTableKey;
+ protected String _userTablePasswordField;
+ protected String _roleTableRoleField;
+ protected int _cacheTime;
+ protected long _lastHashPurge;
+ protected Connection _con;
+ protected String _userSql;
+ protected String _roleSql;
+
+
+ /* ------------------------------------------------------------ */
+ public JDBCLoginService()
+ throws IOException
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public JDBCLoginService(String name)
+ throws IOException
+ {
+ setName(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ public JDBCLoginService(String name, String config)
+ throws IOException
+ {
+ setName(name);
+ setConfig(config);
+ }
+
+ /* ------------------------------------------------------------ */
+ public JDBCLoginService(String name, IdentityService identityService, String config)
+ throws IOException
+ {
+ setName(name);
+ setIdentityService(identityService);
+ setConfig(config);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.MappedLoginService#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ Properties properties = new Properties();
+ Resource resource = Resource.newResource(_config);
+ try (InputStream in = resource.getInputStream())
+ {
+ properties.load(in);
+ }
+ _jdbcDriver = properties.getProperty("jdbcdriver");
+ _url = properties.getProperty("url");
+ _userName = properties.getProperty("username");
+ _password = properties.getProperty("password");
+ String _userTable = properties.getProperty("usertable");
+ _userTableKey = properties.getProperty("usertablekey");
+ String _userTableUserField = properties.getProperty("usertableuserfield");
+ _userTablePasswordField = properties.getProperty("usertablepasswordfield");
+ String _roleTable = properties.getProperty("roletable");
+ String _roleTableKey = properties.getProperty("roletablekey");
+ _roleTableRoleField = properties.getProperty("roletablerolefield");
+ String _userRoleTable = properties.getProperty("userroletable");
+ String _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
+ String _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
+ _cacheTime = new Integer(properties.getProperty("cachetime"));
+
+ if (_jdbcDriver == null || _jdbcDriver.equals("")
+ || _url == null
+ || _url.equals("")
+ || _userName == null
+ || _userName.equals("")
+ || _password == null
+ || _cacheTime < 0)
+ {
+ LOG.warn("UserRealm " + getName() + " has not been properly configured");
+ }
+ _cacheTime *= 1000;
+ _lastHashPurge = 0;
+ _userSql = "select " + _userTableKey + "," + _userTablePasswordField + " from " + _userTable + " where " + _userTableUserField + " = ?";
+ _roleSql = "select r." + _roleTableRoleField
+ + " from "
+ + _roleTable
+ + " r, "
+ + _userRoleTable
+ + " u where u."
+ + _userRoleTableUserKey
+ + " = ?"
+ + " and r."
+ + _roleTableKey
+ + " = u."
+ + _userRoleTableRoleKey;
+
+ Loader.loadClass(this.getClass(), _jdbcDriver).newInstance();
+ super.doStart();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String getConfig()
+ {
+ return _config;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Load JDBC connection configuration from properties file.
+ *
+ * @param config Filename or url of user properties file.
+ */
+ public void setConfig(String config)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _config=config;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * (re)Connect to database with parameters setup by loadConfig()
+ */
+ public void connectDatabase()
+ {
+ try
+ {
+ Class.forName(_jdbcDriver);
+ _con = DriverManager.getConnection(_url, _userName, _password);
+ }
+ catch (SQLException e)
+ {
+ LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+ }
+ catch (ClassNotFoundException e)
+ {
+ LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public UserIdentity login(String username, Object credentials)
+ {
+ long now = System.currentTimeMillis();
+ if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
+ {
+ _users.clear();
+ _lastHashPurge = now;
+ closeConnection();
+ }
+
+ return super.login(username,credentials);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void loadUsers()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected UserIdentity loadUser(String username)
+ {
+ try
+ {
+ if (null == _con)
+ connectDatabase();
+
+ if (null == _con)
+ throw new SQLException("Can't connect to database");
+
+ try (PreparedStatement stat1 = _con.prepareStatement(_userSql))
+ {
+ stat1.setObject(1, username);
+ try (ResultSet rs1 = stat1.executeQuery())
+ {
+ if (rs1.next())
+ {
+ int key = rs1.getInt(_userTableKey);
+ String credentials = rs1.getString(_userTablePasswordField);
+ List<String> roles = new ArrayList<String>();
+
+ try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
+ {
+ stat2.setInt(1, key);
+ try (ResultSet rs2 = stat2.executeQuery())
+ {
+ while (rs2.next())
+ roles.add(rs2.getString(_roleTableRoleField));
+ }
+ }
+ return putUser(username, credentials, roles.toArray(new String[roles.size()]));
+ }
+ }
+ }
+ }
+ catch (SQLException e)
+ {
+ LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
+ closeConnection();
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected UserIdentity putUser (String username, String credentials, String[] roles)
+ {
+ return putUser(username, Credential.getCredential(credentials),roles);
+ }
+
+
+ /**
+ * Close an existing connection
+ */
+ private void closeConnection ()
+ {
+ if (_con != null)
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("Closing db connection for JDBCUserRealm");
+ try { _con.close(); }catch (Exception e) {LOG.ignore(e);}
+ }
+ _con = null;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Login Service Interface.
+ * <p>
+ * The Login service provides an abstract mechanism for an {@link Authenticator}
+ * to check credentials and to create a {@link UserIdentity} using the
+ * set {@link IdentityService}.
+ */
+public interface LoginService
+{
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Get the name of the login service (aka Realm name)
+ */
+ String getName();
+
+ /* ------------------------------------------------------------ */
+ /** Login a user.
+ * @param username The user name
+ * @param credentials The users credentials
+ * @return A UserIdentity if the credentials matched, otherwise null
+ */
+ UserIdentity login(String username,Object credentials);
+
+ /* ------------------------------------------------------------ */
+ /** Validate a user identity.
+ * Validate that a UserIdentity previously created by a call
+ * to {@link #login(String, Object)} is still valid.
+ * @param user The user to validate
+ * @return true if authentication has not been revoked for the user.
+ */
+ boolean validate(UserIdentity user);
+
+ /* ------------------------------------------------------------ */
+ /** Get the IdentityService associated with this Login Service.
+ * @return the IdentityService associated with this Login Service.
+ */
+ IdentityService getIdentityService();
+
+ /* ------------------------------------------------------------ */
+ /** Set the IdentityService associated with this Login Service.
+ * @param service the IdentityService associated with this Login Service.
+ */
+ void setIdentityService(IdentityService service);
+
+ void logout(UserIdentity user);
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Credential;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * A login service that keeps UserIdentities in a concurrent map
+ * either as the source or a cache of the users.
+ *
+ */
+public abstract class MappedLoginService extends AbstractLifeCycle implements LoginService
+{
+ private static final Logger LOG = Log.getLogger(MappedLoginService.class);
+
+ protected IdentityService _identityService=new DefaultIdentityService();
+ protected String _name;
+ protected final ConcurrentMap<String, UserIdentity> _users=new ConcurrentHashMap<String, UserIdentity>();
+
+ /* ------------------------------------------------------------ */
+ protected MappedLoginService()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the name.
+ * @return the name
+ */
+ public String getName()
+ {
+ return _name;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the identityService.
+ * @return the identityService
+ */
+ public IdentityService getIdentityService()
+ {
+ return _identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the users.
+ * @return the users
+ */
+ public ConcurrentMap<String, UserIdentity> getUsers()
+ {
+ return _users;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the identityService.
+ * @param identityService the identityService to set
+ */
+ public void setIdentityService(IdentityService identityService)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _identityService = identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the name.
+ * @param name the name to set
+ */
+ public void setName(String name)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _name = name;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the users.
+ * @param users the users to set
+ */
+ public void setUsers(Map<String, UserIdentity> users)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _users.clear();
+ _users.putAll(users);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ loadUsers();
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void logout(UserIdentity identity)
+ {
+ LOG.debug("logout {}",identity);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return this.getClass().getSimpleName()+"["+_name+"]";
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Put user into realm.
+ * Called by implementations to put the user data loaded from
+ * file/db etc into the user structure.
+ * @param userName User name
+ * @param info a UserIdentity instance, or a String password or Credential instance
+ * @return User instance
+ */
+ protected synchronized UserIdentity putUser(String userName, Object info)
+ {
+ final UserIdentity identity;
+ if (info instanceof UserIdentity)
+ identity=(UserIdentity)info;
+ else
+ {
+ Credential credential = (info instanceof Credential)?(Credential)info:Credential.getCredential(info.toString());
+
+ Principal userPrincipal = new KnownUser(userName,credential);
+ Subject subject = new Subject();
+ subject.getPrincipals().add(userPrincipal);
+ subject.getPrivateCredentials().add(credential);
+ subject.setReadOnly();
+ identity=_identityService.newUserIdentity(subject,userPrincipal,IdentityService.NO_ROLES);
+ }
+
+ _users.put(userName,identity);
+ return identity;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Put user into realm.
+ * @param userName The user to add
+ * @param credential The users Credentials
+ * @param roles The users roles
+ * @return UserIdentity
+ */
+ public synchronized UserIdentity putUser(String userName, Credential credential, String[] roles)
+ {
+ Principal userPrincipal = new KnownUser(userName,credential);
+ Subject subject = new Subject();
+ subject.getPrincipals().add(userPrincipal);
+ subject.getPrivateCredentials().add(credential);
+
+ if (roles!=null)
+ for (String role : roles)
+ subject.getPrincipals().add(new RolePrincipal(role));
+
+ subject.setReadOnly();
+ UserIdentity identity=_identityService.newUserIdentity(subject,userPrincipal,roles);
+ _users.put(userName,identity);
+ return identity;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeUser(String username)
+ {
+ _users.remove(username);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.LoginService#login(java.lang.String, java.lang.Object)
+ */
+ public UserIdentity login(String username, Object credentials)
+ {
+ if (username == null)
+ return null;
+
+ UserIdentity user = _users.get(username);
+
+ if (user==null)
+ user = loadUser(username);
+
+ if (user!=null)
+ {
+ UserPrincipal principal = (UserPrincipal)user.getUserPrincipal();
+ if (principal.authenticate(credentials))
+ return user;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean validate(UserIdentity user)
+ {
+ if (_users.containsKey(user.getUserPrincipal().getName()))
+ return true;
+
+ if (loadUser(user.getUserPrincipal().getName())!=null)
+ return true;
+
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected abstract UserIdentity loadUser(String username);
+
+ /* ------------------------------------------------------------ */
+ protected abstract void loadUsers() throws IOException;
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public interface UserPrincipal extends Principal,Serializable
+ {
+ boolean authenticate(Object credentials);
+ public boolean isAuthenticated();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class RolePrincipal implements Principal,Serializable
+ {
+ private static final long serialVersionUID = 2998397924051854402L;
+ private final String _roleName;
+ public RolePrincipal(String name)
+ {
+ _roleName=name;
+ }
+ public String getName()
+ {
+ return _roleName;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class Anonymous implements UserPrincipal,Serializable
+ {
+ private static final long serialVersionUID = 1097640442553284845L;
+
+ public boolean isAuthenticated()
+ {
+ return false;
+ }
+
+ public String getName()
+ {
+ return "Anonymous";
+ }
+
+ public boolean authenticate(Object credentials)
+ {
+ return false;
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class KnownUser implements UserPrincipal,Serializable
+ {
+ private static final long serialVersionUID = -6226920753748399662L;
+ private final String _name;
+ private final Credential _credential;
+
+ /* -------------------------------------------------------- */
+ public KnownUser(String name,Credential credential)
+ {
+ _name=name;
+ _credential=credential;
+ }
+
+ /* -------------------------------------------------------- */
+ public boolean authenticate(Object credentials)
+ {
+ return _credential!=null && _credential.check(credentials);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getName()
+ {
+ return _name;
+ }
+
+ /* -------------------------------------------------------- */
+ public boolean isAuthenticated()
+ {
+ return true;
+ }
+
+ /* -------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return _name;
+ }
+ }
+}
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.MappedLoginService.KnownUser;
+import org.eclipse.jetty.security.MappedLoginService.RolePrincipal;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.Scanner.BulkListener;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * PropertyUserStore
+ *
+ * This class monitors a property file of the format mentioned below and notifies registered listeners of the changes to the the given file.
+ *
+ * <PRE>
+ * username: password [,rolename ...]
+ * </PRE>
+ *
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ *
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class PropertyUserStore extends AbstractLifeCycle
+{
+ private static final Logger LOG = Log.getLogger(PropertyUserStore.class);
+
+ private String _config;
+ private Resource _configResource;
+ private Scanner _scanner;
+ private int _refreshInterval = 0;// default is not to reload
+
+ private IdentityService _identityService = new DefaultIdentityService();
+ private boolean _firstLoad = true; // true if first load, false from that point on
+ private final List<String> _knownUsers = new ArrayList<String>();
+ private final Map<String, UserIdentity> _knownUserIdentities = new HashMap<String, UserIdentity>();
+ private List<UserListener> _listeners;
+
+ /* ------------------------------------------------------------ */
+ public String getConfig()
+ {
+ return _config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setConfig(String config)
+ {
+ _config = config;
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity getUserIdentity(String userName)
+ {
+ return _knownUserIdentities.get(userName);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * returns the resource associated with the configured properties file, creating it if necessary
+ */
+ public Resource getConfigResource() throws IOException
+ {
+ if (_configResource == null)
+ {
+ _configResource = Resource.newResource(_config);
+ }
+
+ return _configResource;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * sets the refresh interval (in seconds)
+ */
+ public void setRefreshInterval(int msec)
+ {
+ _refreshInterval = msec;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * refresh interval in seconds for how often the properties file should be checked for changes
+ */
+ public int getRefreshInterval()
+ {
+ return _refreshInterval;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void loadUsers() throws IOException
+ {
+ if (_config == null)
+ return;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Load " + this + " from " + _config);
+ Properties properties = new Properties();
+ if (getConfigResource().exists())
+ properties.load(getConfigResource().getInputStream());
+ Set<String> known = new HashSet<String>();
+
+ for (Map.Entry<Object, Object> entry : properties.entrySet())
+ {
+ String username = ((String)entry.getKey()).trim();
+ String credentials = ((String)entry.getValue()).trim();
+ String roles = null;
+ int c = credentials.indexOf(',');
+ if (c > 0)
+ {
+ roles = credentials.substring(c + 1).trim();
+ credentials = credentials.substring(0,c).trim();
+ }
+
+ if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0)
+ {
+ String[] roleArray = IdentityService.NO_ROLES;
+ if (roles != null && roles.length() > 0)
+ {
+ roleArray = roles.split(",");
+ }
+ known.add(username);
+ Credential credential = Credential.getCredential(credentials);
+
+ Principal userPrincipal = new KnownUser(username,credential);
+ Subject subject = new Subject();
+ subject.getPrincipals().add(userPrincipal);
+ subject.getPrivateCredentials().add(credential);
+
+ if (roles != null)
+ {
+ for (String role : roleArray)
+ {
+ subject.getPrincipals().add(new RolePrincipal(role));
+ }
+ }
+
+ subject.setReadOnly();
+
+ _knownUserIdentities.put(username,_identityService.newUserIdentity(subject,userPrincipal,roleArray));
+ notifyUpdate(username,credential,roleArray);
+ }
+ }
+
+ synchronized (_knownUsers)
+ {
+ /*
+ * if its not the initial load then we want to process removed users
+ */
+ if (!_firstLoad)
+ {
+ Iterator<String> users = _knownUsers.iterator();
+ while (users.hasNext())
+ {
+ String user = users.next();
+ if (!known.contains(user))
+ {
+ _knownUserIdentities.remove(user);
+ notifyRemove(user);
+ }
+ }
+ }
+
+ /*
+ * reset the tracked _users list to the known users we just processed
+ */
+
+ _knownUsers.clear();
+ _knownUsers.addAll(known);
+
+ }
+
+ /*
+ * set initial load to false as there should be no more initial loads
+ */
+ _firstLoad = false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Depending on the value of the refresh interval, this method will either start up a scanner thread that will monitor the properties file for changes after
+ * it has initially loaded it. Otherwise the users will be loaded and there will be no active monitoring thread so changes will not be detected.
+ *
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+
+ if (getRefreshInterval() > 0)
+ {
+ _scanner = new Scanner();
+ _scanner.setScanInterval(getRefreshInterval());
+ List<File> dirList = new ArrayList<File>(1);
+ dirList.add(getConfigResource().getFile().getParentFile());
+ _scanner.setScanDirs(dirList);
+ _scanner.setFilenameFilter(new FilenameFilter()
+ {
+ public boolean accept(File dir, String name)
+ {
+ File f = new File(dir,name);
+ try
+ {
+ if (f.compareTo(getConfigResource().getFile()) == 0)
+ {
+ return true;
+ }
+ }
+ catch (IOException e)
+ {
+ return false;
+ }
+
+ return false;
+ }
+
+ });
+
+ _scanner.addListener(new BulkListener()
+ {
+ public void filesChanged(List<String> filenames) throws Exception
+ {
+ if (filenames == null)
+ return;
+ if (filenames.isEmpty())
+ return;
+ if (filenames.size() == 1)
+ {
+ Resource r = Resource.newResource(filenames.get(0));
+ if (r.getFile().equals(_configResource.getFile()))
+ loadUsers();
+ }
+ }
+
+ public String toString()
+ {
+ return "PropertyUserStore$Scanner";
+ }
+
+ });
+
+ _scanner.setReportExistingFilesOnStartup(true);
+ _scanner.setRecursive(false);
+ _scanner.start();
+ }
+ else
+ {
+ loadUsers();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_scanner != null)
+ _scanner.stop();
+ _scanner = null;
+ }
+
+ /**
+ * Notifies the registered listeners of potential updates to a user
+ *
+ * @param username
+ * @param credential
+ * @param roleArray
+ */
+ private void notifyUpdate(String username, Credential credential, String[] roleArray)
+ {
+ if (_listeners != null)
+ {
+ for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
+ {
+ i.next().update(username,credential,roleArray);
+ }
+ }
+ }
+
+ /**
+ * notifies the registered listeners that a user has been removed.
+ *
+ * @param username
+ */
+ private void notifyRemove(String username)
+ {
+ if (_listeners != null)
+ {
+ for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
+ {
+ i.next().remove(username);
+ }
+ }
+ }
+
+ /**
+ * registers a listener to be notified of the contents of the property file
+ */
+ public void registerUserListener(UserListener listener)
+ {
+ if (_listeners == null)
+ {
+ _listeners = new ArrayList<UserListener>();
+ }
+ _listeners.add(listener);
+ }
+
+ /**
+ * UserListener
+ */
+ public interface UserListener
+ {
+ public void update(String username, Credential credential, String[] roleArray);
+
+ public void remove(String username);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * RoleInfo
+ *
+ * Badly named class that holds the role and user data constraint info for a
+ * path/http method combination, extracted and combined from security
+ * constraints.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class RoleInfo
+{
+ private boolean _isAnyAuth;
+ private boolean _isAnyRole;
+ private boolean _checked;
+ private boolean _forbidden;
+ private UserDataConstraint _userDataConstraint;
+
+ /**
+ * List of permitted roles
+ */
+ private final Set<String> _roles = new CopyOnWriteArraySet<String>();
+
+ public RoleInfo()
+ {
+ }
+
+ public boolean isChecked()
+ {
+ return _checked;
+ }
+
+ public void setChecked(boolean checked)
+ {
+ this._checked = checked;
+ if (!checked)
+ {
+ _forbidden=false;
+ _roles.clear();
+ _isAnyRole=false;
+ _isAnyAuth=false;
+ }
+ }
+
+ public boolean isForbidden()
+ {
+ return _forbidden;
+ }
+
+ public void setForbidden(boolean forbidden)
+ {
+ this._forbidden = forbidden;
+ if (forbidden)
+ {
+ _checked = true;
+ _userDataConstraint = null;
+ _isAnyRole=false;
+ _isAnyAuth=false;
+ _roles.clear();
+ }
+ }
+
+ public boolean isAnyRole()
+ {
+ return _isAnyRole;
+ }
+
+ public void setAnyRole(boolean anyRole)
+ {
+ this._isAnyRole=anyRole;
+ if (anyRole)
+ _checked = true;
+ }
+
+ public boolean isAnyAuth ()
+ {
+ return _isAnyAuth;
+ }
+
+ public void setAnyAuth(boolean anyAuth)
+ {
+ this._isAnyAuth=anyAuth;
+ if (anyAuth)
+ _checked = true;
+ }
+
+ public UserDataConstraint getUserDataConstraint()
+ {
+ return _userDataConstraint;
+ }
+
+ public void setUserDataConstraint(UserDataConstraint userDataConstraint)
+ {
+ if (userDataConstraint == null) throw new NullPointerException("Null UserDataConstraint");
+ if (this._userDataConstraint == null)
+ {
+
+ this._userDataConstraint = userDataConstraint;
+ }
+ else
+ {
+ this._userDataConstraint = this._userDataConstraint.combine(userDataConstraint);
+ }
+ }
+
+ public Set<String> getRoles()
+ {
+ return _roles;
+ }
+
+ public void addRole(String role)
+ {
+ _roles.add(role);
+ }
+
+ public void combine(RoleInfo other)
+ {
+ if (other._forbidden)
+ setForbidden(true);
+ else if (!other._checked) // TODO is this the right way around???
+ setChecked(true);
+ else if (other._isAnyRole)
+ setAnyRole(true);
+ else if (other._isAnyAuth)
+ setAnyAuth(true);
+ else if (!_isAnyRole)
+ {
+ for (String r : other._roles)
+ _roles.add(r);
+ }
+
+ setUserDataConstraint(other._userDataConstraint);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "{RoleInfo"+(_forbidden?",F":"")+(_checked?",C":"")+(_isAnyRole?",*":_roles)+(_userDataConstraint!=null?","+_userDataConstraint:"")+"}";
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+
+
+/**
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public class RoleRunAsToken implements RunAsToken
+{
+ private final String _runAsRole;
+
+ public RoleRunAsToken(String runAsRole)
+ {
+ this._runAsRole = runAsRole;
+ }
+
+ public String getRunAsRole()
+ {
+ return _runAsRole;
+ }
+
+ public String toString()
+ {
+ return "RoleRunAsToken("+_runAsRole+")";
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+/**
+ * marker interface for run-as-role tokens
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public interface RunAsToken
+{
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.security.authentication.DeferredAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Abstract SecurityHandler.
+ * Select and apply an {@link Authenticator} to a request.
+ * <p>
+ * The Authenticator may either be directly set on the handler
+ * or will be create during {@link #start()} with a call to
+ * either the default or set AuthenticatorFactory.
+ * <p>
+ * SecurityHandler has a set of initparameters that are used by the
+ * Authentication.Configuration. At startup, any context init parameters
+ * that start with "org.eclipse.jetty.security." that do not have
+ * values in the SecurityHandler init parameters, are copied.
+ *
+ */
+public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
+{
+ private static final Logger LOG = Log.getLogger(SecurityHandler.class);
+
+ /* ------------------------------------------------------------ */
+ private boolean _checkWelcomeFiles = false;
+ private Authenticator _authenticator;
+ private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory();
+ private String _realmName;
+ private String _authMethod;
+ private final Map<String,String> _initParameters=new HashMap<String,String>();
+ private LoginService _loginService;
+ private IdentityService _identityService;
+ private boolean _renewSession=true;
+
+ /* ------------------------------------------------------------ */
+ protected SecurityHandler()
+ {
+ addBean(_authenticatorFactory);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the identityService.
+ * @return the identityService
+ */
+ @Override
+ public IdentityService getIdentityService()
+ {
+ return _identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the identityService.
+ * @param identityService the identityService to set
+ */
+ public void setIdentityService(IdentityService identityService)
+ {
+ if (isStarted())
+ throw new IllegalStateException("Started");
+ updateBean(_identityService,identityService);
+ _identityService = identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the loginService.
+ * @return the loginService
+ */
+ @Override
+ public LoginService getLoginService()
+ {
+ return _loginService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the loginService.
+ * @param loginService the loginService to set
+ */
+ public void setLoginService(LoginService loginService)
+ {
+ if (isStarted())
+ throw new IllegalStateException("Started");
+ updateBean(_loginService,loginService);
+ _loginService = loginService;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public Authenticator getAuthenticator()
+ {
+ return _authenticator;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the authenticator.
+ * @param authenticator
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setAuthenticator(Authenticator authenticator)
+ {
+ if (isStarted())
+ throw new IllegalStateException("Started");
+ updateBean(_authenticator,authenticator);
+ _authenticator = authenticator;
+ if (_authenticator!=null)
+ _authMethod=_authenticator.getAuthMethod();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the authenticatorFactory
+ */
+ public Authenticator.Factory getAuthenticatorFactory()
+ {
+ return _authenticatorFactory;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param authenticatorFactory the authenticatorFactory to set
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ updateBean(_authenticatorFactory,authenticatorFactory);
+ _authenticatorFactory = authenticatorFactory;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the realmName
+ */
+ @Override
+ public String getRealmName()
+ {
+ return _realmName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param realmName the realmName to set
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setRealmName(String realmName)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ _realmName = realmName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the authMethod
+ */
+ @Override
+ public String getAuthMethod()
+ {
+ return _authMethod;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param authMethod the authMethod to set
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setAuthMethod(String authMethod)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ _authMethod = authMethod;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if forwards to welcome files are authenticated
+ */
+ public boolean isCheckWelcomeFiles()
+ {
+ return _checkWelcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param authenticateWelcomeFiles True if forwards to welcome files are
+ * authenticated
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ _checkWelcomeFiles = authenticateWelcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getInitParameter(String key)
+ {
+ return _initParameters.get(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Set<String> getInitParameterNames()
+ {
+ return _initParameters.keySet();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set an initialization parameter.
+ * @param key
+ * @param value
+ * @return previous value
+ * @throws IllegalStateException if the SecurityHandler is running
+ */
+ public String setInitParameter(String key, String value)
+ {
+ if (isRunning())
+ throw new IllegalStateException("running");
+ return _initParameters.put(key,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected LoginService findLoginService() throws Exception
+ {
+ Collection<LoginService> list = getServer().getBeans(LoginService.class);
+ LoginService service = null;
+ String realm=getRealmName();
+ if (realm!=null)
+ {
+ for (LoginService s : list)
+ if (s.getName()!=null && s.getName().equals(realm))
+ {
+ service=s;
+ break;
+ }
+ }
+ else if (list.size()==1)
+ service = list.iterator().next();
+
+ return service;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected IdentityService findIdentityService()
+ {
+ return getServer().getBean(IdentityService.class);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ @Override
+ protected void doStart()
+ throws Exception
+ {
+ // copy security init parameters
+ ContextHandler.Context context =ContextHandler.getCurrentContext();
+ if (context!=null)
+ {
+ Enumeration<String> names=context.getInitParameterNames();
+ while (names!=null && names.hasMoreElements())
+ {
+ String name =names.nextElement();
+ if (name.startsWith("org.eclipse.jetty.security.") &&
+ getInitParameter(name)==null)
+ setInitParameter(name,context.getInitParameter(name));
+ }
+
+ //register a session listener to handle securing sessions when authentication is performed
+ context.getContextHandler().addEventListener(new HttpSessionListener()
+ {
+ @Override
+ public void sessionDestroyed(HttpSessionEvent se)
+ {
+ }
+
+ @Override
+ public void sessionCreated(HttpSessionEvent se)
+ {
+ //if current request is authenticated, then as we have just created the session, mark it as secure, as it has not yet been returned to a user
+ HttpChannel<?> channel = HttpChannel.getCurrentHttpChannel();
+
+ if (channel == null)
+ return;
+ Request request = channel.getRequest();
+ if (request == null)
+ return;
+
+ if (request.isSecure())
+ {
+ se.getSession().setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+ }
+ }
+ });
+ }
+
+ // complicated resolution of login and identity service to handle
+ // many different ways these can be constructed and injected.
+
+ if (_loginService==null)
+ {
+ setLoginService(findLoginService());
+ if (_loginService!=null)
+ unmanage(_loginService);
+ }
+
+ if (_identityService==null)
+ {
+ if (_loginService!=null)
+ setIdentityService(_loginService.getIdentityService());
+
+ if (_identityService==null)
+ setIdentityService(findIdentityService());
+
+ if (_identityService==null)
+ {
+ if (_realmName!=null)
+ {
+ setIdentityService(new DefaultIdentityService());
+ manage(_identityService);
+ }
+ }
+ else
+ unmanage(_identityService);
+ }
+
+ if (_loginService!=null)
+ {
+ if (_loginService.getIdentityService()==null)
+ _loginService.setIdentityService(_identityService);
+ else if (_loginService.getIdentityService()!=_identityService)
+ throw new IllegalStateException("LoginService has different IdentityService to "+this);
+ }
+
+ Authenticator.Factory authenticatorFactory = getAuthenticatorFactory();
+ if (_authenticator==null && authenticatorFactory!=null && _identityService!=null)
+ setAuthenticator(authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService));
+
+ if (_authenticator!=null)
+ _authenticator.setConfiguration(this);
+ else if (_realmName!=null)
+ {
+ LOG.warn("No Authenticator for "+this);
+ throw new IllegalStateException("No Authenticator");
+ }
+
+ super.doStart();
+ }
+
+ @Override
+ /* ------------------------------------------------------------ */
+ protected void doStop() throws Exception
+ {
+ //if we discovered the services (rather than had them explicitly configured), remove them.
+ if (!isManaged(_identityService))
+ {
+ removeBean(_identityService);
+ _identityService = null;
+ }
+
+ if (!isManaged(_loginService))
+ {
+ removeBean(_loginService);
+ _loginService=null;
+ }
+
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected boolean checkSecurity(Request request)
+ {
+ switch(request.getDispatcherType())
+ {
+ case REQUEST:
+ case ASYNC:
+ return true;
+ case FORWARD:
+ if (isCheckWelcomeFiles() && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
+ {
+ request.removeAttribute("org.eclipse.jetty.server.welcome");
+ return true;
+ }
+ return false;
+ default:
+ return false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+ */
+ @Override
+ public boolean isSessionRenewedOnAuthentication()
+ {
+ return _renewSession;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set renew the session on Authentication.
+ * <p>
+ * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
+ * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+ */
+ public void setSessionRenewedOnAuthentication(boolean renew)
+ {
+ _renewSession=renew;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
+ * javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ final Response base_response = baseRequest.getResponse();
+ final Handler handler=getHandler();
+
+ if (handler==null)
+ return;
+
+ final Authenticator authenticator = _authenticator;
+
+ if (checkSecurity(baseRequest))
+ {
+ //See Servlet Spec 3.1 sec 13.6.3
+ if (authenticator != null)
+ authenticator.prepareRequest(baseRequest);
+
+ RoleInfo roleInfo = prepareConstraintInfo(pathInContext, baseRequest);
+
+ // Check data constraints
+ if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, roleInfo))
+ {
+ if (!baseRequest.isHandled())
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ baseRequest.setHandled(true);
+ }
+ return;
+ }
+
+ // is Auth mandatory?
+ boolean isAuthMandatory =
+ isAuthMandatory(baseRequest, base_response, roleInfo);
+
+ if (isAuthMandatory && authenticator==null)
+ {
+ LOG.warn("No authenticator for: "+roleInfo);
+ if (!baseRequest.isHandled())
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ baseRequest.setHandled(true);
+ }
+ return;
+ }
+
+ // check authentication
+ Object previousIdentity = null;
+ try
+ {
+ Authentication authentication = baseRequest.getAuthentication();
+ if (authentication==null || authentication==Authentication.NOT_CHECKED)
+ authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
+
+ if (authentication instanceof Authentication.Wrapped)
+ {
+ request=((Authentication.Wrapped)authentication).getHttpServletRequest();
+ response=((Authentication.Wrapped)authentication).getHttpServletResponse();
+ }
+
+ if (authentication instanceof Authentication.ResponseSent)
+ {
+ baseRequest.setHandled(true);
+ }
+ else if (authentication instanceof Authentication.User)
+ {
+ Authentication.User userAuth = (Authentication.User)authentication;
+ baseRequest.setAuthentication(authentication);
+ if (_identityService!=null)
+ previousIdentity = _identityService.associate(userAuth.getUserIdentity());
+
+ if (isAuthMandatory)
+ {
+ boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity());
+ if (!authorized)
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role");
+ baseRequest.setHandled(true);
+ return;
+ }
+ }
+
+ handler.handle(pathInContext, baseRequest, request, response);
+ if (authenticator!=null)
+ authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+ }
+ else if (authentication instanceof Authentication.Deferred)
+ {
+ DeferredAuthentication deferred= (DeferredAuthentication)authentication;
+ baseRequest.setAuthentication(authentication);
+
+ try
+ {
+ handler.handle(pathInContext, baseRequest, request, response);
+ }
+ finally
+ {
+ previousIdentity = deferred.getPreviousAssociation();
+ }
+
+ if (authenticator!=null)
+ {
+ Authentication auth=baseRequest.getAuthentication();
+ if (auth instanceof Authentication.User)
+ {
+ Authentication.User userAuth = (Authentication.User)auth;
+ authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+ }
+ else
+ authenticator.secureResponse(request, response, isAuthMandatory, null);
+ }
+ }
+ else
+ {
+ baseRequest.setAuthentication(authentication);
+ if (_identityService!=null)
+ previousIdentity = _identityService.associate(null);
+ handler.handle(pathInContext, baseRequest, request, response);
+ if (authenticator!=null)
+ authenticator.secureResponse(request, response, isAuthMandatory, null);
+ }
+ }
+ catch (ServerAuthException e)
+ {
+ // jaspi 3.8.3 send HTTP 500 internal server error, with message
+ // from AuthException
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
+ }
+ finally
+ {
+ if (_identityService!=null)
+ _identityService.disassociate(previousIdentity);
+ }
+ }
+ else
+ handler.handle(pathInContext, baseRequest, request, response);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static SecurityHandler getCurrentSecurityHandler()
+ {
+ Context context = ContextHandler.getCurrentContext();
+ if (context==null)
+ return null;
+
+ return context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void logout(Authentication.User user)
+ {
+ LOG.debug("logout {}",user);
+ LoginService login_service=getLoginService();
+ if (login_service!=null)
+ {
+ login_service.logout(user.getUserIdentity());
+ }
+
+ IdentityService identity_service=getIdentityService();
+ if (identity_service!=null)
+ {
+ // TODO recover previous from threadlocal (or similar)
+ Object previous=null;
+ identity_service.disassociate(previous);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected abstract RoleInfo prepareConstraintInfo(String pathInContext, Request request);
+
+ /* ------------------------------------------------------------ */
+ protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, RoleInfo constraintInfo) throws IOException;
+
+ /* ------------------------------------------------------------ */
+ protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
+
+ /* ------------------------------------------------------------ */
+ protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
+ UserIdentity userIdentity) throws IOException;
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class NotChecked implements Principal
+ {
+ @Override
+ public String getName()
+ {
+ return null;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "NOT CHECKED";
+ }
+
+ public SecurityHandler getSecurityHandler()
+ {
+ return SecurityHandler.this;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static final Principal __NO_USER = new Principal()
+ {
+ @Override
+ public String getName()
+ {
+ return null;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "No User";
+ }
+ };
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
+ * of authentication. A request with a Nobody UserPrincipal will be allowed
+ * past all authentication constraints - but will not be considered an
+ * authenticated request. It can be used by Authenticators such as
+ * FormAuthenticator to allow access to logon and error pages within an
+ * authenticated URI tree.
+ */
+ public static final Principal __NOBODY = new Principal()
+ {
+ @Override
+ public String getName()
+ {
+ return "Nobody";
+ }
+
+ @Override
+ public String toString()
+ {
+ return getName();
+ }
+ };
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public class ServerAuthException extends GeneralSecurityException
+{
+
+ public ServerAuthException()
+ {
+ }
+
+ public ServerAuthException(String s)
+ {
+ super(s);
+ }
+
+ public ServerAuthException(String s, Throwable throwable)
+ {
+ super(s, throwable);
+ }
+
+ public ServerAuthException(Throwable throwable)
+ {
+ super(throwable);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.util.Properties;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+public class SpnegoLoginService extends AbstractLifeCycle implements LoginService
+{
+ private static final Logger LOG = Log.getLogger(SpnegoLoginService.class);
+
+ protected IdentityService _identityService;// = new LdapIdentityService();
+ protected String _name;
+ private String _config;
+
+ private String _targetName;
+
+ public SpnegoLoginService()
+ {
+
+ }
+
+ public SpnegoLoginService( String name )
+ {
+ setName(name);
+ }
+
+ public SpnegoLoginService( String name, String config )
+ {
+ setName(name);
+ setConfig(config);
+ }
+
+ @Override
+ public String getName()
+ {
+ return _name;
+ }
+
+ public void setName(String name)
+ {
+ if (isRunning())
+ {
+ throw new IllegalStateException("Running");
+ }
+
+ _name = name;
+ }
+
+ public String getConfig()
+ {
+ return _config;
+ }
+
+ public void setConfig( String config )
+ {
+ if (isRunning())
+ {
+ throw new IllegalStateException("Running");
+ }
+
+ _config = config;
+ }
+
+
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ Properties properties = new Properties();
+ Resource resource = Resource.newResource(_config);
+ properties.load(resource.getInputStream());
+
+ _targetName = properties.getProperty("targetName");
+
+ LOG.debug("Target Name {}", _targetName);
+
+ super.doStart();
+ }
+
+ /**
+ * username will be null since the credentials will contain all the relevant info
+ */
+ @Override
+ public UserIdentity login(String username, Object credentials)
+ {
+ String encodedAuthToken = (String)credentials;
+
+ byte[] authToken = B64Code.decode(encodedAuthToken);
+
+ GSSManager manager = GSSManager.getInstance();
+ try
+ {
+ Oid krb5Oid = new Oid("1.3.6.1.5.5.2"); // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
+ GSSName gssName = manager.createName(_targetName,null);
+ GSSCredential serverCreds = manager.createCredential(gssName,GSSCredential.INDEFINITE_LIFETIME,krb5Oid,GSSCredential.ACCEPT_ONLY);
+ GSSContext gContext = manager.createContext(serverCreds);
+
+ if (gContext == null)
+ {
+ LOG.debug("SpnegoUserRealm: failed to establish GSSContext");
+ }
+ else
+ {
+ while (!gContext.isEstablished())
+ {
+ authToken = gContext.acceptSecContext(authToken,0,authToken.length);
+ }
+ if (gContext.isEstablished())
+ {
+ String clientName = gContext.getSrcName().toString();
+ String role = clientName.substring(clientName.indexOf('@') + 1);
+
+ LOG.debug("SpnegoUserRealm: established a security context");
+ LOG.debug("Client Principal is: " + gContext.getSrcName());
+ LOG.debug("Server Principal is: " + gContext.getTargName());
+ LOG.debug("Client Default Role: " + role);
+
+ SpnegoUserPrincipal user = new SpnegoUserPrincipal(clientName,authToken);
+
+ Subject subject = new Subject();
+ subject.getPrincipals().add(user);
+
+ return _identityService.newUserIdentity(subject,user, new String[]{role});
+ }
+ }
+
+ }
+ catch (GSSException gsse)
+ {
+ LOG.warn(gsse);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean validate(UserIdentity user)
+ {
+ return false;
+ }
+
+ @Override
+ public IdentityService getIdentityService()
+ {
+ return _identityService;
+ }
+
+ @Override
+ public void setIdentityService(IdentityService service)
+ {
+ _identityService = service;
+ }
+
+ @Override
+ public void logout(UserIdentity user)
+ {
+ // TODO Auto-generated method stub
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.security.Principal;
+import java.util.List;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+public class SpnegoUserIdentity implements UserIdentity
+{
+ private Subject _subject;
+ private Principal _principal;
+ private List<String> _roles;
+
+ public SpnegoUserIdentity( Subject subject, Principal principal, List<String> roles )
+ {
+ _subject = subject;
+ _principal = principal;
+ _roles = roles;
+ }
+
+
+ public Subject getSubject()
+ {
+ return _subject;
+ }
+
+ public Principal getUserPrincipal()
+ {
+ return _principal;
+ }
+
+ public boolean isUserInRole(String role, Scope scope)
+ {
+ return _roles.contains(role);
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import java.security.Principal;
+
+import org.eclipse.jetty.util.B64Code;
+
+public class SpnegoUserPrincipal implements Principal
+{
+ private final String _name;
+ private byte[] _token;
+ private String _encodedToken;
+
+ public SpnegoUserPrincipal( String name, String encodedToken )
+ {
+ _name = name;
+ _encodedToken = encodedToken;
+ }
+
+ public SpnegoUserPrincipal( String name, byte[] token )
+ {
+ _name = name;
+ _token = token;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public byte[] getToken()
+ {
+ if ( _token == null )
+ {
+ _token = B64Code.decode(_encodedToken);
+ }
+ return _token;
+ }
+
+ public String getEncodedToken()
+ {
+ if ( _encodedToken == null )
+ {
+ _encodedToken = new String(B64Code.encode(_token,true));
+ }
+ return _encodedToken;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class UserAuthentication extends AbstractUserAuthentication
+{
+
+ public UserAuthentication(String method, UserIdentity userIdentity)
+ {
+ super(method, userIdentity);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return "{User,"+getAuthMethod()+","+_userIdentity+"}";
+ }
+
+ public void logout()
+ {
+ SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+ if (security!=null)
+ security.logout(this);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public enum UserDataConstraint
+{
+ None, Integral, Confidential;
+
+ public static UserDataConstraint get(int dataConstraint)
+ {
+ if (dataConstraint < -1 || dataConstraint > 2) throw new IllegalArgumentException("Expected -1, 0, 1, or 2, not: " + dataConstraint);
+ if (dataConstraint == -1) return None;
+ return values()[dataConstraint];
+ }
+
+ public UserDataConstraint combine(UserDataConstraint other)
+ {
+ if (this.compareTo(other) < 0) return this;
+ return other;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class BasicAuthenticator extends LoginAuthenticator
+{
+ /* ------------------------------------------------------------ */
+ public BasicAuthenticator()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.Authenticator#getAuthMethod()
+ */
+ @Override
+ public String getAuthMethod()
+ {
+ return Constraint.__BASIC_AUTH;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.Authenticator#validateRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, boolean)
+ */
+ @Override
+ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+ {
+ HttpServletRequest request = (HttpServletRequest)req;
+ HttpServletResponse response = (HttpServletResponse)res;
+ String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+ try
+ {
+ if (!mandatory)
+ return new DeferredAuthentication(this);
+
+ if (credentials != null)
+ {
+ int space=credentials.indexOf(' ');
+ if (space>0)
+ {
+ String method=credentials.substring(0,space);
+ if ("basic".equalsIgnoreCase(method))
+ {
+ credentials = credentials.substring(space+1);
+ credentials = B64Code.decode(credentials, StandardCharsets.ISO_8859_1);
+ int i = credentials.indexOf(':');
+ if (i>0)
+ {
+ String username = credentials.substring(0,i);
+ String password = credentials.substring(i+1);
+
+ UserIdentity user = login (username, password, request);
+ if (user!=null)
+ {
+ return new UserAuthentication(getAuthMethod(),user);
+ }
+ }
+ }
+ }
+ }
+
+ if (DeferredAuthentication.isDeferred(response))
+ return Authentication.UNAUTHENTICATED;
+
+ response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "basic realm=\"" + _loginService.getName() + '"');
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return Authentication.SEND_CONTINUE;
+ }
+ catch (IOException e)
+ {
+ throw new ServerAuthException(e);
+ }
+ }
+
+ @Override
+ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.cert.CRL;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Password;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class ClientCertAuthenticator extends LoginAuthenticator
+{
+ /** String name of keystore password property. */
+ private static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+ /** Truststore path */
+ private String _trustStorePath;
+ /** Truststore provider name */
+ private String _trustStoreProvider;
+ /** Truststore type */
+ private String _trustStoreType = "JKS";
+ /** Truststore password */
+ private transient Password _trustStorePassword;
+
+ /** Set to true if SSL certificate validation is required */
+ private boolean _validateCerts;
+ /** Path to file that contains Certificate Revocation List */
+ private String _crlPath;
+ /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+ private int _maxCertPathLength = -1;
+ /** CRL Distribution Points (CRLDP) support */
+ private boolean _enableCRLDP = false;
+ /** On-Line Certificate Status Protocol (OCSP) support */
+ private boolean _enableOCSP = false;
+ /** Location of OCSP Responder */
+ private String _ocspResponderURL;
+
+ public ClientCertAuthenticator()
+ {
+ super();
+ }
+
+ @Override
+ public String getAuthMethod()
+ {
+ return Constraint.__CERT_AUTH;
+ }
+
+
+
+ /**
+ * @return Authentication for request
+ * @throws ServerAuthException
+ */
+ @Override
+ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+ {
+ if (!mandatory)
+ return new DeferredAuthentication(this);
+
+ HttpServletRequest request = (HttpServletRequest)req;
+ HttpServletResponse response = (HttpServletResponse)res;
+ X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+
+ try
+ {
+ // Need certificates.
+ if (certs != null && certs.length > 0)
+ {
+
+ if (_validateCerts)
+ {
+ KeyStore trustStore = getKeyStore(null,
+ _trustStorePath, _trustStoreType, _trustStoreProvider,
+ _trustStorePassword == null ? null :_trustStorePassword.toString());
+ Collection<? extends CRL> crls = loadCRL(_crlPath);
+ CertificateValidator validator = new CertificateValidator(trustStore, crls);
+ validator.validate(certs);
+ }
+
+ for (X509Certificate cert: certs)
+ {
+ if (cert==null)
+ continue;
+
+ Principal principal = cert.getSubjectDN();
+ if (principal == null) principal = cert.getIssuerDN();
+ final String username = principal == null ? "clientcert" : principal.getName();
+
+ final char[] credential = B64Code.encode(cert.getSignature());
+
+ UserIdentity user = login(username, credential, req);
+ if (user!=null)
+ {
+ return new UserAuthentication(getAuthMethod(),user);
+ }
+ }
+ }
+
+ if (!DeferredAuthentication.isDeferred(response))
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return Authentication.SEND_FAILURE;
+ }
+
+ return Authentication.UNAUTHENTICATED;
+ }
+ catch (Exception e)
+ {
+ throw new ServerAuthException(e.getMessage());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Loads keystore using an input stream or a file path in the same
+ * order of precedence.
+ *
+ * Required for integrations to be able to override the mechanism
+ * used to load a keystore in order to provide their own implementation.
+ *
+ * @param storeStream keystore input stream
+ * @param storePath path of keystore file
+ * @param storeType keystore type
+ * @param storeProvider keystore provider
+ * @param storePassword keystore password
+ * @return created keystore
+ * @throws Exception
+ */
+ protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+ {
+ return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Loads certificate revocation list (CRL) from a file.
+ *
+ * Required for integrations to be able to override the mechanism used to
+ * load CRL in order to provide their own implementation.
+ *
+ * @param crlPath path of certificate revocation list file
+ * @return a (possibly empty) collection view of java.security.cert.CRL objects initialized with the data from the
+ * input stream.
+ * @throws Exception
+ */
+ protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+ {
+ return CertificateUtils.loadCRL(crlPath);
+ }
+
+ @Override
+ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if SSL certificate has to be validated
+ */
+ public boolean isValidateCerts()
+ {
+ return _validateCerts;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param validateCerts
+ * true if SSL certificates have to be validated
+ */
+ public void setValidateCerts(boolean validateCerts)
+ {
+ _validateCerts = validateCerts;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The file name or URL of the trust store location
+ */
+ public String getTrustStore()
+ {
+ return _trustStorePath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param trustStorePath
+ * The file name or URL of the trust store location
+ */
+ public void setTrustStore(String trustStorePath)
+ {
+ _trustStorePath = trustStorePath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The provider of the trust store
+ */
+ public String getTrustStoreProvider()
+ {
+ return _trustStoreProvider;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param trustStoreProvider
+ * The provider of the trust store
+ */
+ public void setTrustStoreProvider(String trustStoreProvider)
+ {
+ _trustStoreProvider = trustStoreProvider;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The type of the trust store (default "JKS")
+ */
+ public String getTrustStoreType()
+ {
+ return _trustStoreType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param trustStoreType
+ * The type of the trust store (default "JKS")
+ */
+ public void setTrustStoreType(String trustStoreType)
+ {
+ _trustStoreType = trustStoreType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param password
+ * The password for the trust store
+ */
+ public void setTrustStorePassword(String password)
+ {
+ _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the crlPath.
+ * @return the crlPath
+ */
+ public String getCrlPath()
+ {
+ return _crlPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the crlPath.
+ * @param crlPath the crlPath to set
+ */
+ public void setCrlPath(String crlPath)
+ {
+ _crlPath = crlPath;
+ }
+
+ /**
+ * @return Maximum number of intermediate certificates in
+ * the certification path (-1 for unlimited)
+ */
+ public int getMaxCertPathLength()
+ {
+ return _maxCertPathLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maxCertPathLength
+ * maximum number of intermediate certificates in
+ * the certification path (-1 for unlimited)
+ */
+ public void setMaxCertPathLength(int maxCertPathLength)
+ {
+ _maxCertPathLength = maxCertPathLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if CRL Distribution Points support is enabled
+ */
+ public boolean isEnableCRLDP()
+ {
+ return _enableCRLDP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Enables CRL Distribution Points Support
+ * @param enableCRLDP true - turn on, false - turns off
+ */
+ public void setEnableCRLDP(boolean enableCRLDP)
+ {
+ _enableCRLDP = enableCRLDP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if On-Line Certificate Status Protocol support is enabled
+ */
+ public boolean isEnableOCSP()
+ {
+ return _enableOCSP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Enables On-Line Certificate Status Protocol support
+ * @param enableOCSP true - turn on, false - turn off
+ */
+ public void setEnableOCSP(boolean enableOCSP)
+ {
+ _enableOCSP = enableOCSP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Location of the OCSP Responder
+ */
+ public String getOcspResponderURL()
+ {
+ return _ocspResponderURL;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the location of the OCSP Responder.
+ * @param ocspResponderURL location of the OCSP Responder
+ */
+ public void setOcspResponderURL(String ocspResponderURL)
+ {
+ _ocspResponderURL = ocspResponderURL;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class DeferredAuthentication implements Authentication.Deferred
+{
+ private static final Logger LOG = Log.getLogger(DeferredAuthentication.class);
+ protected final LoginAuthenticator _authenticator;
+ private Object _previousAssociation;
+
+ /* ------------------------------------------------------------ */
+ public DeferredAuthentication(LoginAuthenticator authenticator)
+ {
+ if (authenticator == null)
+ throw new NullPointerException("No Authenticator");
+ this._authenticator = authenticator;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(ServletRequest)
+ */
+ @Override
+ public Authentication authenticate(ServletRequest request)
+ {
+ try
+ {
+ Authentication authentication = _authenticator.validateRequest(request,__deferredResponse,true);
+
+ if (authentication!=null && (authentication instanceof Authentication.User) && !(authentication instanceof Authentication.ResponseSent))
+ {
+ LoginService login_service= _authenticator.getLoginService();
+ IdentityService identity_service=login_service.getIdentityService();
+
+ if (identity_service!=null)
+ _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+
+ return authentication;
+ }
+ }
+ catch (ServerAuthException e)
+ {
+ LOG.debug(e);
+ }
+
+ return this;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+ */
+ @Override
+ public Authentication authenticate(ServletRequest request, ServletResponse response)
+ {
+ try
+ {
+ LoginService login_service= _authenticator.getLoginService();
+ IdentityService identity_service=login_service.getIdentityService();
+
+ Authentication authentication = _authenticator.validateRequest(request,response,true);
+ if (authentication instanceof Authentication.User && identity_service!=null)
+ _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+ return authentication;
+ }
+ catch (ServerAuthException e)
+ {
+ LOG.debug(e);
+ }
+ return this;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.Authentication.Deferred#login(String, Object, ServletRequest)
+ */
+ @Override
+ public Authentication login(String username, Object password, ServletRequest request)
+ {
+ if (username == null)
+ return null;
+
+ UserIdentity identity = _authenticator.login(username, password, request);
+ if (identity != null)
+ {
+ IdentityService identity_service = _authenticator.getLoginService().getIdentityService();
+ UserAuthentication authentication = new UserAuthentication("API",identity);
+ if (identity_service != null)
+ _previousAssociation=identity_service.associate(identity);
+ return authentication;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getPreviousAssociation()
+ {
+ return _previousAssociation;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param response
+ * @return true if this response is from a deferred call to {@link #authenticate(ServletRequest)}
+ */
+ public static boolean isDeferred(HttpServletResponse response)
+ {
+ return response==__deferredResponse;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ final static HttpServletResponse __deferredResponse = new HttpServletResponse()
+ {
+ @Override
+ public void addCookie(Cookie cookie)
+ {
+ }
+
+ @Override
+ public void addDateHeader(String name, long date)
+ {
+ }
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ }
+
+ @Override
+ public void addIntHeader(String name, int value)
+ {
+ }
+
+ @Override
+ public boolean containsHeader(String name)
+ {
+ return false;
+ }
+
+ @Override
+ public String encodeRedirectURL(String url)
+ {
+ return null;
+ }
+
+ @Override
+ public String encodeRedirectUrl(String url)
+ {
+ return null;
+ }
+
+ @Override
+ public String encodeURL(String url)
+ {
+ return null;
+ }
+
+ @Override
+ public String encodeUrl(String url)
+ {
+ return null;
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException
+ {
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException
+ {
+ }
+
+ @Override
+ public void sendRedirect(String location) throws IOException
+ {
+ }
+
+ @Override
+ public void setDateHeader(String name, long date)
+ {
+ }
+
+ @Override
+ public void setHeader(String name, String value)
+ {
+ }
+
+ @Override
+ public void setIntHeader(String name, int value)
+ {
+ }
+
+ @Override
+ public void setStatus(int sc)
+ {
+ }
+
+ @Override
+ public void setStatus(int sc, String sm)
+ {
+ }
+
+ @Override
+ public void flushBuffer() throws IOException
+ {
+ }
+
+ @Override
+ public int getBufferSize()
+ {
+ return 1024;
+ }
+
+ @Override
+ public String getCharacterEncoding()
+ {
+ return null;
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return null;
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ return null;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException
+ {
+ return __nullOut;
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException
+ {
+ return IO.getNullPrintWriter();
+ }
+
+ @Override
+ public boolean isCommitted()
+ {
+ return true;
+ }
+
+ @Override
+ public void reset()
+ {
+ }
+
+ @Override
+ public void resetBuffer()
+ {
+ }
+
+ @Override
+ public void setBufferSize(int size)
+ {
+ }
+
+ @Override
+ public void setCharacterEncoding(String charset)
+ {
+ }
+
+ @Override
+ public void setContentLength(int len)
+ {
+ }
+
+ public void setContentLengthLong(long len)
+ {
+
+ }
+
+ @Override
+ public void setContentType(String type)
+ {
+ }
+
+ @Override
+ public void setLocale(Locale loc)
+ {
+ }
+
+ @Override
+ public Collection<String> getHeaderNames()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getHeader(String arg0)
+ {
+ return null;
+ }
+
+ @Override
+ public Collection<String> getHeaders(String arg0)
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public int getStatus()
+ {
+ return 0;
+ }
+
+
+ };
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static ServletOutputStream __nullOut = new ServletOutputStream()
+ {
+ @Override
+ public void write(int b) throws IOException
+ {
+ }
+
+ @Override
+ public void print(String s) throws IOException
+ {
+ }
+
+ @Override
+ public void println(String s) throws IOException
+ {
+ }
+
+
+ @Override
+ public void setWriteListener(WriteListener writeListener)
+ {
+
+ }
+
+ @Override
+ public boolean isReady()
+ {
+ return false;
+ }
+ };
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.BitSet;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ *
+ * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceAge". The nonce max count can be set with {@link SecurityHandler#setInitParameter(String, String)}
+ * using the name "maxNonceCount". When the age or count is exceeded, the nonce is considered stale.
+ */
+public class DigestAuthenticator extends LoginAuthenticator
+{
+ private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
+ SecureRandom _random = new SecureRandom();
+ private long _maxNonceAgeMs = 60*1000;
+ private int _maxNC=1024;
+ private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<String, Nonce>();
+ private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
+ private static class Nonce
+ {
+ final String _nonce;
+ final long _ts;
+ final BitSet _seen;
+
+ public Nonce(String nonce, long ts, int size)
+ {
+ _nonce=nonce;
+ _ts=ts;
+ _seen = new BitSet(size);
+ }
+
+ public boolean seen(int count)
+ {
+ synchronized (this)
+ {
+ if (count>=_seen.size())
+ return true;
+ boolean s=_seen.get(count);
+ _seen.set(count);
+ return s;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public DigestAuthenticator()
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+ */
+ @Override
+ public void setConfiguration(AuthConfiguration configuration)
+ {
+ super.setConfiguration(configuration);
+
+ String mna=configuration.getInitParameter("maxNonceAge");
+ if (mna!=null)
+ {
+ _maxNonceAgeMs=Long.valueOf(mna);
+ }
+ String mnc=configuration.getInitParameter("maxNonceCount");
+ if (mnc!=null)
+ {
+ _maxNC=Integer.valueOf(mnc);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxNonceCount()
+ {
+ return _maxNC;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxNonceCount(int maxNC)
+ {
+ _maxNC = maxNC;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getMaxNonceAge()
+ {
+ return _maxNonceAgeMs;
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
+ {
+ _maxNonceAgeMs = maxNonceAgeInMillis;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getAuthMethod()
+ {
+ return Constraint.__DIGEST_AUTH;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+ {
+ if (!mandatory)
+ return new DeferredAuthentication(this);
+
+ HttpServletRequest request = (HttpServletRequest)req;
+ HttpServletResponse response = (HttpServletResponse)res;
+ String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+ try
+ {
+ boolean stale = false;
+ if (credentials != null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Credentials: " + credentials);
+ QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false);
+ final Digest digest = new Digest(request.getMethod());
+ String last = null;
+ String name = null;
+
+ while (tokenizer.hasMoreTokens())
+ {
+ String tok = tokenizer.nextToken();
+ char c = (tok.length() == 1) ? tok.charAt(0) : '\0';
+
+ switch (c)
+ {
+ case '=':
+ name = last;
+ last = tok;
+ break;
+ case ',':
+ name = null;
+ break;
+ case ' ':
+ break;
+
+ default:
+ last = tok;
+ if (name != null)
+ {
+ if ("username".equalsIgnoreCase(name))
+ digest.username = tok;
+ else if ("realm".equalsIgnoreCase(name))
+ digest.realm = tok;
+ else if ("nonce".equalsIgnoreCase(name))
+ digest.nonce = tok;
+ else if ("nc".equalsIgnoreCase(name))
+ digest.nc = tok;
+ else if ("cnonce".equalsIgnoreCase(name))
+ digest.cnonce = tok;
+ else if ("qop".equalsIgnoreCase(name))
+ digest.qop = tok;
+ else if ("uri".equalsIgnoreCase(name))
+ digest.uri = tok;
+ else if ("response".equalsIgnoreCase(name))
+ digest.response = tok;
+ name=null;
+ }
+ }
+ }
+
+ int n = checkNonce(digest,(Request)request);
+
+ if (n > 0)
+ {
+ //UserIdentity user = _loginService.login(digest.username,digest);
+ UserIdentity user = login(digest.username, digest, req);
+ if (user!=null)
+ {
+ return new UserAuthentication(getAuthMethod(),user);
+ }
+ }
+ else if (n == 0)
+ stale = true;
+
+ }
+
+ if (!DeferredAuthentication.isDeferred(response))
+ {
+ String domain = request.getContextPath();
+ if (domain == null)
+ domain = "/";
+ response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Digest realm=\"" + _loginService.getName()
+ + "\", domain=\""
+ + domain
+ + "\", nonce=\""
+ + newNonce((Request)request)
+ + "\", algorithm=MD5, qop=\"auth\","
+ + " stale=" + stale);
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+
+ return Authentication.SEND_CONTINUE;
+ }
+
+ return Authentication.UNAUTHENTICATED;
+ }
+ catch (IOException e)
+ {
+ throw new ServerAuthException(e);
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public String newNonce(Request request)
+ {
+ Nonce nonce;
+
+ do
+ {
+ byte[] nounce = new byte[24];
+ _random.nextBytes(nounce);
+
+ nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
+ }
+ while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
+ _nonceQueue.add(nonce);
+
+ return nonce._nonce;
+ }
+
+ /**
+ * @param nstring nonce to check
+ * @param request
+ * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce
+ */
+ /* ------------------------------------------------------------ */
+ private int checkNonce(Digest digest, Request request)
+ {
+ // firstly let's expire old nonces
+ long expired = request.getTimeStamp()-_maxNonceAgeMs;
+ Nonce nonce=_nonceQueue.peek();
+ while (nonce!=null && nonce._ts<expired)
+ {
+ _nonceQueue.remove(nonce);
+ _nonceMap.remove(nonce._nonce);
+ nonce=_nonceQueue.peek();
+ }
+
+ // Now check the requested nonce
+ try
+ {
+ nonce = _nonceMap.get(digest.nonce);
+ if (nonce==null)
+ return 0;
+
+ long count = Long.parseLong(digest.nc,16);
+ if (count>=_maxNC)
+ return 0;
+
+ if (nonce.seen((int)count))
+ return -1;
+
+ return 1;
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ }
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class Digest extends Credential
+ {
+ private static final long serialVersionUID = -2484639019549527724L;
+ final String method;
+ String username = "";
+ String realm = "";
+ String nonce = "";
+ String nc = "";
+ String cnonce = "";
+ String qop = "";
+ String uri = "";
+ String response = "";
+
+ /* ------------------------------------------------------------ */
+ Digest(String m)
+ {
+ method = m;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean check(Object credentials)
+ {
+ if (credentials instanceof char[])
+ credentials=new String((char[])credentials);
+ String password = (credentials instanceof String) ? (String) credentials : credentials.toString();
+
+ try
+ {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] ha1;
+ if (credentials instanceof Credential.MD5)
+ {
+ // Credentials are already a MD5 digest - assume it's in
+ // form user:realm:password (we have no way to know since
+ // it's a digest, alright?)
+ ha1 = ((Credential.MD5) credentials).getDigest();
+ }
+ else
+ {
+ // calc A1 digest
+ md.update(username.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(realm.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(password.getBytes(StandardCharsets.ISO_8859_1));
+ ha1 = md.digest();
+ }
+ // calc A2 digest
+ md.reset();
+ md.update(method.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
+ byte[] ha2 = md.digest();
+
+ // calc digest
+ // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":"
+ // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) )
+ // <">
+ // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
+ // ) > <">
+
+ md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(nc.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(qop.getBytes(StandardCharsets.ISO_8859_1));
+ md.update((byte) ':');
+ md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
+ byte[] digest = md.digest();
+
+ // check digest
+ return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response));
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ return username + "," + response;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * FORM Authenticator.
+ *
+ * <p>This authenticator implements form authentication will use dispatchers to
+ * the login page if the {@link #__FORM_DISPATCH} init parameter is set to true.
+ * Otherwise it will redirect.</p>
+ *
+ * <p>The form authenticator redirects unauthenticated requests to a log page
+ * which should use a form to gather username/password from the user and send them
+ * to the /j_security_check URI within the context. FormAuthentication uses
+ * {@link SessionAuthentication} to wrap Authentication results so that they
+ * are associated with the session.</p>
+ *
+ *
+ */
+public class FormAuthenticator extends LoginAuthenticator
+{
+ private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
+
+ public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
+ public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
+ public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
+ public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
+ public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
+ public final static String __J_METHOD = "org.eclipse.jetty.security.form_METHOD";
+ public final static String __J_SECURITY_CHECK = "/j_security_check";
+ public final static String __J_USERNAME = "j_username";
+ public final static String __J_PASSWORD = "j_password";
+
+ private String _formErrorPage;
+ private String _formErrorPath;
+ private String _formLoginPage;
+ private String _formLoginPath;
+ private boolean _dispatch;
+ private boolean _alwaysSaveUri;
+
+ public FormAuthenticator()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public FormAuthenticator(String login,String error,boolean dispatch)
+ {
+ this();
+ if (login!=null)
+ setLoginPage(login);
+ if (error!=null)
+ setErrorPage(error);
+ _dispatch=dispatch;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * If true, uris that cause a redirect to a login page will always
+ * be remembered. If false, only the first uri that leads to a login
+ * page redirect is remembered.
+ * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=379909
+ * @param alwaysSave
+ */
+ public void setAlwaysSaveUri (boolean alwaysSave)
+ {
+ _alwaysSaveUri = alwaysSave;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean getAlwaysSaveUri ()
+ {
+ return _alwaysSaveUri;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+ */
+ @Override
+ public void setConfiguration(AuthConfiguration configuration)
+ {
+ super.setConfiguration(configuration);
+ String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
+ if (login!=null)
+ setLoginPage(login);
+ String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
+ if (error!=null)
+ setErrorPage(error);
+ String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
+ _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getAuthMethod()
+ {
+ return Constraint.__FORM_AUTH;
+ }
+
+ /* ------------------------------------------------------------ */
+ private void setLoginPage(String path)
+ {
+ if (!path.startsWith("/"))
+ {
+ LOG.warn("form-login-page must start with /");
+ path = "/" + path;
+ }
+ _formLoginPage = path;
+ _formLoginPath = path;
+ if (_formLoginPath.indexOf('?') > 0)
+ _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
+ }
+
+ /* ------------------------------------------------------------ */
+ private void setErrorPage(String path)
+ {
+ if (path == null || path.trim().length() == 0)
+ {
+ _formErrorPath = null;
+ _formErrorPage = null;
+ }
+ else
+ {
+ if (!path.startsWith("/"))
+ {
+ LOG.warn("form-error-page must start with /");
+ path = "/" + path;
+ }
+ _formErrorPage = path;
+ _formErrorPath = path;
+
+ if (_formErrorPath.indexOf('?') > 0)
+ _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public UserIdentity login(String username, Object password, ServletRequest request)
+ {
+
+ UserIdentity user = super.login(username,password,request);
+ if (user!=null)
+ {
+ HttpSession session = ((HttpServletRequest)request).getSession(true);
+ Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
+ session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
+ }
+ return user;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void prepareRequest(ServletRequest request)
+ {
+ //if this is a request resulting from a redirect after auth is complete
+ //(ie its from a redirect to the original request uri) then due to
+ //browser handling of 302 redirects, the method may not be the same as
+ //that of the original request. Replace the method and original post
+ //params (if it was a post).
+ //
+ //See Servlet Spec 3.1 sec 13.6.3
+ HttpServletRequest httpRequest = (HttpServletRequest)request;
+ HttpSession session = httpRequest.getSession(false);
+ if (session == null || session.getAttribute(SessionAuthentication.__J_AUTHENTICATED) == null)
+ return; //not authenticated yet
+
+ String juri = (String)session.getAttribute(__J_URI);
+ if (juri == null || juri.length() == 0)
+ return; //no original uri saved
+
+ String method = (String)session.getAttribute(__J_METHOD);
+ if (method == null || method.length() == 0)
+ return; //didn't save original request method
+
+ StringBuffer buf = httpRequest.getRequestURL();
+ if (httpRequest.getQueryString() != null)
+ buf.append("?").append(httpRequest.getQueryString());
+
+ if (!juri.equals(buf.toString()))
+ return; //this request is not for the same url as the original
+
+ //restore the original request's method on this request
+ if (LOG.isDebugEnabled()) LOG.debug("Restoring original method {} for {} with method {}", method, juri,httpRequest.getMethod());
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ HttpMethod m = HttpMethod.fromString(method);
+ base_request.setMethod(m,m.asString());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+ {
+ HttpServletRequest request = (HttpServletRequest)req;
+ HttpServletResponse response = (HttpServletResponse)res;
+ String uri = request.getRequestURI();
+ if (uri==null)
+ uri=URIUtil.SLASH;
+
+ mandatory|=isJSecurityCheck(uri);
+ if (!mandatory)
+ return new DeferredAuthentication(this);
+
+ if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
+ return new DeferredAuthentication(this);
+
+ HttpSession session = request.getSession(true);
+
+ try
+ {
+ // Handle a request for authentication.
+ if (isJSecurityCheck(uri))
+ {
+ final String username = request.getParameter(__J_USERNAME);
+ final String password = request.getParameter(__J_PASSWORD);
+
+ UserIdentity user = login(username, password, request);
+ LOG.debug("jsecuritycheck {} {}",username,user);
+ session = request.getSession(true);
+ if (user!=null)
+ {
+ // Redirect to original request
+ String nuri;
+ FormAuthentication form_auth;
+ synchronized(session)
+ {
+ nuri = (String) session.getAttribute(__J_URI);
+
+ if (nuri == null || nuri.length() == 0)
+ {
+ nuri = request.getContextPath();
+ if (nuri.length() == 0)
+ nuri = URIUtil.SLASH;
+ }
+ form_auth = new FormAuthentication(getAuthMethod(),user);
+ }
+ LOG.debug("authenticated {}->{}",form_auth,nuri);
+
+ response.setContentLength(0);
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
+ return form_auth;
+ }
+
+ // not authenticated
+ if (LOG.isDebugEnabled())
+ LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
+ if (_formErrorPage == null)
+ {
+ LOG.debug("auth failed {}->403",username);
+ if (response != null)
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ }
+ else if (_dispatch)
+ {
+ LOG.debug("auth failed {}=={}",username,_formErrorPage);
+ RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
+ response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+ response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+ dispatcher.forward(new FormRequest(request), new FormResponse(response));
+ }
+ else
+ {
+ LOG.debug("auth failed {}->{}",username,_formErrorPage);
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
+ }
+
+ return Authentication.SEND_FAILURE;
+ }
+
+ // Look for cached authentication
+ Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
+ if (authentication != null)
+ {
+ // Has authentication been revoked?
+ if (authentication instanceof Authentication.User &&
+ _loginService!=null &&
+ !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
+ {
+ LOG.debug("auth revoked {}",authentication);
+ session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
+ }
+ else
+ {
+ synchronized (session)
+ {
+ String j_uri=(String)session.getAttribute(__J_URI);
+ if (j_uri!=null)
+ {
+ //check if the request is for the same url as the original and restore
+ //params if it was a post
+ LOG.debug("auth retry {}->{}",authentication,j_uri);
+ StringBuffer buf = request.getRequestURL();
+ if (request.getQueryString() != null)
+ buf.append("?").append(request.getQueryString());
+
+ if (j_uri.equals(buf.toString()))
+ {
+ MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
+ if (j_post!=null)
+ {
+ LOG.debug("auth rePOST {}->{}",authentication,j_uri);
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ base_request.setContentParameters(j_post);
+ }
+ session.removeAttribute(__J_URI);
+ session.removeAttribute(__J_METHOD);
+ session.removeAttribute(__J_POST);
+ }
+ }
+ }
+ LOG.debug("auth {}",authentication);
+ return authentication;
+ }
+ }
+
+ // if we can't send challenge
+ if (DeferredAuthentication.isDeferred(response))
+ {
+ LOG.debug("auth deferred {}",session.getId());
+ return Authentication.UNAUTHENTICATED;
+ }
+
+ // remember the current URI
+ synchronized (session)
+ {
+ // But only if it is not set already, or we save every uri that leads to a login form redirect
+ if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
+ {
+ StringBuffer buf = request.getRequestURL();
+ if (request.getQueryString() != null)
+ buf.append("?").append(request.getQueryString());
+ session.setAttribute(__J_URI, buf.toString());
+ session.setAttribute(__J_METHOD, request.getMethod());
+
+ if (MimeTypes.Type.FORM_ENCODED.is(req.getContentType()) && HttpMethod.POST.is(request.getMethod()))
+ {
+ Request base_request = (req instanceof Request)?(Request)req:HttpChannel.getCurrentHttpChannel().getRequest();
+ MultiMap<String> formParameters = new MultiMap<>();
+ base_request.extractFormParameters(formParameters);
+ session.setAttribute(__J_POST, formParameters);
+ }
+ }
+ }
+
+ // send the the challenge
+ if (_dispatch)
+ {
+ LOG.debug("challenge {}=={}",session.getId(),_formLoginPage);
+ RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
+ response.setHeader(HttpHeader.CACHE_CONTROL.asString(),HttpHeaderValue.NO_CACHE.asString());
+ response.setDateHeader(HttpHeader.EXPIRES.asString(),1);
+ dispatcher.forward(new FormRequest(request), new FormResponse(response));
+ }
+ else
+ {
+ LOG.debug("challenge {}->{}",session.getId(),_formLoginPage);
+ Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
+ Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
+ int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
+ base_response.sendRedirect(redirectCode, response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
+ }
+ return Authentication.SEND_CONTINUE;
+ }
+ catch (IOException | ServletException e)
+ {
+ throw new ServerAuthException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isJSecurityCheck(String uri)
+ {
+ int jsc = uri.indexOf(__J_SECURITY_CHECK);
+
+ if (jsc<0)
+ return false;
+ int e=jsc+__J_SECURITY_CHECK.length();
+ if (e==uri.length())
+ return true;
+ char c = uri.charAt(e);
+ return c==';'||c=='#'||c=='/'||c=='?';
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isLoginOrErrorPage(String pathInContext)
+ {
+ return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected static class FormRequest extends HttpServletRequestWrapper
+ {
+ public FormRequest(HttpServletRequest request)
+ {
+ super(request);
+ }
+
+ @Override
+ public long getDateHeader(String name)
+ {
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+ return -1;
+ return super.getDateHeader(name);
+ }
+
+ @Override
+ public String getHeader(String name)
+ {
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+ return null;
+ return super.getHeader(name);
+ }
+
+ @Override
+ public Enumeration<String> getHeaderNames()
+ {
+ return Collections.enumeration(Collections.list(super.getHeaderNames()));
+ }
+
+ @Override
+ public Enumeration<String> getHeaders(String name)
+ {
+ if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+ return Collections.<String>enumeration(Collections.<String>emptyList());
+ return super.getHeaders(name);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected static class FormResponse extends HttpServletResponseWrapper
+ {
+ public FormResponse(HttpServletResponse response)
+ {
+ super(response);
+ }
+
+ @Override
+ public void addDateHeader(String name, long date)
+ {
+ if (notIgnored(name))
+ super.addDateHeader(name,date);
+ }
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ if (notIgnored(name))
+ super.addHeader(name,value);
+ }
+
+ @Override
+ public void setDateHeader(String name, long date)
+ {
+ if (notIgnored(name))
+ super.setDateHeader(name,date);
+ }
+
+ @Override
+ public void setHeader(String name, String value)
+ {
+ if (notIgnored(name))
+ super.setHeader(name,value);
+ }
+
+ private boolean notIgnored(String name)
+ {
+ if (HttpHeader.CACHE_CONTROL.is(name) ||
+ HttpHeader.PRAGMA.is(name) ||
+ HttpHeader.ETAG.is(name) ||
+ HttpHeader.EXPIRES.is(name) ||
+ HttpHeader.LAST_MODIFIED.is(name) ||
+ HttpHeader.AGE.is(name))
+ return false;
+ return true;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** This Authentication represents a just completed Form authentication.
+ * Subsequent requests from the same user are authenticated by the presents
+ * of a {@link SessionAuthentication} instance in their session.
+ */
+ public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
+ {
+ public FormAuthentication(String method, UserIdentity userIdentity)
+ {
+ super(method,userIdentity);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Form"+super.toString();
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.security.Authenticator;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class LoginAuthenticator implements Authenticator
+{
+ private static final Logger LOG = Log.getLogger(LoginAuthenticator.class);
+
+ protected LoginService _loginService;
+ protected IdentityService _identityService;
+ private boolean _renewSession;
+
+
+ /* ------------------------------------------------------------ */
+ protected LoginAuthenticator()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void prepareRequest(ServletRequest request)
+ {
+ //empty implementation as the default
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity login(String username, Object password, ServletRequest request)
+ {
+ UserIdentity user = _loginService.login(username,password);
+ if (user!=null)
+ {
+ renewSession((HttpServletRequest)request, (request instanceof Request? ((Request)request).getResponse() : null));
+ return user;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setConfiguration(AuthConfiguration configuration)
+ {
+ _loginService=configuration.getLoginService();
+ if (_loginService==null)
+ throw new IllegalStateException("No LoginService for "+this+" in "+configuration);
+ _identityService=configuration.getIdentityService();
+ if (_identityService==null)
+ throw new IllegalStateException("No IdentityService for "+this+" in "+configuration);
+ _renewSession=configuration.isSessionRenewedOnAuthentication();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public LoginService getLoginService()
+ {
+ return _loginService;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Change the session id.
+ * The session is changed to a new instance with a new ID if and only if:<ul>
+ * <li>A session exists.
+ * <li>The {@link AuthConfiguration#isSessionRenewedOnAuthentication()} returns true.
+ * <li>The session ID has been given to unauthenticated responses
+ * </ul>
+ * @param request
+ * @param response
+ * @return The new session.
+ */
+ protected HttpSession renewSession(HttpServletRequest request, HttpServletResponse response)
+ {
+ HttpSession httpSession = request.getSession(false);
+
+ if (_renewSession && httpSession!=null)
+ {
+ synchronized (httpSession)
+ {
+ //if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
+ //(indicated by SESSION_SECURED not being set on the session) then we should change id
+ if (httpSession.getAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED)!=Boolean.TRUE)
+ {
+ if (httpSession instanceof AbstractSession)
+ {
+ AbstractSession abstractSession = (AbstractSession)httpSession;
+ String oldId = abstractSession.getId();
+ abstractSession.renewId(request);
+ abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+ if (abstractSession.isIdChanged() && response != null && (response instanceof Response))
+ ((Response)response).addCookie(abstractSession.getSessionManager().getSessionCookie(abstractSession, request.getContextPath(), request.isSecure()));
+ LOG.debug("renew {}->{}",oldId,abstractSession.getId());
+ }
+ else
+ LOG.warn("Unable to renew session "+httpSession);
+
+ return httpSession;
+ }
+ }
+ }
+ return httpSession;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ *
+ * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $
+ */
+public interface LoginCallback
+{
+ public Subject getSubject();
+
+ public String getUserName();
+
+ public Object getCredential();
+
+ public boolean isSuccess();
+
+ public void setSuccess(boolean success);
+
+ public Principal getUserPrincipal();
+
+ public void setUserPrincipal(Principal userPrincipal);
+
+ public String[] getRoles();
+
+ public void setRoles(String[] roles);
+
+ public void clearPassword();
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.IdentityService;
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ *
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class LoginCallbackImpl implements LoginCallback
+{
+ // initial data
+ private final Subject subject;
+
+ private final String userName;
+
+ private Object credential;
+
+ private boolean success;
+
+ private Principal userPrincipal;
+
+ private String[] roles = IdentityService.NO_ROLES;
+
+ //TODO could use Credential instance instead of Object if Basic/Form create a Password object
+ public LoginCallbackImpl (Subject subject, String userName, Object credential)
+ {
+ this.subject = subject;
+ this.userName = userName;
+ this.credential = credential;
+ }
+
+ public Subject getSubject()
+ {
+ return subject;
+ }
+
+ public String getUserName()
+ {
+ return userName;
+ }
+
+ public Object getCredential()
+ {
+ return credential;
+ }
+
+ public boolean isSuccess()
+ {
+ return success;
+ }
+
+ public void setSuccess(boolean success)
+ {
+ this.success = success;
+ }
+
+ public Principal getUserPrincipal()
+ {
+ return userPrincipal;
+ }
+
+ public void setUserPrincipal(Principal userPrincipal)
+ {
+ this.userPrincipal = userPrincipal;
+ }
+
+ public String[] getRoles()
+ {
+ return roles;
+ }
+
+ public void setRoles(String[] groups)
+ {
+ this.roles = groups;
+ }
+
+ public void clearPassword()
+ {
+ if (credential != null)
+ {
+ credential = null;
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.security.AbstractUserAuthentication;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class SessionAuthentication extends AbstractUserAuthentication implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener
+{
+ private static final Logger LOG = Log.getLogger(SessionAuthentication.class);
+
+ private static final long serialVersionUID = -4643200685888258706L;
+
+
+
+ public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity";
+
+ private final String _name;
+ private final Object _credentials;
+ private transient HttpSession _session;
+
+ public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
+ {
+ super(method, userIdentity);
+ _name=userIdentity.getUserPrincipal().getName();
+ _credentials=credentials;
+ }
+
+
+ private void readObject(ObjectInputStream stream)
+ throws IOException, ClassNotFoundException
+ {
+ stream.defaultReadObject();
+
+ SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+ if (security==null)
+ throw new IllegalStateException("!SecurityHandler");
+ LoginService login_service=security.getLoginService();
+ if (login_service==null)
+ throw new IllegalStateException("!LoginService");
+
+ _userIdentity=login_service.login(_name,_credentials);
+ LOG.debug("Deserialized and relogged in {}",this);
+ }
+
+ public void logout()
+ {
+ if (_session!=null && _session.getAttribute(__J_AUTHENTICATED)!=null)
+ _session.removeAttribute(__J_AUTHENTICATED);
+
+ doLogout();
+ }
+
+ private void doLogout()
+ {
+ SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+ if (security!=null)
+ security.logout(this);
+ if (_session!=null)
+ _session.removeAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s,%s}",this.getClass().getSimpleName(),hashCode(),_session==null?"-":_session.getId(),_userIdentity);
+ }
+
+ @Override
+ public void sessionWillPassivate(HttpSessionEvent se)
+ {
+
+ }
+
+ @Override
+ public void sessionDidActivate(HttpSessionEvent se)
+ {
+ if (_session==null)
+ {
+ _session=se.getSession();
+ }
+ }
+
+ @Override
+ public void valueBound(HttpSessionBindingEvent event)
+ {
+ if (_session==null)
+ {
+ _session=event.getSession();
+ }
+ }
+
+ @Override
+ public void valueUnbound(HttpSessionBindingEvent event)
+ {
+ doLogout();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.security.authentication;
+
+import java.io.IOException;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+public class SpnegoAuthenticator extends LoginAuthenticator
+{
+ private static final Logger LOG = Log.getLogger(SpnegoAuthenticator.class);
+ private String _authMethod = Constraint.__SPNEGO_AUTH;
+
+ public SpnegoAuthenticator()
+ {
+ }
+
+ /**
+ * Allow for a custom authMethod value to be set for instances where SPENGO may not be appropriate
+ * @param authMethod
+ */
+ public SpnegoAuthenticator( String authMethod )
+ {
+ _authMethod = authMethod;
+ }
+
+ @Override
+ public String getAuthMethod()
+ {
+ return _authMethod;
+ }
+
+ @Override
+ public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException
+ {
+ HttpServletRequest req = (HttpServletRequest)request;
+ HttpServletResponse res = (HttpServletResponse)response;
+
+ String header = req.getHeader(HttpHeader.AUTHORIZATION.asString());
+
+ if (!mandatory)
+ {
+ return new DeferredAuthentication(this);
+ }
+
+ // check to see if we have authorization headers required to continue
+ if ( header == null )
+ {
+ try
+ {
+ if (DeferredAuthentication.isDeferred(res))
+ {
+ return Authentication.UNAUTHENTICATED;
+ }
+
+ LOG.debug("SpengoAuthenticator: sending challenge");
+ res.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), HttpHeader.NEGOTIATE.asString());
+ res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return Authentication.SEND_CONTINUE;
+ }
+ catch (IOException ioe)
+ {
+ throw new ServerAuthException(ioe);
+ }
+ }
+ else if (header != null && header.startsWith(HttpHeader.NEGOTIATE.asString()))
+ {
+ String spnegoToken = header.substring(10);
+
+ UserIdentity user = login(null,spnegoToken, request);
+
+ if ( user != null )
+ {
+ return new UserAuthentication(getAuthMethod(),user);
+ }
+ }
+
+ return Authentication.UNAUTHENTICATED;
+ }
+
+ @Override
+ public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException
+ {
+ return true;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Security : Authenticators and Callbacks
+ */
+package org.eclipse.jetty.security.authentication;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Security : Modular Support for Security in Jetty
+ */
+package org.eclipse.jetty.security;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public abstract class AbstractConnectionFactory extends ContainerLifeCycle implements ConnectionFactory
+{
+ private final String _protocol;
+ private int _inputbufferSize = 8192;
+
+ protected AbstractConnectionFactory(String protocol)
+ {
+ _protocol=protocol;
+ }
+
+ @Override
+ public String getProtocol()
+ {
+ return _protocol;
+ }
+
+ public int getInputBufferSize()
+ {
+ return _inputbufferSize;
+ }
+
+ public void setInputBufferSize(int size)
+ {
+ _inputbufferSize=size;
+ }
+
+ protected AbstractConnection configure(AbstractConnection connection, Connector connector, EndPoint endPoint)
+ {
+ connection.setInputBufferSize(getInputBufferSize());
+
+ if (connector instanceof ContainerLifeCycle)
+ {
+ ContainerLifeCycle aggregate = (ContainerLifeCycle)connector;
+ for (Connection.Listener listener : aggregate.getBeans(Connection.Listener.class))
+ connection.addListener(listener);
+ }
+ return connection;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol());
+ }
+
+ public static ConnectionFactory[] getFactories(SslContextFactory sslContextFactory, ConnectionFactory... factories)
+ {
+ factories=ArrayUtil.removeNulls(factories);
+
+ if (sslContextFactory==null)
+ return factories;
+
+ for (ConnectionFactory factory : factories)
+ {
+ if (factory instanceof HttpConfiguration.ConnectionFactory)
+ {
+ HttpConfiguration config = ((HttpConfiguration.ConnectionFactory)factory).getHttpConfiguration();
+ if (config.getCustomizer(SecureRequestCustomizer.class)==null)
+ config.addCustomizer(new SecureRequestCustomizer());
+ }
+ }
+ return ArrayUtil.prependToArray(new SslConnectionFactory(sslContextFactory,factories[0].getProtocol()),factories,ConnectionFactory.class);
+
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ArrayByteBufferPool;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>An abstract implementation of {@link Connector} that provides a {@link ConnectionFactory} mechanism
+ * for creating {@link Connection} instances for various protocols (HTTP, SSL, SPDY, etc).</p>
+ *
+ * <h2>Connector Services</h2>
+ * The abstract connector manages the dependent services needed by all specific connector instances:
+ * <ul>
+ * <li>The {@link Executor} service is used to run all active tasks needed by this connector such as accepting connections
+ * or handle HTTP requests. The default is to use the {@link Server#getThreadPool()} as an executor.
+ * </li>
+ * <li>The {@link Scheduler} service is used to monitor the idle timeouts of all connections and is also made available
+ * to the connections to time such things as asynchronous request timeouts. The default is to use a new
+ * {@link ScheduledExecutorScheduler} instance.
+ * </li>
+ * <li>The {@link ByteBufferPool} service is made available to all connections to be used to acquire and release
+ * {@link ByteBuffer} instances from a pool. The default is to use a new {@link ArrayByteBufferPool} instance.
+ * </li>
+ * </ul>
+ * These services are managed as aggregate beans by the {@link ContainerLifeCycle} super class and
+ * may either be managed or unmanaged beans.
+ *
+ * <h2>Connection Factories</h2>
+ * The connector keeps a collection of {@link ConnectionFactory} instances, each of which are known by their
+ * protocol name. The protocol name may be a real protocol (eg http/1.1 or spdy/3) or it may be a private name
+ * that represents a special connection factory. For example, the name "SSL-http/1.1" is used for
+ * an {@link SslConnectionFactory} that has been instantiated with the {@link HttpConnectionFactory} as it's
+ * next protocol.
+ *
+ * <h4>Configuring Connection Factories</h4>
+ * The collection of available {@link ConnectionFactory} may be constructor injected or modified with the
+ * methods {@link #addConnectionFactory(ConnectionFactory)}, {@link #removeConnectionFactory(String)} and
+ * {@link #setConnectionFactories(Collection)}. Only a single {@link ConnectionFactory} instance may be configured
+ * per protocol name, so if two factories with the same {@link ConnectionFactory#getProtocol()} are set, then
+ * the second will replace the first.
+ * <p>
+ * The protocol factory used for newly accepted connections is specified by
+ * the method {@link #setDefaultProtocol(String)} or defaults to the protocol of the first configured factory.
+ * <p>
+ * Each Connection factory type is responsible for the configuration of the protocols that it accepts. Thus to
+ * configure the HTTP protocol, you pass a {@link HttpConfiguration} instance to the {@link HttpConnectionFactory}
+ * (or the SPDY factories that can also provide HTTP Semantics). Similarly the {@link SslConnectionFactory} is
+ * configured by passing it a {@link SslContextFactory} and a next protocol name.
+ *
+ * <h4>Connection Factory Operation</h4>
+ * {@link ConnectionFactory}s may simply create a {@link Connection} instance to support a specific
+ * protocol. For example, the {@link HttpConnectionFactory} will create a {@link HttpConnection} instance
+ * that can handle http/1.1, http/1.0 and http/0.9.
+ * <p>
+ * {@link ConnectionFactory}s may also create a chain of {@link Connection} instances, using other {@link ConnectionFactory} instances.
+ * For example, the {@link SslConnectionFactory} is configured with a next protocol name, so that once it has accepted
+ * a connection and created an {@link SslConnection}, it then used the next {@link ConnectionFactory} from the
+ * connector using the {@link #getConnectionFactory(String)} method, to create a {@link Connection} instance that
+ * will handle the unecrypted bytes from the {@link SslConnection}. If the next protocol is "http/1.1", then the
+ * {@link SslConnectionFactory} will have a protocol name of "SSL-http/1.1" and lookup "http/1.1" for the protocol
+ * to run over the SSL connection.
+ * <p>
+ * {@link ConnectionFactory}s may also create temporary {@link Connection} instances that will exchange bytes
+ * over the connection to determine what is the next protocol to use. For example the NPN protocol is an extension
+ * of SSL to allow a protocol to be specified during the SSL handshake. NPN is used by the SPDY protocol to
+ * negotiate the version of SPDY or HTTP that the client and server will speak. Thus to accept a SPDY connection, the
+ * connector will be configured with {@link ConnectionFactory}s for "SSL-NPN", "NPN", "spdy/3", "spdy/2", "http/1.1"
+ * with the default protocol being "SSL-NPN". Thus a newly accepted connection uses "SSL-NPN", which specifies a
+ * SSLConnectionFactory with "NPN" as the next protocol. Thus an SslConnection instance is created chained to an NPNConnection
+ * instance. The NPN connection then negotiates with the client to determined the next protocol, which could be
+ * "spdy/3", "spdy/2" or the default of "http/1.1". Once the next protocol is determined, the NPN connection
+ * calls {@link #getConnectionFactory(String)} to create a connection instance that will replace the NPN connection as
+ * the connection chained to the SSLConnection.
+ * <p>
+ * <h2>Acceptors</h2>
+ * The connector will execute a number of acceptor tasks to the {@link Exception} service passed to the constructor.
+ * The acceptor tasks run in a loop while the connector is running and repeatedly call the abstract {@link #accept(int)} method.
+ * The implementation of the accept method must:
+ * <nl>
+ * <li>block waiting for new connections
+ * <li>accept the connection (eg socket accept)
+ * <li>perform any configuration of the connection (eg. socket linger times)
+ * <li>call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint)}
+ * method to create a new Connection instance.
+ * </nl>
+ * The default number of acceptor tasks is the minimum of 1 and half the number of available CPUs. Having more acceptors may reduce
+ * the latency for servers that see a high rate of new connections (eg HTTP/1.0 without keep-alive). Typically the default is
+ * sufficient for modern persistent protocols (HTTP/1.1, SPDY etc.)
+ */
+@ManagedObject("Abstract implementation of the Connector Interface")
+public abstract class AbstractConnector extends ContainerLifeCycle implements Connector, Dumpable
+{
+ protected final Logger LOG = Log.getLogger(getClass());
+ // Order is important on server side, so we use a LinkedHashMap
+ private final Map<String, ConnectionFactory> _factories = new LinkedHashMap<>();
+ private final Server _server;
+ private final Executor _executor;
+ private final Scheduler _scheduler;
+ private final ByteBufferPool _byteBufferPool;
+ private final Thread[] _acceptors;
+ private final Set<EndPoint> _endpoints = Collections.newSetFromMap(new ConcurrentHashMap());
+ private final Set<EndPoint> _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
+ private volatile CountDownLatch _stopping;
+ private long _idleTimeout = 30000;
+ private String _defaultProtocol;
+ private ConnectionFactory _defaultConnectionFactory;
+ private String _name;
+
+
+ /**
+ * @param server The server this connector will be added to. Must not be null.
+ * @param executor An executor for this connector or null to use the servers executor
+ * @param scheduler A scheduler for this connector or null to either a {@link Scheduler} set as a server bean or if none set, then a new {@link ScheduledExecutorScheduler} instance.
+ * @param pool A buffer pool for this connector or null to either a {@link ByteBufferPool} set as a server bean or none set, the new {@link ArrayByteBufferPool} instance.
+ * @param acceptors the number of acceptor threads to use, or 0 for a default value.
+ * @param factories The Connection Factories to use.
+ */
+ public AbstractConnector(
+ Server server,
+ Executor executor,
+ Scheduler scheduler,
+ ByteBufferPool pool,
+ int acceptors,
+ ConnectionFactory... factories)
+ {
+ _server=server;
+ _executor=executor!=null?executor:_server.getThreadPool();
+ if (scheduler==null)
+ scheduler=_server.getBean(Scheduler.class);
+ _scheduler=scheduler!=null?scheduler:new ScheduledExecutorScheduler();
+ if (pool==null)
+ pool=_server.getBean(ByteBufferPool.class);
+ _byteBufferPool = pool!=null?pool:new ArrayByteBufferPool();
+
+ addBean(_server,false);
+ addBean(_executor);
+ if (executor==null)
+ unmanage(_executor); // inherited from server
+ addBean(_scheduler);
+ addBean(_byteBufferPool);
+
+ for (ConnectionFactory factory:factories)
+ addConnectionFactory(factory);
+
+ int cores = Runtime.getRuntime().availableProcessors();
+ if (acceptors < 0)
+ acceptors = 1 + cores / 16;
+ if (acceptors > 2 * cores)
+ LOG.warn("Acceptors should be <= 2*availableProcessors: " + this);
+ _acceptors = new Thread[acceptors];
+ }
+
+
+ @Override
+ public Server getServer()
+ {
+ return _server;
+ }
+
+ @Override
+ public Executor getExecutor()
+ {
+ return _executor;
+ }
+
+ @Override
+ public ByteBufferPool getByteBufferPool()
+ {
+ return _byteBufferPool;
+ }
+
+ @Override
+ @ManagedAttribute("Idle timeout")
+ public long getIdleTimeout()
+ {
+ return _idleTimeout;
+ }
+
+ /**
+ * <p>Sets the maximum Idle time for a connection, which roughly translates to the {@link Socket#setSoTimeout(int)}
+ * call, although with NIO implementations other mechanisms may be used to implement the timeout.</p>
+ * <p>The max idle time is applied:</p>
+ * <ul>
+ * <li>When waiting for a new message to be received on a connection</li>
+ * <li>When waiting for a new message to be sent on a connection</li>
+ * </ul>
+ * <p>This value is interpreted as the maximum time between some progress being made on the connection.
+ * So if a single byte is read or written, then the timeout is reset.</p>
+ *
+ * @param idleTimeout the idle timeout
+ */
+ public void setIdleTimeout(long idleTimeout)
+ {
+ _idleTimeout = idleTimeout;
+ }
+
+ /**
+ * @return Returns the number of acceptor threads.
+ */
+ @ManagedAttribute("number of acceptor threads")
+ public int getAcceptors()
+ {
+ return _acceptors.length;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ _defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
+ if(_defaultConnectionFactory==null)
+ throw new IllegalStateException("No protocol factory for default protocol: "+_defaultProtocol);
+
+ super.doStart();
+
+ _stopping=new CountDownLatch(_acceptors.length);
+ for (int i = 0; i < _acceptors.length; i++)
+ getExecutor().execute(new Acceptor(i));
+
+ LOG.info("Started {}", this);
+ }
+
+
+ protected void interruptAcceptors()
+ {
+ synchronized (this)
+ {
+ for (Thread thread : _acceptors)
+ {
+ if (thread != null)
+ thread.interrupt();
+ }
+ }
+ }
+
+ @Override
+ public Future<Void> shutdown()
+ {
+ return new FutureCallback(true);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ // Tell the acceptors we are stopping
+ interruptAcceptors();
+
+ // If we have a stop timeout
+ long stopTimeout = getStopTimeout();
+ CountDownLatch stopping=_stopping;
+ if (stopTimeout > 0 && stopping!=null)
+ stopping.await(stopTimeout,TimeUnit.MILLISECONDS);
+ _stopping=null;
+
+ super.doStop();
+
+ LOG.info("Stopped {}", this);
+ }
+
+ public void join() throws InterruptedException
+ {
+ join(0);
+ }
+
+ public void join(long timeout) throws InterruptedException
+ {
+ synchronized (this)
+ {
+ for (Thread thread : _acceptors)
+ if (thread != null)
+ thread.join(timeout);
+ }
+ }
+
+ protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Is the connector accepting new connections
+ */
+ protected boolean isAccepting()
+ {
+ return isRunning();
+ }
+
+ @Override
+ public ConnectionFactory getConnectionFactory(String protocol)
+ {
+ synchronized (_factories)
+ {
+ return _factories.get(protocol.toLowerCase(Locale.ENGLISH));
+ }
+ }
+
+ @Override
+ public <T> T getConnectionFactory(Class<T> factoryType)
+ {
+ synchronized (_factories)
+ {
+ for (ConnectionFactory f : _factories.values())
+ if (factoryType.isAssignableFrom(f.getClass()))
+ return (T)f;
+ return null;
+ }
+ }
+
+ public void addConnectionFactory(ConnectionFactory factory)
+ {
+ synchronized (_factories)
+ {
+ ConnectionFactory old=_factories.remove(factory.getProtocol());
+ if (old!=null)
+ removeBean(old);
+ _factories.put(factory.getProtocol().toLowerCase(Locale.ENGLISH), factory);
+ addBean(factory);
+ if (_defaultProtocol==null)
+ _defaultProtocol=factory.getProtocol();
+ }
+ }
+
+ public ConnectionFactory removeConnectionFactory(String protocol)
+ {
+ synchronized (_factories)
+ {
+ ConnectionFactory factory= _factories.remove(protocol.toLowerCase(Locale.ENGLISH));
+ removeBean(factory);
+ return factory;
+ }
+ }
+
+ @Override
+ public Collection<ConnectionFactory> getConnectionFactories()
+ {
+ synchronized (_factories)
+ {
+ return _factories.values();
+ }
+ }
+
+ public void setConnectionFactories(Collection<ConnectionFactory> factories)
+ {
+ synchronized (_factories)
+ {
+ List<ConnectionFactory> existing = new ArrayList<>(_factories.values());
+ for (ConnectionFactory factory: existing)
+ removeConnectionFactory(factory.getProtocol());
+ for (ConnectionFactory factory: factories)
+ if (factory!=null)
+ addConnectionFactory(factory);
+ }
+ }
+
+
+ @Override
+ @ManagedAttribute("Protocols supported by this connector")
+ public List<String> getProtocols()
+ {
+ synchronized (_factories)
+ {
+ return new ArrayList<>(_factories.keySet());
+ }
+ }
+
+ public void clearConnectionFactories()
+ {
+ synchronized (_factories)
+ {
+ _factories.clear();
+ }
+ }
+
+ @ManagedAttribute("This connector's default protocol")
+ public String getDefaultProtocol()
+ {
+ return _defaultProtocol;
+ }
+
+ public void setDefaultProtocol(String defaultProtocol)
+ {
+ _defaultProtocol = defaultProtocol.toLowerCase(Locale.ENGLISH);
+ if (isRunning())
+ _defaultConnectionFactory=getConnectionFactory(_defaultProtocol);
+ }
+
+ @Override
+ public ConnectionFactory getDefaultConnectionFactory()
+ {
+ if (isStarted())
+ return _defaultConnectionFactory;
+ return getConnectionFactory(_defaultProtocol);
+ }
+
+ private class Acceptor implements Runnable
+ {
+ private final int _acceptor;
+
+ private Acceptor(int id)
+ {
+ _acceptor = id;
+ }
+
+ @Override
+ public void run()
+ {
+ Thread current = Thread.currentThread();
+ String name = current.getName();
+ current.setName(name + "-acceptor-" + _acceptor + "-" + AbstractConnector.this);
+
+ synchronized (AbstractConnector.this)
+ {
+ _acceptors[_acceptor] = current;
+ }
+
+ try
+ {
+ while (isAccepting())
+ {
+ try
+ {
+ accept(_acceptor);
+ }
+ catch (Throwable e)
+ {
+ if (isAccepting())
+ LOG.warn(e);
+ else
+ LOG.ignore(e);
+ }
+ }
+ }
+ finally
+ {
+ current.setName(name);
+
+ synchronized (AbstractConnector.this)
+ {
+ _acceptors[_acceptor] = null;
+ }
+ CountDownLatch stopping=_stopping;
+ if (stopping!=null)
+ stopping.countDown();
+ }
+ }
+ }
+
+
+
+
+// protected void connectionOpened(Connection connection)
+// {
+// _stats.connectionOpened();
+// connection.onOpen();
+// }
+//
+// protected void connectionClosed(Connection connection)
+// {
+// connection.onClose();
+// long duration = System.currentTimeMillis() - connection.getEndPoint().getCreatedTimeStamp();
+// _stats.connectionClosed(duration, connection.getMessagesIn(), connection.getMessagesOut());
+// }
+//
+// public void connectionUpgraded(Connection oldConnection, Connection newConnection)
+// {
+// oldConnection.onClose();
+// _stats.connectionUpgraded(oldConnection.getMessagesIn(), oldConnection.getMessagesOut());
+// newConnection.onOpen();
+// }
+
+ @Override
+ public Collection<EndPoint> getConnectedEndPoints()
+ {
+ return _immutableEndPoints;
+ }
+
+ protected void onEndPointOpened(EndPoint endp)
+ {
+ _endpoints.add(endp);
+ }
+
+ protected void onEndPointClosed(EndPoint endp)
+ {
+ _endpoints.remove(endp);
+ }
+
+ @Override
+ public Scheduler getScheduler()
+ {
+ return _scheduler;
+ }
+
+ @Override
+ public String getName()
+ {
+ return _name;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set a connector name. A context may be configured with
+ * virtual hosts in the form "@contextname" and will only serve
+ * requests from the named connector,
+ * @param name A connector name.
+ */
+ public void setName(String name)
+ {
+ _name=name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}",
+ _name==null?getClass().getSimpleName():_name,
+ hashCode(),
+ getDefaultProtocol());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Base implementation of the {@link RequestLog} outputs logs in the pseudo-standard NCSA common log format.
+ * Configuration options allow a choice between the standard Common Log Format (as used in the 3 log format) and the
+ * Combined Log Format (single log format). This log format can be output by most web servers, and almost all web log
+ * analysis software can understand these formats.
+ */
+public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implements RequestLog
+{
+ protected static final Logger LOG = Log.getLogger(AbstractNCSARequestLog.class);
+
+ private static ThreadLocal<StringBuilder> _buffers = new ThreadLocal<StringBuilder>()
+ {
+ @Override
+ protected StringBuilder initialValue()
+ {
+ return new StringBuilder(256);
+ }
+ };
+
+
+ private String[] _ignorePaths;
+ private boolean _extended;
+ private transient PathMap<String> _ignorePathMap;
+ private boolean _logLatency = false;
+ private boolean _logCookies = false;
+ private boolean _logServer = false;
+ private boolean _preferProxiedForAddress;
+ private transient DateCache _logDateCache;
+ private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
+ private Locale _logLocale = Locale.getDefault();
+ private String _logTimeZone = "GMT";
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * Is logging enabled
+ */
+ protected abstract boolean isEnabled();
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * Write requestEntry out. (to disk or slf4j log)
+ */
+ public abstract void write(String requestEntry) throws IOException;
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * Writes the request and response information to the output stream.
+ *
+ * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request,
+ * org.eclipse.jetty.server.Response)
+ */
+ @Override
+ public void log(Request request, Response response)
+ {
+ try
+ {
+ if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
+ return;
+
+ if (!isEnabled())
+ return;
+
+ StringBuilder buf = _buffers.get();
+ buf.setLength(0);
+
+ if (_logServer)
+ {
+ buf.append(request.getServerName());
+ buf.append(' ');
+ }
+
+ String addr = null;
+ if (_preferProxiedForAddress)
+ {
+ addr = request.getHeader(HttpHeader.X_FORWARDED_FOR.toString());
+ }
+
+ if (addr == null)
+ addr = request.getRemoteAddr();
+
+ buf.append(addr);
+ buf.append(" - ");
+ Authentication authentication = request.getAuthentication();
+ if (authentication instanceof Authentication.User)
+ buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
+ else
+ buf.append(" - ");
+
+ buf.append(" [");
+ if (_logDateCache != null)
+ buf.append(_logDateCache.format(request.getTimeStamp()));
+ else
+ buf.append(request.getTimeStamp());
+
+ buf.append("] \"");
+ buf.append(request.getMethod());
+ buf.append(' ');
+ buf.append(request.getUri().toString());
+ buf.append(' ');
+ buf.append(request.getProtocol());
+ buf.append("\" ");
+
+ int status = response.getStatus();
+ if (status <= 0)
+ status = 404;
+ buf.append((char)('0' + ((status / 100) % 10)));
+ buf.append((char)('0' + ((status / 10) % 10)));
+ buf.append((char)('0' + (status % 10)));
+
+ long responseLength = response.getLongContentLength();
+ if (responseLength >= 0)
+ {
+ buf.append(' ');
+ if (responseLength > 99999)
+ buf.append(responseLength);
+ else
+ {
+ if (responseLength > 9999)
+ buf.append((char)('0' + ((responseLength / 10000) % 10)));
+ if (responseLength > 999)
+ buf.append((char)('0' + ((responseLength / 1000) % 10)));
+ if (responseLength > 99)
+ buf.append((char)('0' + ((responseLength / 100) % 10)));
+ if (responseLength > 9)
+ buf.append((char)('0' + ((responseLength / 10) % 10)));
+ buf.append((char)('0' + (responseLength) % 10));
+ }
+ buf.append(' ');
+ }
+ else
+ buf.append(" - ");
+
+
+ if (_extended)
+ logExtended(request, response, buf);
+
+ if (_logCookies)
+ {
+ Cookie[] cookies = request.getCookies();
+ if (cookies == null || cookies.length == 0)
+ buf.append(" -");
+ else
+ {
+ buf.append(" \"");
+ for (int i = 0; i < cookies.length; i++)
+ {
+ if (i != 0)
+ buf.append(';');
+ buf.append(cookies[i].getName());
+ buf.append('=');
+ buf.append(cookies[i].getValue());
+ }
+ buf.append('\"');
+ }
+ }
+
+ if (_logLatency)
+ {
+ long now = System.currentTimeMillis();
+
+ if (_logLatency)
+ {
+ buf.append(' ');
+ buf.append(now - request.getTimeStamp());
+ }
+ }
+
+ String log = buf.toString();
+ write(log);
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * Writes extended request and response information to the output stream.
+ *
+ * @param request request object
+ * @param response response object
+ * @param b StringBuilder to write to
+ * @throws IOException
+ */
+ protected void logExtended(Request request,
+ Response response,
+ StringBuilder b) throws IOException
+ {
+ String referer = request.getHeader(HttpHeader.REFERER.toString());
+ if (referer == null)
+ b.append("\"-\" ");
+ else
+ {
+ b.append('"');
+ b.append(referer);
+ b.append("\" ");
+ }
+
+ String agent = request.getHeader(HttpHeader.USER_AGENT.toString());
+ if (agent == null)
+ b.append("\"-\" ");
+ else
+ {
+ b.append('"');
+ b.append(agent);
+ b.append('"');
+ }
+ }
+
+
+ /**
+ * Set request paths that will not be logged.
+ *
+ * @param ignorePaths array of request paths
+ */
+ public void setIgnorePaths(String[] ignorePaths)
+ {
+ _ignorePaths = ignorePaths;
+ }
+
+ /**
+ * Retrieve the request paths that will not be logged.
+ *
+ * @return array of request paths
+ */
+ public String[] getIgnorePaths()
+ {
+ return _ignorePaths;
+ }
+
+ /**
+ * Controls logging of the request cookies.
+ *
+ * @param logCookies true - values of request cookies will be logged, false - values of request cookies will not be
+ * logged
+ */
+ public void setLogCookies(boolean logCookies)
+ {
+ _logCookies = logCookies;
+ }
+
+ /**
+ * Retrieve log cookies flag
+ *
+ * @return value of the flag
+ */
+ public boolean getLogCookies()
+ {
+ return _logCookies;
+ }
+
+ /**
+ * Controls logging of the request hostname.
+ *
+ * @param logServer true - request hostname will be logged, false - request hostname will not be logged
+ */
+ public void setLogServer(boolean logServer)
+ {
+ _logServer = logServer;
+ }
+
+ /**
+ * Retrieve log hostname flag.
+ *
+ * @return value of the flag
+ */
+ public boolean getLogServer()
+ {
+ return _logServer;
+ }
+
+ /**
+ * Controls logging of request processing time.
+ *
+ * @param logLatency true - request processing time will be logged false - request processing time will not be
+ * logged
+ */
+ public void setLogLatency(boolean logLatency)
+ {
+ _logLatency = logLatency;
+ }
+
+ /**
+ * Retrieve log request processing time flag.
+ *
+ * @return value of the flag
+ */
+ public boolean getLogLatency()
+ {
+ return _logLatency;
+ }
+
+ /**
+ * @deprecated use {@link StatisticsHandler}
+ */
+ public void setLogDispatch(boolean value)
+ {
+ }
+
+ /**
+ * @deprecated use {@link StatisticsHandler}
+ */
+ public boolean isLogDispatch()
+ {
+ return false;
+ }
+
+ /**
+ * Controls whether the actual IP address of the connection or the IP address from the X-Forwarded-For header will
+ * be logged.
+ *
+ * @param preferProxiedForAddress true - IP address from header will be logged, false - IP address from the
+ * connection will be logged
+ */
+ public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
+ {
+ _preferProxiedForAddress = preferProxiedForAddress;
+ }
+
+ /**
+ * Retrieved log X-Forwarded-For IP address flag.
+ *
+ * @return value of the flag
+ */
+ public boolean getPreferProxiedForAddress()
+ {
+ return _preferProxiedForAddress;
+ }
+
+ /**
+ * Set the extended request log format flag.
+ *
+ * @param extended true - log the extended request information, false - do not log the extended request information
+ */
+ public void setExtended(boolean extended)
+ {
+ _extended = extended;
+ }
+
+ /**
+ * Retrieve the extended request log format flag.
+ *
+ * @return value of the flag
+ */
+ @ManagedAttribute("use extended NCSA format")
+ public boolean isExtended()
+ {
+ return _extended;
+ }
+
+ /**
+ * Set up request logging and open log file.
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected synchronized void doStart() throws Exception
+ {
+ if (_logDateFormat != null)
+ {
+ _logDateCache = new DateCache(_logDateFormat, _logLocale ,_logTimeZone);
+ }
+
+ if (_ignorePaths != null && _ignorePaths.length > 0)
+ {
+ _ignorePathMap = new PathMap<>();
+ for (int i = 0; i < _ignorePaths.length; i++)
+ _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]);
+ }
+ else
+ _ignorePathMap = null;
+
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ _logDateCache = null;
+ super.doStop();
+ }
+
+ /**
+ * Set the timestamp format for request log entries in the file. If this is not set, the pre-formated request
+ * timestamp is used.
+ *
+ * @param format timestamp format string
+ */
+ public void setLogDateFormat(String format)
+ {
+ _logDateFormat = format;
+ }
+
+ /**
+ * Retrieve the timestamp format string for request log entries.
+ *
+ * @return timestamp format string.
+ */
+ public String getLogDateFormat()
+ {
+ return _logDateFormat;
+ }
+
+ /**
+ * Set the locale of the request log.
+ *
+ * @param logLocale locale object
+ */
+ public void setLogLocale(Locale logLocale)
+ {
+ _logLocale = logLocale;
+ }
+
+ /**
+ * Retrieve the locale of the request log.
+ *
+ * @return locale object
+ */
+ public Locale getLogLocale()
+ {
+ return _logLocale;
+ }
+
+ /**
+ * Set the timezone of the request log.
+ *
+ * @param tz timezone string
+ */
+ public void setLogTimeZone(String tz)
+ {
+ _logTimeZone = tz;
+ }
+
+ /**
+ * Retrieve the timezone of the request log.
+ *
+ * @return timezone string
+ */
+ @ManagedAttribute("the timezone")
+ public String getLogTimeZone()
+ {
+ return _logTimeZone;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An abstract Network Connector.
+ * <p>
+ * Extends the {@link AbstractConnector} support for the {@link NetworkConnector} interface.
+ */
+@ManagedObject("AbstractNetworkConnector")
+public abstract class AbstractNetworkConnector extends AbstractConnector implements NetworkConnector
+{
+
+ private volatile String _host;
+ private volatile int _port = 0;
+
+ public AbstractNetworkConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
+ {
+ super(server,executor,scheduler,pool,acceptors,factories);
+ }
+
+ public void setHost(String host)
+ {
+ _host = host;
+ }
+
+ @Override
+ @ManagedAttribute("The network interface this connector binds to as an IP address or a hostname. If null or 0.0.0.0, then bind to all interfaces.")
+ public String getHost()
+ {
+ return _host;
+ }
+
+ public void setPort(int port)
+ {
+ _port = port;
+ }
+
+ @Override
+ @ManagedAttribute("Port this connector listens on. If set the 0 a random port is assigned which may be obtained with getLocalPort()")
+ public int getPort()
+ {
+ return _port;
+ }
+
+ @Override
+ public int getLocalPort()
+ {
+ return -1;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ open();
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ close();
+ super.doStop();
+ }
+
+ @Override
+ public void open() throws IOException
+ {
+ }
+
+ @Override
+ public void close()
+ {
+ // Interrupting is often sufficient to close the channel
+ interruptAcceptors();
+ }
+
+
+ @Override
+ public Future<Void> shutdown()
+ {
+ close();
+ return super.shutdown();
+ }
+
+ @Override
+ protected boolean isAccepting()
+ {
+ return super.isAccepting() && isOpen();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s{%s:%d}",
+ super.toString(),
+ getHost() == null ? "0.0.0.0" : getHost(),
+ getLocalPort() <= 0 ? getPort() : getLocalPort());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class AsyncContextEvent extends AsyncEvent implements Runnable
+{
+ final private Context _context;
+ final private AsyncContextState _asyncContext;
+ private volatile HttpChannelState _state;
+ private ServletContext _dispatchContext;
+ private String _dispatchPath;
+ private volatile Scheduler.Task _timeoutTask;
+ private Throwable _throwable;
+
+ public AsyncContextEvent(Context context,AsyncContextState asyncContext, HttpChannelState state, Request baseRequest, ServletRequest request, ServletResponse response)
+ {
+ super(null,request,response,null);
+ _context=context;
+ _asyncContext=asyncContext;
+ _state=state;
+
+ // If we haven't been async dispatched before
+ if (baseRequest.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
+ {
+ // We are setting these attributes during startAsync, when the spec implies that
+ // they are only available after a call to AsyncContext.dispatch(...);
+
+ // have we been forwarded before?
+ String uri=(String)baseRequest.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+ if (uri!=null)
+ {
+ baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
+ baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
+ baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
+ baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
+ baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
+ }
+ else
+ {
+ baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,baseRequest.getRequestURI());
+ baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,baseRequest.getContextPath());
+ baseRequest.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,baseRequest.getServletPath());
+ baseRequest.setAttribute(AsyncContext.ASYNC_PATH_INFO,baseRequest.getPathInfo());
+ baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getQueryString());
+ }
+ }
+ }
+
+ public ServletContext getSuspendedContext()
+ {
+ return _context;
+ }
+
+ public Context getContext()
+ {
+ return _context;
+ }
+
+ public ServletContext getDispatchContext()
+ {
+ return _dispatchContext;
+ }
+
+ public ServletContext getServletContext()
+ {
+ return _dispatchContext==null?_context:_dispatchContext;
+ }
+
+ /**
+ * @return The path in the context
+ */
+ public String getPath()
+ {
+ return _dispatchPath;
+ }
+
+ public void setTimeoutTask(Scheduler.Task task)
+ {
+ _timeoutTask = task;
+ }
+
+ public void cancelTimeoutTask()
+ {
+ Scheduler.Task task=_timeoutTask;
+ _timeoutTask=null;
+ if (task!=null)
+ task.cancel();
+ }
+
+ @Override
+ public AsyncContext getAsyncContext()
+ {
+ return _asyncContext;
+ }
+
+ @Override
+ public Throwable getThrowable()
+ {
+ return _throwable;
+ }
+
+ public void setThrowable(Throwable throwable)
+ {
+ _throwable=throwable;
+ }
+
+ public void setDispatchContext(ServletContext context)
+ {
+ _dispatchContext=context;
+ }
+
+ public void setDispatchPath(String path)
+ {
+ _dispatchPath=path;
+ }
+
+ public void completed()
+ {
+ _timeoutTask=null;
+ _asyncContext.reset();
+ }
+
+ public HttpChannelState getHttpChannelState()
+ {
+ return _state;
+ }
+
+ @Override
+ public void run()
+ {
+ Scheduler.Task task=_timeoutTask;
+ _timeoutTask=null;
+ if (task!=null)
+ _state.expired();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+
+public class AsyncContextState implements AsyncContext
+{
+ volatile HttpChannelState _state;
+
+ public AsyncContextState(HttpChannelState state)
+ {
+ _state=state;
+ }
+
+ HttpChannelState state()
+ {
+ HttpChannelState state=_state;
+ if (state==null)
+ throw new IllegalStateException("AsyncContext completed");
+ return state;
+ }
+
+ @Override
+ public void addListener(final AsyncListener listener, final ServletRequest request, final ServletResponse response)
+ {
+ AsyncListener wrap = new AsyncListener()
+ {
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ listener.onTimeout(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ listener.onStartAsync(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
+ }
+ };
+ state().addListener(wrap);
+ }
+
+ @Override
+ public void addListener(AsyncListener listener)
+ {
+ state().addListener(listener);
+ }
+
+ @Override
+ public void complete()
+ {
+ state().complete();
+ }
+
+ @Override
+ public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
+ {
+ ContextHandler contextHandler = state().getContextHandler();
+ if (contextHandler != null)
+ return contextHandler.getServletContext().createListener(clazz);
+ try
+ {
+ return clazz.newInstance();
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public void dispatch()
+ {
+ state().dispatch(null,null);
+ }
+
+ @Override
+ public void dispatch(String path)
+ {
+ state().dispatch(null,path);
+ }
+
+ @Override
+ public void dispatch(ServletContext context, String path)
+ {
+ state().dispatch(context,path);
+ }
+
+ @Override
+ public ServletRequest getRequest()
+ {
+ return state().getAsyncContextEvent().getSuppliedRequest();
+ }
+
+ @Override
+ public ServletResponse getResponse()
+ {
+ return state().getAsyncContextEvent().getSuppliedResponse();
+ }
+
+ @Override
+ public long getTimeout()
+ {
+ return state().getTimeout();
+ }
+
+ @Override
+ public boolean hasOriginalRequestAndResponse()
+ {
+ HttpChannel<?> channel=state().getHttpChannel();
+ return channel.getRequest()==getRequest() && channel.getResponse()==getResponse();
+ }
+
+ @Override
+ public void setTimeout(long arg0)
+ {
+ state().setTimeout(arg0);
+ }
+
+ @Override
+ public void start(final Runnable task)
+ {
+ state().getHttpChannel().execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ state().getAsyncContextEvent().getContext().getContextHandler().handle(task);
+ }
+ });
+ }
+
+ public void reset()
+ {
+ _state=null;
+ }
+
+ public HttpChannelState getHttpChannelState()
+ {
+ return state();
+ }
+
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * An asynchronously writing NCSA Request Log
+ */
+public class AsyncNCSARequestLog extends NCSARequestLog
+{
+ private static final Logger LOG = Log.getLogger(AsyncNCSARequestLog.class);
+ private final BlockingQueue<String> _queue;
+ private transient WriterThread _thread;
+ private boolean _warnedFull;
+
+ public AsyncNCSARequestLog()
+ {
+ this(null,null);
+ }
+
+ public AsyncNCSARequestLog(BlockingQueue<String> queue)
+ {
+ this(null,queue);
+ }
+
+ public AsyncNCSARequestLog(String filename)
+ {
+ this(filename,null);
+ }
+
+ public AsyncNCSARequestLog(String filename,BlockingQueue<String> queue)
+ {
+ super(filename);
+ if (queue==null)
+ queue=new BlockingArrayQueue<>(1024);
+ _queue=queue;
+ }
+
+ private class WriterThread extends Thread
+ {
+ WriterThread()
+ {
+ setName("AsyncNCSARequestLog@"+Integer.toString(AsyncNCSARequestLog.this.hashCode(),16));
+ }
+
+ @Override
+ public void run()
+ {
+ while (isRunning())
+ {
+ try
+ {
+ String log = _queue.poll(10,TimeUnit.SECONDS);
+ if (log!=null)
+ AsyncNCSARequestLog.super.write(log);
+
+ while(!_queue.isEmpty())
+ {
+ log=_queue.poll();
+ if (log!=null)
+ AsyncNCSARequestLog.super.write(log);
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e);
+ }
+ catch (InterruptedException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected synchronized void doStart() throws Exception
+ {
+ super.doStart();
+ _thread = new WriterThread();
+ _thread.start();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ _thread.interrupt();
+ _thread.join();
+ super.doStop();
+ _thread=null;
+ }
+
+ @Override
+ public void write(String log) throws IOException
+ {
+ if (!_queue.offer(log))
+ {
+ if (_warnedFull)
+ LOG.warn("Log Queue overflow");
+ _warnedFull=true;
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** The Authentication state of a request.
+ * <p>
+ * The Authentication state can be one of several sub-types that
+ * reflects where the request is in the many different authentication
+ * cycles. Authentication might not yet be checked or it might be checked
+ * and failed, checked and deferred or succeeded.
+ *
+ */
+public interface Authentication
+{
+ /* ------------------------------------------------------------ */
+ public static class Failed extends QuietServletException
+ {
+ public Failed(String message)
+ {
+ super(message);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** A successful Authentication with User information.
+ */
+ public interface User extends Authentication
+ {
+ String getAuthMethod();
+ UserIdentity getUserIdentity();
+ boolean isUserInRole(UserIdentity.Scope scope,String role);
+ void logout();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** A wrapped authentication with methods provide the
+ * wrapped request/response for use by the application
+ */
+ public interface Wrapped extends Authentication
+ {
+ HttpServletRequest getHttpServletRequest();
+ HttpServletResponse getHttpServletResponse();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** A deferred authentication with methods to progress
+ * the authentication process.
+ */
+ public interface Deferred extends Authentication
+ {
+ /* ------------------------------------------------------------ */
+ /** Authenticate if possible without sending a challenge.
+ * This is used to check credentials that have been sent for
+ * non-manditory authentication.
+ * @return The new Authentication state.
+ */
+ Authentication authenticate(ServletRequest request);
+
+ /* ------------------------------------------------------------ */
+ /** Authenticate and possibly send a challenge.
+ * This is used to initiate authentication for previously
+ * non-manditory authentication.
+ * @return The new Authentication state.
+ */
+ Authentication authenticate(ServletRequest request,ServletResponse response);
+
+
+ /* ------------------------------------------------------------ */
+ /** Login with the LOGIN authenticator
+ * @param username
+ * @param password
+ * @return The new Authentication state
+ */
+ Authentication login(String username,Object password,ServletRequest request);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Authentication Response sent state.
+ * Responses are sent by authenticators either to issue an
+ * authentication challenge or on successful authentication in
+ * order to redirect the user to the original URL.
+ */
+ public interface ResponseSent extends Authentication
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** An Authentication Challenge has been sent.
+ */
+ public interface Challenge extends ResponseSent
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** An Authentication Failure has been sent.
+ */
+ public interface Failure extends ResponseSent
+ {
+ }
+
+ public interface SendSuccess extends ResponseSent
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Unauthenticated state.
+ * <p>
+ * This convenience instance is for non mandatory authentication where credentials
+ * have been presented and checked, but failed authentication.
+ */
+ public final static Authentication UNAUTHENTICATED = new Authentication(){@Override
+ public String toString(){return "UNAUTHENTICATED";}};
+
+ /* ------------------------------------------------------------ */
+ /** Authentication not checked
+ * <p>
+ * This convenience instance us for non mandatory authentication when no
+ * credentials are present to be checked.
+ */
+ public final static Authentication NOT_CHECKED = new Authentication(){@Override
+ public String toString(){return "NOT CHECKED";}};
+
+ /* ------------------------------------------------------------ */
+ /** Authentication challenge sent.
+ * <p>
+ * This convenience instance is for when an authentication challenge has been sent.
+ */
+ public final static Authentication SEND_CONTINUE = new Authentication.Challenge(){@Override
+ public String toString(){return "CHALLENGE";}};
+
+ /* ------------------------------------------------------------ */
+ /** Authentication failure sent.
+ * <p>
+ * This convenience instance is for when an authentication failure has been sent.
+ */
+ public final static Authentication SEND_FAILURE = new Authentication.Failure(){@Override
+ public String toString(){return "FAILURE";}};
+ public final static Authentication SEND_SUCCESS = new SendSuccess(){@Override
+ public String toString(){return "SEND_SUCCESS";}};
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.nio.ByteBuffer;
+
+/**
+ * <p>An implementation of HttpInput using {@link ByteBuffer} as items.</p>
+ */
+public class ByteBufferQueuedHttpInput extends QueuedHttpInput<ByteBuffer>
+{
+ @Override
+ protected int remaining(ByteBuffer item)
+ {
+ return item.remaining();
+ }
+
+ @Override
+ protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+ {
+ int l = Math.min(item.remaining(), length);
+ item.get(buffer, offset, l);
+ return l;
+ }
+
+ @Override
+ protected void consume(ByteBuffer item, int length)
+ {
+ item.position(item.position()+length);
+ }
+
+ @Override
+ protected void onContentConsumed(ByteBuffer item)
+ {
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.net.URLClassLoader;
+import java.util.Collections;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+public class ClassLoaderDump implements Dumpable
+{
+ final ClassLoader _loader;
+
+ public ClassLoaderDump(ClassLoader loader)
+ {
+ _loader = loader;
+ }
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ if (_loader==null)
+ out.append("No ClassLoader\n");
+ else
+ {
+ out.append(String.valueOf(_loader)).append("\n");
+
+ Object parent = _loader.getParent();
+ if (parent != null)
+ {
+ if (!(parent instanceof Dumpable))
+ parent = new ClassLoaderDump((ClassLoader)parent);
+
+ if (_loader instanceof URLClassLoader)
+ ContainerLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
+ else
+ ContainerLifeCycle.dump(out,indent,Collections.singleton(parent));
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+
+/**
+ * <p>A Factory to create {@link Connection} instances for {@link Connector}s.</p>
+ * <p>A Connection factory is responsible for instantiating and configuring a {@link Connection} instance
+ * to handle an {@link EndPoint} accepted by a {@link Connector}.</p>
+ * <p>
+ * A ConnectionFactory has a protocol name that represents the protocol of the Connections
+ * created. Example of protocol names include:<dl>
+ * <dt>http</dt><dd>Creates a HTTP connection that can handle multiple versions of HTTP from 0.9 to 1.1</dd>
+ * <dt>spdy/2</dt><dd>Creates a HTTP connection that handles a specific version of the SPDY protocol</dd>
+ * <dt>SSL-XYZ</dt><dd>Create an SSL connection chained to a connection obtained from a connection factory
+ * with a protocol "XYZ".</dd>
+ * <dt>SSL-http</dt><dd>Create an SSL connection chained to a HTTP connection (aka https)</dd>
+ * <dt>SSL-npn</dt><dd>Create an SSL connection chained to a NPN connection, that uses a negotiation with
+ * the client to determine the next protocol.</dd>
+ * </dl>
+ */
+public interface ConnectionFactory
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @return A string representing the protocol name.
+ */
+ public String getProtocol();
+
+ /**
+ * <p>Creates a new {@link Connection} with the given parameters</p>
+ * @param connector The {@link Connector} creating this connection
+ * @param endPoint the {@link EndPoint} associated with the connection
+ * @return a new {@link Connection}
+ */
+ public Connection newConnection(Connector connector, EndPoint endPoint);
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>A {@link Connector} accept connections and data from remote peers,
+ * and allows applications to send data to remote peers, by setting up
+ * the machinery needed to handle such tasks.</p>
+ */
+@ManagedObject("Connector Interface")
+public interface Connector extends LifeCycle, Graceful
+{
+ /**
+ * @return the {@link Server} instance associated with this {@link Connector}
+ */
+ public Server getServer();
+
+ /**
+ * @return the {@link Executor} used to submit tasks
+ */
+ public Executor getExecutor();
+
+ /**
+ * @return the {@link Scheduler} used to schedule tasks
+ */
+ public Scheduler getScheduler();
+
+ /**
+ * @return the {@link ByteBufferPool} to acquire buffers from and release buffers to
+ */
+ public ByteBufferPool getByteBufferPool();
+
+ /**
+ * @return the {@link ConnectionFactory} associated with the protocol name
+ */
+ public ConnectionFactory getConnectionFactory(String nextProtocol);
+
+
+ public <T> T getConnectionFactory(Class<T> factoryType);
+
+ /**
+ * @return the default {@link ConnectionFactory} associated with the default protocol name
+ */
+ public ConnectionFactory getDefaultConnectionFactory();
+
+ public Collection<ConnectionFactory> getConnectionFactories();
+
+ public List<String> getProtocols();
+
+ /**
+ * @return the max idle timeout for connections in milliseconds
+ */
+ @ManagedAttribute("maximum time a connection can be idle before being closed (in ms)")
+ public long getIdleTimeout();
+
+ /**
+ * @return the underlying socket, channel, buffer etc. for the connector.
+ */
+ public Object getTransport();
+
+ /**
+ * @return immutable collection of connected endpoints
+ */
+ public Collection<EndPoint> getConnectedEndPoints();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the connector name if set.
+ * <p>A {@link ContextHandler} may be configured with
+ * virtual hosts in the form "@connectorName" and will only serve
+ * requests from the named connector.
+ * @return The connector name or null.
+ */
+ public String getName();
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+
+/* ------------------------------------------------------------ */
+/** A Connector.Listener that gathers Connector and Connections Statistics.
+ * Adding an instance of this class as with {@link AbstractConnector#addBean(Object)}
+ * will register the listener with all connections accepted by that connector.
+ */
+@ManagedObject("Connector Statistics")
+public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, Connection.Listener
+{
+ private final static Sample ZERO=new Sample();
+ private final AtomicLong _startMillis = new AtomicLong(-1L);
+ private final CounterStatistic _connectionStats = new CounterStatistic();
+ private final SampleStatistic _messagesIn = new SampleStatistic();
+ private final SampleStatistic _messagesOut = new SampleStatistic();
+ private final SampleStatistic _connectionDurationStats = new SampleStatistic();
+ private final ConcurrentMap<Connection, Sample> _samples = new ConcurrentHashMap<>();
+ private final AtomicInteger _closedIn = new AtomicInteger();
+ private final AtomicInteger _closedOut = new AtomicInteger();
+ private AtomicLong _nanoStamp=new AtomicLong();
+ private volatile int _messagesInPerSecond;
+ private volatile int _messagesOutPerSecond;
+
+ @Override
+ public void onOpened(Connection connection)
+ {
+ if (isStarted())
+ {
+ _connectionStats.increment();
+ _samples.put(connection,ZERO);
+ }
+ }
+
+ @Override
+ public void onClosed(Connection connection)
+ {
+ if (isStarted())
+ {
+ int msgsIn=connection.getMessagesIn();
+ int msgsOut=connection.getMessagesOut();
+ _messagesIn.set(msgsIn);
+ _messagesOut.set(msgsOut);
+ _connectionStats.decrement();
+ _connectionDurationStats.set(System.currentTimeMillis()-connection.getCreatedTimeStamp());
+
+ Sample sample=_samples.remove(connection);
+ if (sample!=null)
+ {
+ _closedIn.addAndGet(msgsIn-sample._messagesIn);
+ _closedOut.addAndGet(msgsOut-sample._messagesOut);
+ }
+ }
+ }
+
+ @ManagedAttribute("Total number of bytes received by this connector")
+ public int getBytesIn()
+ {
+ // TODO
+ return -1;
+ }
+
+ @ManagedAttribute("Total number of bytes sent by this connector")
+ public int getBytesOut()
+ {
+ // TODO
+ return -1;
+ }
+
+ @ManagedAttribute("Total number of connections seen by this connector")
+ public int getConnections()
+ {
+ return (int)_connectionStats.getTotal();
+ }
+
+ @ManagedAttribute("Connection duration maximum in ms")
+ public long getConnectionDurationMax()
+ {
+ return _connectionDurationStats.getMax();
+ }
+
+ @ManagedAttribute("Connection duration mean in ms")
+ public double getConnectionDurationMean()
+ {
+ return _connectionDurationStats.getMean();
+ }
+
+ @ManagedAttribute("Connection duration standard deviation")
+ public double getConnectionDurationStdDev()
+ {
+ return _connectionDurationStats.getStdDev();
+ }
+
+ @ManagedAttribute("Messages In for all connections")
+ public int getMessagesIn()
+ {
+ return (int)_messagesIn.getTotal();
+ }
+
+ @ManagedAttribute("Messages In per connection maximum")
+ public int getMessagesInPerConnectionMax()
+ {
+ return (int)_messagesIn.getMax();
+ }
+
+ @ManagedAttribute("Messages In per connection mean")
+ public double getMessagesInPerConnectionMean()
+ {
+ return _messagesIn.getMean();
+ }
+
+ @ManagedAttribute("Messages In per connection standard deviation")
+ public double getMessagesInPerConnectionStdDev()
+ {
+ return _messagesIn.getStdDev();
+ }
+
+ @ManagedAttribute("Connections open")
+ public int getConnectionsOpen()
+ {
+ return (int)_connectionStats.getCurrent();
+ }
+
+ @ManagedAttribute("Connections open maximum")
+ public int getConnectionsOpenMax()
+ {
+ return (int)_connectionStats.getMax();
+ }
+
+ @ManagedAttribute("Messages Out for all connections")
+ public int getMessagesOut()
+ {
+ return (int)_messagesIn.getTotal();
+ }
+
+ @ManagedAttribute("Messages In per connection maximum")
+ public int getMessagesOutPerConnectionMax()
+ {
+ return (int)_messagesIn.getMax();
+ }
+
+ @ManagedAttribute("Messages In per connection mean")
+ public double getMessagesOutPerConnectionMean()
+ {
+ return _messagesIn.getMean();
+ }
+
+ @ManagedAttribute("Messages In per connection standard deviation")
+ public double getMessagesOutPerConnectionStdDev()
+ {
+ return _messagesIn.getStdDev();
+ }
+
+ @ManagedAttribute("Connection statistics started ms since epoch")
+ public long getStartedMillis()
+ {
+ long start = _startMillis.get();
+ return start < 0 ? 0 : System.currentTimeMillis() - start;
+ }
+
+ @ManagedAttribute("Messages in per second calculated over period since last called")
+ public int getMessagesInPerSecond()
+ {
+ update();
+ return _messagesInPerSecond;
+ }
+
+ @ManagedAttribute("Messages out per second calculated over period since last called")
+ public int getMessagesOutPerSecond()
+ {
+ update();
+ return _messagesOutPerSecond;
+ }
+
+ @Override
+ public void doStart()
+ {
+ reset();
+ }
+
+ @Override
+ public void doStop()
+ {
+ _samples.clear();
+ }
+
+ @ManagedOperation("Reset the statistics")
+ public void reset()
+ {
+ _startMillis.set(System.currentTimeMillis());
+ _messagesIn.reset();
+ _messagesOut.reset();
+ _connectionStats.reset();
+ _connectionDurationStats.reset();
+ _samples.clear();
+ }
+
+ @Override
+ @ManagedOperation("dump thread state")
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ ContainerLifeCycle.dumpObject(out,this);
+ ContainerLifeCycle.dump(out,indent,Arrays.asList(new String[]{"connections="+_connectionStats,"duration="+_connectionDurationStats,"in="+_messagesIn,"out="+_messagesOut}));
+ }
+
+ public static void addToAllConnectors(Server server)
+ {
+ for (Connector connector : server.getConnectors())
+ {
+ if (connector instanceof Container)
+ ((Container)connector).addBean(new ConnectorStatistics());
+ }
+ }
+
+ private static final long SECOND_NANOS=TimeUnit.SECONDS.toNanos(1);
+ private synchronized void update()
+ {
+ long now=System.nanoTime();
+ long then=_nanoStamp.get();
+ long duration=now-then;
+
+ if (duration>SECOND_NANOS/2)
+ {
+ if (_nanoStamp.compareAndSet(then,now))
+ {
+ long msgsIn=_closedIn.getAndSet(0);
+ long msgsOut=_closedOut.getAndSet(0);
+
+ for (Map.Entry<Connection, Sample> entry : _samples.entrySet())
+ {
+ Connection connection=entry.getKey();
+ Sample sample = entry.getValue();
+ Sample next = new Sample(connection);
+ if (_samples.replace(connection,sample,next))
+ {
+ msgsIn+=next._messagesIn-sample._messagesIn;
+ msgsOut+=next._messagesOut-sample._messagesOut;
+ }
+ }
+
+ _messagesInPerSecond=(int)(msgsIn*SECOND_NANOS/duration);
+ _messagesOutPerSecond=(int)(msgsOut*SECOND_NANOS/duration);
+ }
+ }
+ }
+
+ private static class Sample
+ {
+ Sample()
+ {
+ _messagesIn=0;
+ _messagesOut=0;
+ }
+
+ Sample(Connection connection)
+ {
+ _messagesIn=connection.getMessagesIn();
+ _messagesOut=connection.getMessagesOut();
+ }
+
+ final int _messagesIn;
+ final int _messagesOut;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Cookie parser
+ * <p>Optimized stateful cookie parser. Cookies fields are added with the
+ * {@link #addCookieField(String)} method and parsed on the next subsequent
+ * call to {@link #getCookies()}.
+ * If the added fields are identical to those last added (as strings), then the
+ * cookies are not re parsed.
+ *
+ *
+ */
+public class CookieCutter
+{
+ private static final Logger LOG = Log.getLogger(CookieCutter.class);
+
+ private Cookie[] _cookies;
+ private Cookie[] _lastCookies;
+ private final List<String> _fieldList = new ArrayList<>();
+ int _fields;
+
+ public CookieCutter()
+ {
+ }
+
+ public Cookie[] getCookies()
+ {
+ if (_cookies!=null)
+ return _cookies;
+
+ if (_lastCookies!=null && _fields==_fieldList.size())
+ _cookies=_lastCookies;
+ else
+ parseFields();
+ _lastCookies=_cookies;
+ return _cookies;
+ }
+
+ public void setCookies(Cookie[] cookies)
+ {
+ _cookies=cookies;
+ _lastCookies=null;
+ _fieldList.clear();
+ _fields=0;
+ }
+
+ public void reset()
+ {
+ _cookies=null;
+ _fields=0;
+ }
+
+ public void addCookieField(String f)
+ {
+ if (f==null)
+ return;
+ f=f.trim();
+ if (f.length()==0)
+ return;
+
+ if (_fieldList.size()>_fields)
+ {
+ if (f.equals(_fieldList.get(_fields)))
+ {
+ _fields++;
+ return;
+ }
+
+ while (_fieldList.size()>_fields)
+ _fieldList.remove(_fields);
+ }
+ _cookies=null;
+ _lastCookies=null;
+ _fieldList.add(_fields++,f);
+ }
+
+
+ protected void parseFields()
+ {
+ _lastCookies=null;
+ _cookies=null;
+
+ List<Cookie> cookies = new ArrayList<>();
+
+ int version = 0;
+
+ // delete excess fields
+ while (_fieldList.size()>_fields)
+ _fieldList.remove(_fields);
+
+ // For each cookie field
+ for (String hdr : _fieldList)
+ {
+ // Parse the header
+ String name = null;
+ String value = null;
+
+ Cookie cookie = null;
+
+ boolean invalue=false;
+ boolean quoted=false;
+ boolean escaped=false;
+ int tokenstart=-1;
+ int tokenend=-1;
+ for (int i = 0, length = hdr.length(), last=length-1; i < length; i++)
+ {
+ char c = hdr.charAt(i);
+
+ // Handle quoted values for name or value
+ if (quoted)
+ {
+ if (escaped)
+ {
+ escaped=false;
+ continue;
+ }
+
+ switch (c)
+ {
+ case '"':
+ tokenend=i;
+ quoted=false;
+
+ // handle quote as last character specially
+ if (i==last)
+ {
+ if (invalue)
+ value = hdr.substring(tokenstart, tokenend+1);
+ else
+ {
+ name = hdr.substring(tokenstart, tokenend+1);
+ value = "";
+ }
+ }
+ break;
+
+ case '\\':
+ escaped=true;
+ continue;
+ default:
+ continue;
+ }
+ }
+ else
+ {
+ // Handle name and value state machines
+ if (invalue)
+ {
+ // parse the value
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ continue;
+
+ case '"':
+ if (tokenstart<0)
+ {
+ quoted=true;
+ tokenstart=i;
+ }
+ tokenend=i;
+ if (i==last)
+ {
+ value = hdr.substring(tokenstart, tokenend+1);
+ break;
+ }
+ continue;
+
+ case ';':
+ if (tokenstart>=0)
+ value = hdr.substring(tokenstart, tokenend+1);
+ else
+ value="";
+ tokenstart = -1;
+ invalue=false;
+ break;
+
+ default:
+ if (tokenstart<0)
+ tokenstart=i;
+ tokenend=i;
+ if (i==last)
+ {
+ value = hdr.substring(tokenstart, tokenend+1);
+ break;
+ }
+ continue;
+ }
+ }
+ else
+ {
+ // parse the name
+ switch (c)
+ {
+ case ' ':
+ case '\t':
+ continue;
+
+ case '"':
+ if (tokenstart<0)
+ {
+ quoted=true;
+ tokenstart=i;
+ }
+ tokenend=i;
+ if (i==last)
+ {
+ name = hdr.substring(tokenstart, tokenend+1);
+ value = "";
+ break;
+ }
+ continue;
+
+ case ';':
+ if (tokenstart>=0)
+ {
+ name = hdr.substring(tokenstart, tokenend+1);
+ value = "";
+ }
+ tokenstart = -1;
+ break;
+
+ case '=':
+ if (tokenstart>=0)
+ name = hdr.substring(tokenstart, tokenend+1);
+ tokenstart = -1;
+ invalue=true;
+ continue;
+
+ default:
+ if (tokenstart<0)
+ tokenstart=i;
+ tokenend=i;
+ if (i==last)
+ {
+ name = hdr.substring(tokenstart, tokenend+1);
+ value = "";
+ break;
+ }
+ continue;
+ }
+ }
+ }
+
+ // If after processing the current character we have a value and a name, then it is a cookie
+ if (value!=null && name!=null)
+ {
+ name=QuotedStringTokenizer.unquoteOnly(name);
+ value=QuotedStringTokenizer.unquoteOnly(value);
+
+ try
+ {
+ if (name.startsWith("$"))
+ {
+ String lowercaseName = name.toLowerCase(Locale.ENGLISH);
+ if ("$path".equals(lowercaseName))
+ {
+ if (cookie!=null)
+ cookie.setPath(value);
+ }
+ else if ("$domain".equals(lowercaseName))
+ {
+ if (cookie!=null)
+ cookie.setDomain(value);
+ }
+ else if ("$port".equals(lowercaseName))
+ {
+ if (cookie!=null)
+ cookie.setComment("$port="+value);
+ }
+ else if ("$version".equals(lowercaseName))
+ {
+ version = Integer.parseInt(value);
+ }
+ }
+ else
+ {
+ cookie = new Cookie(name, value);
+ if (version > 0)
+ cookie.setVersion(version);
+ cookies.add(cookie);
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.debug(e);
+ }
+
+ name = null;
+ value = null;
+ }
+ }
+ }
+
+ _cookies = (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
+ _lastCookies=_cookies;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.MultiMap;
+
+public class Dispatcher implements RequestDispatcher
+{
+ /** Dispatch include attribute names */
+ public final static String __INCLUDE_PREFIX="javax.servlet.include.";
+
+ /** Dispatch include attribute names */
+ public final static String __FORWARD_PREFIX="javax.servlet.forward.";
+
+ private final ContextHandler _contextHandler;
+ private final String _uri;
+ private final String _path;
+ private final String _query;
+ private final String _named;
+
+ public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query)
+ {
+ _contextHandler=contextHandler;
+ _uri=uri;
+ _path=pathInContext;
+ _query=query;
+ _named=null;
+ }
+
+ public Dispatcher(ContextHandler contextHandler, String name) throws IllegalStateException
+ {
+ _contextHandler=contextHandler;
+ _named=name;
+ _uri=null;
+ _path=null;
+ _query=null;
+ }
+
+ @Override
+ public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
+ {
+ forward(request, response, DispatcherType.FORWARD);
+ }
+
+ public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
+ {
+ forward(request, response, DispatcherType.ERROR);
+ }
+
+ @Override
+ public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
+ {
+ Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+
+ if (!(request instanceof HttpServletRequest))
+ request = new ServletRequestHttpWrapper(request);
+ if (!(response instanceof HttpServletResponse))
+ response = new ServletResponseHttpWrapper(response);
+
+ final DispatcherType old_type = baseRequest.getDispatcherType();
+ final Attributes old_attr=baseRequest.getAttributes();
+ final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
+ try
+ {
+ baseRequest.setDispatcherType(DispatcherType.INCLUDE);
+ baseRequest.getResponse().include();
+ if (_named!=null)
+ {
+ _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+ }
+ else
+ {
+ IncludeAttributes attr = new IncludeAttributes(old_attr);
+
+ attr._requestURI=_uri;
+ attr._contextPath=_contextHandler.getContextPath();
+ attr._servletPath=null; // set by ServletHandler
+ attr._pathInfo=_path;
+ attr._query=_query;
+
+ if (_query!=null)
+ baseRequest.mergeQueryParameters(_query, false);
+ baseRequest.setAttributes(attr);
+
+ _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+ }
+ }
+ finally
+ {
+ baseRequest.setAttributes(old_attr);
+ baseRequest.getResponse().included();
+ baseRequest.setQueryParameters(old_query_params);
+ baseRequest.resetParameters();
+ baseRequest.setDispatcherType(old_type);
+ }
+ }
+
+ protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException
+ {
+ Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+ Response base_response=baseRequest.getResponse();
+ base_response.resetForForward();
+
+ if (!(request instanceof HttpServletRequest))
+ request = new ServletRequestHttpWrapper(request);
+ if (!(response instanceof HttpServletResponse))
+ response = new ServletResponseHttpWrapper(response);
+
+ final boolean old_handled=baseRequest.isHandled();
+ final String old_uri=baseRequest.getRequestURI();
+ final String old_context_path=baseRequest.getContextPath();
+ final String old_servlet_path=baseRequest.getServletPath();
+ final String old_path_info=baseRequest.getPathInfo();
+ final String old_query=baseRequest.getQueryString();
+ final MultiMap<String> old_query_params=baseRequest.getQueryParameters();
+ final Attributes old_attr=baseRequest.getAttributes();
+ final DispatcherType old_type=baseRequest.getDispatcherType();
+
+ try
+ {
+ baseRequest.setHandled(false);
+ baseRequest.setDispatcherType(dispatch);
+
+ if (_named!=null)
+ {
+ _contextHandler.handle(_named, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+ }
+ else
+ {
+ ForwardAttributes attr = new ForwardAttributes(old_attr);
+
+ //If we have already been forwarded previously, then keep using the established
+ //original value. Otherwise, this is the first forward and we need to establish the values.
+ //Note: the established value on the original request for pathInfo and
+ //for queryString is allowed to be null, but cannot be null for the other values.
+ if (old_attr.getAttribute(FORWARD_REQUEST_URI) != null)
+ {
+ attr._pathInfo=(String)old_attr.getAttribute(FORWARD_PATH_INFO);
+ attr._query=(String)old_attr.getAttribute(FORWARD_QUERY_STRING);
+ attr._requestURI=(String)old_attr.getAttribute(FORWARD_REQUEST_URI);
+ attr._contextPath=(String)old_attr.getAttribute(FORWARD_CONTEXT_PATH);
+ attr._servletPath=(String)old_attr.getAttribute(FORWARD_SERVLET_PATH);
+ }
+ else
+ {
+ attr._pathInfo=old_path_info;
+ attr._query=old_query;
+ attr._requestURI=old_uri;
+ attr._contextPath=old_context_path;
+ attr._servletPath=old_servlet_path;
+ }
+
+ baseRequest.setRequestURI(_uri);
+ baseRequest.setContextPath(_contextHandler.getContextPath());
+ baseRequest.setServletPath(null);
+ baseRequest.setPathInfo(_uri);
+ if (_query!=null)
+ baseRequest.mergeQueryParameters(_query, true);
+ baseRequest.setAttributes(attr);
+
+ _contextHandler.handle(_path, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+
+ if (!baseRequest.getHttpChannelState().isAsync())
+ commitResponse(response,baseRequest);
+ }
+ }
+ finally
+ {
+ baseRequest.setHandled(old_handled);
+ baseRequest.setRequestURI(old_uri);
+ baseRequest.setContextPath(old_context_path);
+ baseRequest.setServletPath(old_servlet_path);
+ baseRequest.setPathInfo(old_path_info);
+ baseRequest.setQueryString(old_query);
+ baseRequest.setQueryParameters(old_query_params);
+ baseRequest.resetParameters();
+ baseRequest.setAttributes(old_attr);
+ baseRequest.setDispatcherType(old_type);
+ }
+ }
+
+ private void commitResponse(ServletResponse response, Request baseRequest) throws IOException
+ {
+ if (baseRequest.getResponse().isWriting())
+ {
+ try
+ {
+ response.getWriter().close();
+ }
+ catch (IllegalStateException e)
+ {
+ response.getOutputStream().close();
+ }
+ }
+ else
+ {
+ try
+ {
+ response.getOutputStream().close();
+ }
+ catch (IllegalStateException e)
+ {
+ response.getWriter().close();
+ }
+ }
+ }
+
+ private class ForwardAttributes implements Attributes
+ {
+ final Attributes _attr;
+
+ String _requestURI;
+ String _contextPath;
+ String _servletPath;
+ String _pathInfo;
+ String _query;
+
+ ForwardAttributes(Attributes attributes)
+ {
+ _attr=attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ 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;
+ }
+
+ if (key.startsWith(__INCLUDE_PREFIX))
+ return null;
+
+ return _attr.getAttribute(key);
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ HashSet<String> set=new HashSet<>();
+ Enumeration<String> e=_attr.getAttributeNames();
+ while(e.hasMoreElements())
+ {
+ String name=e.nextElement();
+ if (!name.startsWith(__INCLUDE_PREFIX) &&
+ !name.startsWith(__FORWARD_PREFIX))
+ set.add(name);
+ }
+
+ if (_named==null)
+ {
+ if (_pathInfo!=null)
+ set.add(FORWARD_PATH_INFO);
+ else
+ set.remove(FORWARD_PATH_INFO);
+ set.add(FORWARD_REQUEST_URI);
+ set.add(FORWARD_SERVLET_PATH);
+ set.add(FORWARD_CONTEXT_PATH);
+ if (_query!=null)
+ set.add(FORWARD_QUERY_STRING);
+ else
+ set.remove(FORWARD_QUERY_STRING);
+ }
+
+ return Collections.enumeration(set);
+ }
+
+ @Override
+ 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);
+ }
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "FORWARD+"+_attr.toString();
+ }
+
+ @Override
+ public void clearAttributes()
+ {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public void removeAttribute(String name)
+ {
+ setAttribute(name,null);
+ }
+ }
+
+ private class IncludeAttributes implements Attributes
+ {
+ final Attributes _attr;
+
+ String _requestURI;
+ String _contextPath;
+ String _servletPath;
+ String _pathInfo;
+ String _query;
+
+ IncludeAttributes(Attributes attributes)
+ {
+ _attr=attributes;
+ }
+
+ @Override
+ 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;
+ }
+ else if (key.startsWith(__INCLUDE_PREFIX))
+ return null;
+
+
+ return _attr.getAttribute(key);
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ HashSet<String> set=new HashSet<>();
+ Enumeration<String> e=_attr.getAttributeNames();
+ while(e.hasMoreElements())
+ {
+ String name=e.nextElement();
+ if (!name.startsWith(__INCLUDE_PREFIX))
+ set.add(name);
+ }
+
+ if (_named==null)
+ {
+ if (_pathInfo!=null)
+ set.add(INCLUDE_PATH_INFO);
+ else
+ set.remove(INCLUDE_PATH_INFO);
+ set.add(INCLUDE_REQUEST_URI);
+ set.add(INCLUDE_SERVLET_PATH);
+ set.add(INCLUDE_CONTEXT_PATH);
+ if (_query!=null)
+ set.add(INCLUDE_QUERY_STRING);
+ else
+ set.remove(INCLUDE_QUERY_STRING);
+ }
+
+ return Collections.enumeration(set);
+ }
+
+ @Override
+ 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);
+ }
+ else if (value==null)
+ _attr.removeAttribute(key);
+ else
+ _attr.setAttribute(key,value);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "INCLUDE+"+_attr.toString();
+ }
+
+ @Override
+ public void clearAttributes()
+ {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public void removeAttribute(String name)
+ {
+ setAttribute(name,null);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+/**
+ *
+ */
+public class EncodingHttpWriter extends HttpWriter
+{
+ final Writer _converter;
+
+ /* ------------------------------------------------------------ */
+ public EncodingHttpWriter(HttpOutput out, String encoding)
+ {
+ super(out);
+ try
+ {
+ _converter = new OutputStreamWriter(_bytes, encoding);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (char[] s,int offset, int length) throws IOException
+ {
+ HttpOutput out = _out;
+ if (length==0 && out.isAllContentWritten())
+ {
+ out.close();
+ return;
+ }
+
+ while (length > 0)
+ {
+ _bytes.reset();
+ int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+ _converter.write(s, offset, chars);
+ _converter.flush();
+ _bytes.writeTo(out);
+ length-=chars;
+ offset+=chars;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.net.InetSocketAddress;
+
+import javax.servlet.ServletRequest;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.server.HttpConfiguration.Customizer;
+
+
+/* ------------------------------------------------------------ */
+/** Customize Requests for Proxy Forwarding.
+ * <p>
+ * This customizer looks at at HTTP request for headers that indicate
+ * it has been forwarded by one or more proxies. Specifically handled are:
+ * <ul>
+ * <li>X-Forwarded-Host</li>
+ * <li>X-Forwarded-Server</li>
+ * <li>X-Forwarded-For</li>
+ * <li>X-Forwarded-Proto</li>
+ * </ul>
+ * <p>If these headers are present, then the {@link Request} object is updated
+ * so that the proxy is not seen as the other end point of the connection on which
+ * the request came</p>
+ * <p>Headers can also be defined so that forwarded SSL Session IDs and Cipher
+ * suites may be customised</p>
+ * @see <a href="http://en.wikipedia.org/wiki/X-Forwarded-For">Wikipedia: X-Forwarded-For</a>
+ */
+public class ForwardedRequestCustomizer implements Customizer
+{
+ private String _hostHeader;
+ private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
+ private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
+ private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
+ private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
+ private String _forwardedCipherSuiteHeader;
+ private String _forwardedSslSessionIdHeader;
+
+
+ /* ------------------------------------------------------------ */
+ public String getHostHeader()
+ {
+ return _hostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
+ *
+ * @param hostHeader
+ * The value of the host header to force.
+ */
+ public void setHostHeader(String hostHeader)
+ {
+ _hostHeader = hostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ *
+ * @see #setForwarded(boolean)
+ */
+ public String getForwardedHostHeader()
+ {
+ return _forwardedHostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedHostHeader
+ * The header name for forwarded hosts (default x-forwarded-host)
+ */
+ public void setForwardedHostHeader(String forwardedHostHeader)
+ {
+ _forwardedHostHeader = forwardedHostHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the header name for forwarded server.
+ */
+ public String getForwardedServerHeader()
+ {
+ return _forwardedServerHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedServerHeader
+ * The header name for forwarded server (default x-forwarded-server)
+ */
+ public void setForwardedServerHeader(String forwardedServerHeader)
+ {
+ _forwardedServerHeader = forwardedServerHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the forwarded for header
+ */
+ public String getForwardedForHeader()
+ {
+ return _forwardedForHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedRemoteAddressHeader
+ * The header name for forwarded for (default x-forwarded-for)
+ */
+ public void setForwardedForHeader(String forwardedRemoteAddressHeader)
+ {
+ _forwardedForHeader = forwardedRemoteAddressHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the forwardedProtoHeader.
+ *
+ * @return the forwardedProtoHeader (default X-Forwarded-For)
+ */
+ public String getForwardedProtoHeader()
+ {
+ return _forwardedProtoHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the forwardedProtoHeader.
+ *
+ * @param forwardedProtoHeader
+ * the forwardedProtoHeader to set (default X-Forwarded-For)
+ */
+ public void setForwardedProtoHeader(String forwardedProtoHeader)
+ {
+ _forwardedProtoHeader = forwardedProtoHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The header name holding a forwarded cipher suite (default null)
+ */
+ public String getForwardedCipherSuiteHeader()
+ {
+ return _forwardedCipherSuiteHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedCipherSuite
+ * The header name holding a forwarded cipher suite (default null)
+ */
+ public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
+ {
+ _forwardedCipherSuiteHeader = forwardedCipherSuite;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The header name holding a forwarded SSL Session ID (default null)
+ */
+ public String getForwardedSslSessionIdHeader()
+ {
+ return _forwardedSslSessionIdHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forwardedSslSessionId
+ * The header name holding a forwarded SSL Session ID (default null)
+ */
+ public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
+ {
+ _forwardedSslSessionIdHeader = forwardedSslSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void customize(Connector connector, HttpConfiguration config, Request request)
+ {
+ HttpFields httpFields = request.getHttpFields();
+
+ // Do SSL first
+ if (getForwardedCipherSuiteHeader()!=null)
+ {
+ String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader());
+ if (cipher_suite!=null)
+ request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite);
+ }
+ if (getForwardedSslSessionIdHeader()!=null)
+ {
+ String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader());
+ if(ssl_session_id!=null)
+ {
+ request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
+ request.setScheme(HttpScheme.HTTPS.asString());
+ }
+ }
+
+ // Retrieving headers from the request
+ String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader());
+ String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader());
+ String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader());
+ String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader());
+
+ if (_hostHeader != null)
+ {
+ // Update host header
+ httpFields.put(HttpHeader.HOST.toString(),_hostHeader);
+ request.setServerName(null);
+ request.setServerPort(-1);
+ request.getServerName();
+ }
+ else if (forwardedHost != null)
+ {
+ // Update host header
+ httpFields.put(HttpHeader.HOST.toString(),forwardedHost);
+ request.setServerName(null);
+ request.setServerPort(-1);
+ request.getServerName();
+ }
+ else if (forwardedServer != null)
+ {
+ // Use provided server name
+ request.setServerName(forwardedServer);
+ }
+
+ if (forwardedFor != null)
+ {
+ request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor,request.getRemotePort()));
+ }
+
+ if (forwardedProto != null)
+ {
+ request.setScheme(forwardedProto);
+ if (forwardedProto.equals(config.getSecureScheme()))
+ request.setSecure(true);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected String getLeftMostFieldValue(HttpFields fields, String header)
+ {
+ if (header == null)
+ return null;
+
+ String headerValue = fields.getStringField(header);
+
+ if (headerValue == null)
+ return null;
+
+ int commaIndex = headerValue.indexOf(',');
+
+ if (commaIndex == -1)
+ {
+ // Single value
+ return headerValue;
+ }
+
+ // The left-most value is the farthest downstream client
+ return headerValue.substring(0,commaIndex);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A Jetty Server Handler.
+ *
+ * A Handler instance is required by a {@link Server} to handle incoming
+ * HTTP requests. A Handler may: <ul>
+ * <li>Completely generate the HTTP Response</li>
+ * <li>Examine/modify the request and call another Handler (see {@link HandlerWrapper}).
+ * <li>Pass the request to one or more other Handlers (see {@link HandlerCollection}).
+ * </ul>
+ *
+ * Handlers are passed the servlet API request and response object, but are
+ * not Servlets. The servlet container is implemented by handlers for
+ * context, security, session and servlet that modify the request object
+ * before passing it to the next stage of handling.
+ *
+ */
+@ManagedObject("Jetty Handler")
+public interface Handler extends LifeCycle, Destroyable
+{
+ /* ------------------------------------------------------------ */
+ /** Handle a request.
+ * @param target The target of the request - either a URI or a name.
+ * @param baseRequest The original unwrapped request object.
+ * @param request The request either as the {@link Request}
+ * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
+ * method can be used access the Request object if required.
+ * @param response The response as the {@link Response}
+ * object or a wrapper of that request. The {@link HttpChannel#getCurrentHttpChannel()}
+ * method can be used access the Response object if required.
+ * @throws IOException
+ * @throws ServletException
+ */
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException;
+
+ public void setServer(Server server);
+
+ @ManagedAttribute(value="the jetty server for this handler", readonly=true)
+ public Server getServer();
+
+ @ManagedOperation(value="destroy associated resources", impact="ACTION")
+ public void destroy();
+
+}
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/**
+ * A Handler that contains other Handlers.
+ * <p>
+ * The contained handlers may be one (see @{link {@link org.eclipse.jetty.server.handler.HandlerWrapper})
+ * or many (see {@link org.eclipse.jetty.server.handler.HandlerList} or {@link org.eclipse.jetty.server.handler.HandlerCollection}.
+ *
+ */
+@ManagedObject("Handler of Multiple Handlers")
+public interface HandlerContainer extends LifeCycle
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @return array of handlers directly contained by this handler.
+ */
+ @ManagedAttribute("handlers in this container")
+ public Handler[] getHandlers();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return array of all handlers contained by this handler and it's children
+ */
+ @ManagedAttribute("all contained handlers")
+ public Handler[] getChildHandlers();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param byclass
+ * @return array of all handlers contained by this handler and it's children of the passed type.
+ */
+ public Handler[] getChildHandlersByClass(Class<?> byclass);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param byclass
+ * @return first handler of all handlers contained by this handler and it's children of the passed type.
+ */
+ public <T extends Handler> T getChildHandlerByClass(Class<T> byclass);
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Customizes requests that lack the {@code Host} header (for example, HTTP 1.0 requests).
+ * <p />
+ * In case of HTTP 1.0 requests that lack the {@code Host} header, the application may issue
+ * a redirect, and the {@code Location} header is usually constructed from the {@code Host}
+ * header; if the {@code Host} header is missing, the server may query the connector for its
+ * IP address in order to construct the {@code Location} header, and thus leak to clients
+ * internal IP addresses.
+ * <p />
+ * This {@link HttpConfiguration.Customizer} is configured with a {@code serverName} and
+ * optionally a {@code serverPort}.
+ * If the {@code Host} header is absent, the configured {@code serverName} will be set on
+ * the request so that {@link HttpServletRequest#getServerName()} will return that value,
+ * and likewise for {@code serverPort} and {@link HttpServletRequest#getServerPort()}.
+ */
+public class HostHeaderCustomizer implements HttpConfiguration.Customizer
+{
+ private final String serverName;
+ private final int serverPort;
+
+ /**
+ * @param serverName the {@code serverName} to set on the request (the {@code serverPort} will not be set)
+ */
+ public HostHeaderCustomizer(String serverName)
+ {
+ this(serverName, 0);
+ }
+
+ /**
+ * @param serverName the {@code serverName} to set on the request
+ * @param serverPort the {@code serverPort} to set on the request
+ */
+ public HostHeaderCustomizer(String serverName, int serverPort)
+ {
+ this.serverName = Objects.requireNonNull(serverName);
+ this.serverPort = serverPort;
+ }
+
+ @Override
+ public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+ {
+ if (request.getHeader("Host") == null)
+ {
+ request.setServerName(serverName);
+ if (serverPort > 0)
+ request.setServerPort(serverPort);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.server.HttpChannelState.Action;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/** HttpChannel.
+ * Represents a single endpoint for HTTP semantic processing.
+ * The HttpChannel is both a HttpParser.RequestHandler, where it passively receives events from
+ * an incoming HTTP request, and a Runnable, where it actively takes control of the request/response
+ * life cycle and calls the application (perhaps suspending and resuming with multiple calls to run).
+ * The HttpChannel signals the switch from passive mode to active mode by returning true to one of the
+ * HttpParser.RequestHandler callbacks. The completion of the active phase is signalled by a call to
+ * HttpTransport.completed().
+ *
+ */
+public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable, HttpParser.ProxyHandler
+{
+ private static final Logger LOG = Log.getLogger(HttpChannel.class);
+ private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
+
+ /* ------------------------------------------------------------ */
+ /** Get the current channel that this thread is dispatched to.
+ * @see Request#getAttribute(String) for a more general way to access the HttpChannel
+ * @return the current HttpChannel or null
+ */
+ public static HttpChannel<?> getCurrentHttpChannel()
+ {
+ return __currentChannel.get();
+ }
+
+ protected static HttpChannel<?> setCurrentHttpChannel(HttpChannel<?> channel)
+ {
+ HttpChannel<?> last=__currentChannel.get();
+ if (channel==null)
+ __currentChannel.remove();
+ else
+ __currentChannel.set(channel);
+ return last;
+ }
+
+ private final AtomicBoolean _committed = new AtomicBoolean();
+ private final AtomicInteger _requests = new AtomicInteger();
+ private final Connector _connector;
+ private final HttpConfiguration _configuration;
+ private final EndPoint _endPoint;
+ private final HttpTransport _transport;
+ private final HttpURI _uri;
+ private final HttpChannelState _state;
+ private final Request _request;
+ private final Response _response;
+ private HttpVersion _version = HttpVersion.HTTP_1_1;
+ private boolean _expect = false;
+ private boolean _expect100Continue = false;
+ private boolean _expect102Processing = false;
+
+ public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<T> input)
+ {
+ _connector = connector;
+ _configuration = configuration;
+ _endPoint = endPoint;
+ _transport = transport;
+
+ _uri = new HttpURI(URIUtil.__CHARSET);
+ _state = new HttpChannelState(this);
+ input.init(_state);
+ _request = new Request(this, input);
+ _response = new Response(this, new HttpOutput(this));
+ }
+
+ public HttpChannelState getState()
+ {
+ return _state;
+ }
+
+ public HttpVersion getHttpVersion()
+ {
+ return _version;
+ }
+ /**
+ * @return the number of requests handled by this connection
+ */
+ public int getRequests()
+ {
+ return _requests.get();
+ }
+
+ public Connector getConnector()
+ {
+ return _connector;
+ }
+
+ public HttpTransport getHttpTransport()
+ {
+ return _transport;
+ }
+
+ public ByteBufferPool getByteBufferPool()
+ {
+ return _connector.getByteBufferPool();
+ }
+
+ public HttpConfiguration getHttpConfiguration()
+ {
+ return _configuration;
+ }
+
+ public Server getServer()
+ {
+ return _connector.getServer();
+ }
+
+ public Request getRequest()
+ {
+ return _request;
+ }
+
+ public Response getResponse()
+ {
+ return _response;
+ }
+
+ public EndPoint getEndPoint()
+ {
+ return _endPoint;
+ }
+
+ public InetSocketAddress getLocalAddress()
+ {
+ return _endPoint.getLocalAddress();
+ }
+
+ public InetSocketAddress getRemoteAddress()
+ {
+ return _endPoint.getRemoteAddress();
+ }
+
+ @Override
+ public int getHeaderCacheSize()
+ {
+ return _configuration.getHeaderCacheSize();
+ }
+
+ /**
+ * If the associated response has the Expect header set to 100 Continue,
+ * then accessing the input stream indicates that the handler/servlet
+ * is ready for the request body and thus a 100 Continue response is sent.
+ *
+ * @throws IOException if the InputStream cannot be created
+ */
+ public void continue100(int available) throws IOException
+ {
+ // If the client is expecting 100 CONTINUE, then send it now.
+ // TODO: consider using an AtomicBoolean ?
+ if (isExpecting100Continue())
+ {
+ _expect100Continue = false;
+
+ // is content missing?
+ if (available == 0)
+ {
+ if (_response.isCommitted())
+ throw new IOException("Committed before 100 Continues");
+
+ // TODO: break this dependency with HttpGenerator
+ boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
+ if (!committed)
+ throw new IOException("Concurrent commit while trying to send 100-Continue");
+ }
+ }
+ }
+
+ public void reset()
+ {
+ _committed.set(false);
+ _expect = false;
+ _expect100Continue = false;
+ _expect102Processing = false;
+ _request.recycle();
+ _response.recycle();
+ _uri.clear();
+ }
+
+ @Override
+ public void run()
+ {
+ handle();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if the channel is ready to continue handling (ie it is not suspended)
+ */
+ public boolean handle()
+ {
+ LOG.debug("{} handle enter", this);
+
+ final HttpChannel<?>last = setCurrentHttpChannel(this);
+
+ String threadName = null;
+ if (LOG.isDebugEnabled())
+ {
+ threadName = Thread.currentThread().getName();
+ Thread.currentThread().setName(threadName + " - " + _uri);
+ }
+
+ HttpChannelState.Action action = _state.handling();
+ try
+ {
+ // Loop here to handle async request redispatches.
+ // The loop is controlled by the call to async.unhandle in the
+ // finally block below. Unhandle will return false only if an async dispatch has
+ // already happened when unhandle is called.
+ loop: while (action.ordinal()<HttpChannelState.Action.WAIT.ordinal() && getServer().isRunning())
+ {
+ boolean error=false;
+ try
+ {
+ LOG.debug("{} action {}",this,action);
+
+ switch(action)
+ {
+ case REQUEST_DISPATCH:
+ _request.setHandled(false);
+ _response.getHttpOutput().reopen();
+ _request.setDispatcherType(DispatcherType.REQUEST);
+
+ for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
+ customizer.customize(getConnector(),_configuration,_request);
+ getServer().handle(this);
+ break;
+
+ case ASYNC_DISPATCH:
+ _request.setHandled(false);
+ _response.getHttpOutput().reopen();
+ _request.setDispatcherType(DispatcherType.ASYNC);
+ getServer().handleAsync(this);
+ break;
+
+ case ASYNC_EXPIRED:
+ _request.setHandled(false);
+ _response.getHttpOutput().reopen();
+ _request.setDispatcherType(DispatcherType.ERROR);
+
+ Throwable ex=_state.getAsyncContextEvent().getThrowable();
+ String reason="Async Timeout";
+ if (ex!=null)
+ {
+ reason="Async Exception";
+ _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
+ }
+ _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
+ _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,reason);
+ _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
+
+ _response.setStatusWithReason(500,reason);
+
+
+ ErrorHandler eh = ErrorHandler.getErrorHandler(getServer(),_state.getContextHandler());
+ if (eh instanceof ErrorHandler.ErrorPageMapper)
+ {
+ String error_page=((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest());
+ if (error_page!=null)
+ _state.getAsyncContextEvent().setDispatchPath(error_page);
+ }
+
+ getServer().handleAsync(this);
+ break;
+
+ case READ_CALLBACK:
+ {
+ ContextHandler handler=_state.getContextHandler();
+ if (handler!=null)
+ handler.handle(_request.getHttpInput());
+ else
+ _request.getHttpInput().run();
+ break;
+ }
+
+ case WRITE_CALLBACK:
+ {
+ ContextHandler handler=_state.getContextHandler();
+
+ if (handler!=null)
+ handler.handle(_response.getHttpOutput());
+ else
+ _response.getHttpOutput().run();
+ break;
+ }
+
+ default:
+ break loop;
+
+ }
+ }
+ catch (Error e)
+ {
+ if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
+ LOG.ignore(e);
+ else
+ {
+ error=true;
+ throw e;
+ }
+ }
+ catch (Exception e)
+ {
+ error=true;
+ if (e instanceof EofException)
+ LOG.debug(e);
+ else
+ LOG.warn(String.valueOf(_uri), e);
+ _state.error(e);
+ _request.setHandled(true);
+ handleException(e);
+ }
+ finally
+ {
+ if (error && _state.isAsyncStarted())
+ _state.errorComplete();
+ action = _state.unhandle();
+ }
+ }
+
+ }
+ finally
+ {
+ setCurrentHttpChannel(last);
+ if (threadName != null && LOG.isDebugEnabled())
+ Thread.currentThread().setName(threadName);
+ }
+
+ if (action==Action.COMPLETE)
+ {
+ try
+ {
+ _state.completed();
+
+ if (!_response.isCommitted() && !_request.isHandled())
+ _response.sendError(404);
+ else
+ // Complete generating the response
+ _response.closeOutput();
+ }
+ catch(EofException|ClosedChannelException e)
+ {
+ LOG.debug(e);
+ }
+ catch(Exception e)
+ {
+ LOG.warn("complete failed",e);
+ }
+ finally
+ {
+ _request.setHandled(true);
+ _transport.completed();
+ }
+ }
+
+ LOG.debug("{} handle exit, result {}", this, action);
+
+ return action!=Action.WAIT;
+ }
+
+ /**
+ * <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
+ * to avoid concurrent writes from the application.</p>
+ * <p>It may happen that the application suspends, and then throws an exception, while an application
+ * spawned thread writes the response content; in such case, we attempt to commit the error directly
+ * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
+ *
+ * @param x the Throwable that caused the problem
+ */
+ protected void handleException(Throwable x)
+ {
+ try
+ {
+ _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,x);
+ _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,x.getClass());
+ if (_state.isSuspended())
+ {
+ HttpFields fields = new HttpFields();
+ fields.add(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+ ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
+ boolean committed = sendResponse(info, null, true);
+ if (!committed)
+ LOG.warn("Could not send response error 500: "+x);
+ _request.getAsyncContext().complete();
+ }
+ else if (isCommitted())
+ {
+ _transport.abort();
+ if (!(x instanceof EofException))
+ LOG.warn("Could not send response error 500: "+x);
+ }
+ else
+ {
+ _response.setHeader(HttpHeader.CONNECTION.asString(),HttpHeaderValue.CLOSE.asString());
+ _response.sendError(500, x.getMessage());
+ }
+ }
+ catch (IOException e)
+ {
+ // We tried our best, just log
+ LOG.debug("Could not commit response error 500", e);
+ }
+ }
+
+ public boolean isExpecting100Continue()
+ {
+ return _expect100Continue;
+ }
+
+ public boolean isExpecting102Processing()
+ {
+ return _expect102Processing;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{r=%s,a=%s,uri=%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ _requests,
+ _state.getState(),
+ _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
+ );
+ }
+
+ @Override
+ public void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort)
+ {
+ _request.setAttribute("PROXY", protocol);
+ _request.setServerName(sAddr);
+ _request.setServerPort(dPort);
+ _request.setRemoteAddr(InetSocketAddress.createUnresolved(sAddr,sPort));
+ }
+
+ @Override
+ public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
+ {
+ _expect = false;
+ _expect100Continue = false;
+ _expect102Processing = false;
+
+ _request.setTimeStamp(System.currentTimeMillis());
+ _request.setMethod(httpMethod, method);
+
+ if (httpMethod == HttpMethod.CONNECT)
+ _uri.parseConnect(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+ else
+ _uri.parse(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
+ _request.setUri(_uri);
+
+ String path;
+ try
+ {
+ path = _uri.getDecodedPath();
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
+ LOG.ignore(e);
+ path = _uri.getDecodedPath(StandardCharsets.ISO_8859_1);
+ }
+
+ String info = URIUtil.canonicalPath(path);
+
+ if (info == null)
+ {
+ if( path==null && _uri.getScheme()!=null &&_uri.getHost()!=null)
+ {
+ info = "/";
+ _request.setRequestURI("");
+ }
+ else
+ {
+ badMessage(400,null);
+ return true;
+ }
+ }
+ _request.setPathInfo(info);
+ _version = version == null ? HttpVersion.HTTP_0_9 : version;
+ _request.setHttpVersion(_version);
+
+ return false;
+ }
+
+ @Override
+ public boolean parsedHeader(HttpField field)
+ {
+ HttpHeader header=field.getHeader();
+ String value=field.getValue();
+ if (value == null)
+ value = "";
+ if (header != null)
+ {
+ switch (header)
+ {
+ case EXPECT:
+ if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+ {
+ HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
+ switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
+ {
+ case CONTINUE:
+ _expect100Continue = true;
+ break;
+
+ case PROCESSING:
+ _expect102Processing = true;
+ break;
+
+ default:
+ String[] values = value.split(",");
+ for (int i = 0; values != null && i < values.length; i++)
+ {
+ expect = HttpHeaderValue.CACHE.get(values[i].trim());
+ if (expect == null)
+ _expect = true;
+ else
+ {
+ switch (expect)
+ {
+ case CONTINUE:
+ _expect100Continue = true;
+ break;
+ case PROCESSING:
+ _expect102Processing = true;
+ break;
+ default:
+ _expect = true;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case CONTENT_TYPE:
+ MimeTypes.Type mime = MimeTypes.CACHE.get(value);
+ String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(value) : mime.getCharset().toString();
+ if (charset != null)
+ _request.setCharacterEncodingUnchecked(charset);
+ break;
+ default:
+ }
+ }
+
+ if (field.getName()!=null)
+ _request.getHttpFields().add(field);
+ return false;
+ }
+
+ @Override
+ public boolean parsedHostHeader(String host, int port)
+ {
+ if (_uri.getHost()==null)
+ {
+ _request.setServerName(host);
+ _request.setServerPort(port);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean headerComplete()
+ {
+ _requests.incrementAndGet();
+ switch (_version)
+ {
+ case HTTP_0_9:
+ break;
+
+ case HTTP_1_0:
+ if (_configuration.getSendDateHeader())
+ _response.getHttpFields().put(_connector.getServer().getDateField());
+ break;
+
+ case HTTP_1_1:
+ if (_configuration.getSendDateHeader())
+ _response.getHttpFields().put(_connector.getServer().getDateField());
+
+ if (_expect)
+ {
+ badMessage(HttpStatus.EXPECTATION_FAILED_417,null);
+ return true;
+ }
+
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean content(T item)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} content {}", this, item);
+ @SuppressWarnings("unchecked")
+ HttpInput<T> input = (HttpInput<T>)_request.getHttpInput();
+ input.content(item);
+
+ return false;
+ }
+
+ @Override
+ public boolean messageComplete()
+ {
+ LOG.debug("{} messageComplete", this);
+ _request.getHttpInput().messageComplete();
+ return true;
+ }
+
+ @Override
+ public void earlyEOF()
+ {
+ _request.getHttpInput().earlyEOF();
+ }
+
+ @Override
+ public void badMessage(int status, String reason)
+ {
+ if (status < 400 || status > 599)
+ status = HttpStatus.BAD_REQUEST_400;
+
+ try
+ {
+ if (_state.handling()==Action.REQUEST_DISPATCH)
+ {
+ ByteBuffer content=null;
+ HttpFields fields=new HttpFields();
+
+ ErrorHandler handler=getServer().getBean(ErrorHandler.class);
+ if (handler!=null)
+ content=handler.badMessageError(status,reason,fields);
+
+ sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,fields,0,status,reason,false),content ,true);
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ finally
+ {
+ if (_state.unhandle()==Action.COMPLETE)
+ _state.completed();
+ else
+ throw new IllegalStateException();
+ }
+ }
+
+ protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback)
+ {
+ // TODO check that complete only set true once by changing _committed to AtomicRef<Enum>
+ boolean committing = _committed.compareAndSet(false, true);
+ if (committing)
+ {
+ // We need an info to commit
+ if (info==null)
+ info = _response.newResponseInfo();
+
+ // wrap callback to process 100 or 500 responses
+ final int status=info.getStatus();
+ final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback);
+
+ // committing write
+ _transport.send(info, content, complete, committed);
+ }
+ else if (info==null)
+ {
+ // This is a normal write
+ _transport.send(content, complete, callback);
+ }
+ else
+ {
+ callback.failed(new IllegalStateException("committed"));
+ }
+ return committing;
+ }
+
+ protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
+ {
+ try(Blocker blocker = _response.getHttpOutput().acquireWriteBlockingCallback())
+ {
+ boolean committing = sendResponse(info,content,complete,blocker);
+ blocker.block();
+ return committing;
+ }
+ }
+
+ public boolean isCommitted()
+ {
+ return _committed.get();
+ }
+
+ /**
+ * <p>Non-Blocking write, committing the response if needed.</p>
+ *
+ * @param content the content buffer to write
+ * @param complete whether the content is complete for the response
+ * @param callback Callback when complete or failed
+ */
+ protected void write(ByteBuffer content, boolean complete, Callback callback)
+ {
+ sendResponse(null,content,complete,callback);
+ }
+
+ protected void execute(Runnable task)
+ {
+ _connector.getExecutor().execute(task);
+ }
+
+ public Scheduler getScheduler()
+ {
+ return _connector.getScheduler();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
+ */
+ public boolean useDirectBuffers()
+ {
+ return getEndPoint() instanceof ChannelEndPoint;
+ }
+
+ /**
+ * If a write or similar to this channel fails this method should be called. The standard implementation
+ * of {@link #failed()} is a noop. But the different implementations of HttpChannel might want to take actions.
+ */
+ public void failed()
+ {
+ }
+
+ private class CommitCallback implements Callback
+ {
+ private final Callback _callback;
+
+ private CommitCallback(Callback callback)
+ {
+ _callback = callback;
+ }
+
+ @Override
+ public void succeeded()
+ {
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ if (x instanceof EofException || x instanceof ClosedChannelException)
+ {
+ LOG.debug(x);
+ _callback.failed(x);
+ _response.getHttpOutput().closed();
+ }
+ else
+ {
+ LOG.warn("Commit failed",x);
+ _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ _callback.failed(x);
+ _response.getHttpOutput().closed();
+ }
+
+ @Override
+ public void failed(Throwable th)
+ {
+ LOG.ignore(th);
+ _callback.failed(x);
+ _response.getHttpOutput().closed();
+ }
+ });
+ }
+ }
+ }
+
+ private class Commit100Callback extends CommitCallback
+ {
+ private Commit100Callback(Callback callback)
+ {
+ super(callback);
+ }
+
+ @Override
+ public void succeeded()
+ {
+ _committed.set(false);
+ super.succeeded();
+ }
+
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.AsyncListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Implementation of AsyncContext interface that holds the state of request-response cycle.
+ */
+public class HttpChannelState
+{
+ private static final Logger LOG = Log.getLogger(HttpChannelState.class);
+
+ private final static long DEFAULT_TIMEOUT=30000L;
+
+ /** The dispatched state of the HttpChannel, used to control the overall livecycle
+ */
+ public enum State
+ {
+ IDLE, // Idle request
+ DISPATCHED, // Request dispatched to filter/servlet
+ ASYNC_WAIT, // Suspended and parked
+ ASYNC_WOKEN, // A thread has been dispatch to handle from ASYNCWAIT
+ ASYNC_IO, // Has been dispatched for async IO
+ COMPLETING, // Request is completable
+ COMPLETED // Request is complete
+ }
+
+ /**
+ * The actions to take as the channel moves from state to state.
+ */
+ public enum Action
+ {
+ REQUEST_DISPATCH, // handle a normal request dispatch
+ ASYNC_DISPATCH, // handle an async request dispatch
+ ASYNC_EXPIRED, // handle an async timeout
+ WRITE_CALLBACK, // handle an IO write callback
+ READ_CALLBACK, // handle an IO read callback
+ WAIT, // Wait for further events
+ COMPLETE // Complete the channel
+ }
+
+ /**
+ * The state of the servlet async API. This can lead or follow the
+ * channel dispatch state and also includes reasons such as expired,
+ * dispatched or completed.
+ */
+ public enum Async
+ {
+ STARTED,
+ DISPATCH,
+ COMPLETE,
+ EXPIRING,
+ EXPIRED
+ }
+
+ private final boolean DEBUG=LOG.isDebugEnabled();
+ private final HttpChannel<?> _channel;
+
+ private List<AsyncListener> _asyncListeners;
+ private State _state;
+ private Async _async;
+ private boolean _initial;
+ private boolean _asyncRead;
+ private boolean _asyncWrite;
+ private long _timeoutMs=DEFAULT_TIMEOUT;
+ private AsyncContextEvent _event;
+
+ protected HttpChannelState(HttpChannel<?> channel)
+ {
+ _channel=channel;
+ _state=State.IDLE;
+ _async=null;
+ _initial=true;
+ }
+
+ public State getState()
+ {
+ synchronized(this)
+ {
+ return _state;
+ }
+ }
+
+ public void addListener(AsyncListener listener)
+ {
+ synchronized(this)
+ {
+ if (_asyncListeners==null)
+ _asyncListeners=new ArrayList<>();
+ _asyncListeners.add(listener);
+ }
+ }
+
+ public void setTimeout(long ms)
+ {
+ synchronized(this)
+ {
+ _timeoutMs=ms;
+ }
+ }
+
+ public long getTimeout()
+ {
+ synchronized(this)
+ {
+ return _timeoutMs;
+ }
+ }
+
+ public AsyncContextEvent getAsyncContextEvent()
+ {
+ synchronized(this)
+ {
+ return _event;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ synchronized (this)
+ {
+ return String.format("%s@%x{s=%s i=%b a=%s}",getClass().getSimpleName(),hashCode(),_state,_initial,_async);
+ }
+ }
+
+ public String getStatusString()
+ {
+ synchronized (this)
+ {
+ return String.format("s=%s i=%b a=%s",_state,_initial,_async);
+ }
+ }
+
+ /**
+ * @return Next handling of the request should proceed
+ */
+ protected Action handling()
+ {
+ synchronized (this)
+ {
+ if(DEBUG)
+ LOG.debug("{} handling {}",this,_state);
+ switch(_state)
+ {
+ case IDLE:
+ _initial=true;
+ _state=State.DISPATCHED;
+ return Action.REQUEST_DISPATCH;
+
+ case COMPLETING:
+ return Action.COMPLETE;
+
+ case COMPLETED:
+ return Action.WAIT;
+
+ case ASYNC_WOKEN:
+ if (_asyncRead)
+ {
+ _state=State.ASYNC_IO;
+ _asyncRead=false;
+ return Action.READ_CALLBACK;
+ }
+ if (_asyncWrite)
+ {
+ _state=State.ASYNC_IO;
+ _asyncWrite=false;
+ return Action.WRITE_CALLBACK;
+ }
+
+ if (_async!=null)
+ {
+ Async async=_async;
+ switch(async)
+ {
+ case COMPLETE:
+ _state=State.COMPLETING;
+ return Action.COMPLETE;
+ case DISPATCH:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_DISPATCH;
+ case EXPIRING:
+ break;
+ case EXPIRED:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_EXPIRED;
+ case STARTED:
+ // TODO
+ if (DEBUG)
+ LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
+ .getStatusString()));
+ return Action.WAIT;
+ }
+ }
+
+ return Action.WAIT;
+
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+ }
+ }
+
+ public void startAsync(AsyncContextEvent event)
+ {
+ final List<AsyncListener> lastAsyncListeners;
+
+ synchronized (this)
+ {
+ if (_state!=State.DISPATCHED || _async!=null)
+ throw new IllegalStateException(this.getStatusString());
+
+ _async=Async.STARTED;
+ _event=event;
+ lastAsyncListeners=_asyncListeners;
+ _asyncListeners=null;
+ }
+
+ if (lastAsyncListeners!=null)
+ {
+ for (AsyncListener listener : lastAsyncListeners)
+ {
+ try
+ {
+ listener.onStartAsync(event);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+ }
+
+ protected void error(Throwable th)
+ {
+ synchronized (this)
+ {
+ if (_event!=null)
+ _event.setThrowable(th);
+ }
+ }
+
+ /**
+ * Signal that the HttpConnection has finished handling the request.
+ * For blocking connectors, this call may block if the request has
+ * been suspended (startAsync called).
+ * @return next actions
+ * be handled again (eg because of a resume that happened before unhandle was called)
+ */
+ protected Action unhandle()
+ {
+ synchronized (this)
+ {
+ if(DEBUG)
+ LOG.debug("{} unhandle {}",this,_state);
+
+ switch(_state)
+ {
+ case DISPATCHED:
+ case ASYNC_IO:
+ break;
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+
+ if (_asyncRead)
+ {
+ _state=State.ASYNC_IO;
+ _asyncRead=false;
+ return Action.READ_CALLBACK;
+ }
+
+ if (_asyncWrite)
+ {
+ _asyncWrite=false;
+ _state=State.ASYNC_IO;
+ return Action.WRITE_CALLBACK;
+ }
+
+ if (_async!=null)
+ {
+ _initial=false;
+ switch(_async)
+ {
+ case COMPLETE:
+ _state=State.COMPLETING;
+ _async=null;
+ return Action.COMPLETE;
+ case DISPATCH:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_DISPATCH;
+ case EXPIRED:
+ _state=State.DISPATCHED;
+ _async=null;
+ return Action.ASYNC_EXPIRED;
+ case EXPIRING:
+ case STARTED:
+ scheduleTimeout();
+ _state=State.ASYNC_WAIT;
+ return Action.WAIT;
+ }
+ }
+
+ _state=State.COMPLETING;
+ return Action.COMPLETE;
+ }
+ }
+
+ public void dispatch(ServletContext context, String path)
+ {
+ boolean dispatch;
+ synchronized (this)
+ {
+ if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+ throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString());
+ _async=Async.DISPATCH;
+
+ if (context!=null)
+ _event.setDispatchContext(context);
+ if (path!=null)
+ _event.setDispatchPath(path);
+
+ switch(_state)
+ {
+ case DISPATCHED:
+ case ASYNC_IO:
+ dispatch=false;
+ break;
+ case ASYNC_WAIT:
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
+ break;
+ case ASYNC_WOKEN:
+ dispatch=false;
+ break;
+ default:
+ LOG.warn("async dispatched when complete {}",this);
+ dispatch=false;
+ break;
+ }
+ }
+
+ cancelTimeout();
+ if (dispatch)
+ scheduleDispatch();
+ }
+
+ protected void expired()
+ {
+ final List<AsyncListener> aListeners;
+ AsyncContextEvent event;
+ synchronized (this)
+ {
+ if (_async!=Async.STARTED)
+ return;
+ _async=Async.EXPIRING;
+ event=_event;
+ aListeners=_asyncListeners;
+ }
+
+ if (aListeners!=null)
+ {
+ for (AsyncListener listener : aListeners)
+ {
+ try
+ {
+ listener.onTimeout(event);
+ }
+ catch(Exception e)
+ {
+ LOG.debug(e);
+ event.setThrowable(e);
+ _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+ break;
+ }
+ }
+ }
+
+ boolean dispatch=false;
+ synchronized (this)
+ {
+ if (_async==Async.EXPIRING)
+ {
+ _async=Async.EXPIRED;
+ if (_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
+ }
+ }
+ }
+
+ if (dispatch)
+ scheduleDispatch();
+ }
+
+ public void complete()
+ {
+ // just like resume, except don't set _dispatched=true;
+ boolean handle=false;
+ synchronized (this)
+ {
+ if (_async!=Async.STARTED && _async!=Async.EXPIRING)
+ throw new IllegalStateException(this.getStatusString());
+ _async=Async.COMPLETE;
+ if (_state==State.ASYNC_WAIT)
+ {
+ handle=true;
+ _state=State.ASYNC_WOKEN;
+ }
+ }
+
+ cancelTimeout();
+ if (handle)
+ {
+ ContextHandler handler=getContextHandler();
+ if (handler!=null)
+ handler.handle(_channel);
+ else
+ _channel.handle();
+ }
+ }
+
+ public void errorComplete()
+ {
+ synchronized (this)
+ {
+ _async=Async.COMPLETE;
+ _event.setDispatchContext(null);
+ _event.setDispatchPath(null);
+ }
+
+ cancelTimeout();
+ }
+
+ protected void completed()
+ {
+ final List<AsyncListener> aListeners;
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case COMPLETING:
+ _state=State.COMPLETED;
+ aListeners=_asyncListeners;
+ event=_event;
+ break;
+
+ default:
+ throw new IllegalStateException(this.getStatusString());
+ }
+ }
+
+ if (event!=null)
+ {
+ if (aListeners!=null)
+ {
+ if (event.getThrowable()!=null)
+ {
+ event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
+ event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
+ }
+
+ for (AsyncListener listener : aListeners)
+ {
+ try
+ {
+ if (event.getThrowable()!=null)
+ listener.onError(event);
+ else
+ listener.onComplete(event);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+
+ event.completed();
+ }
+ }
+
+ protected void recycle()
+ {
+ synchronized (this)
+ {
+ switch(_state)
+ {
+ case DISPATCHED:
+ case ASYNC_IO:
+ throw new IllegalStateException(getStatusString());
+ default:
+ break;
+ }
+ _asyncListeners=null;
+ _state=State.IDLE;
+ _async=null;
+ _initial=true;
+ _asyncRead=false;
+ _asyncWrite=false;
+ _timeoutMs=DEFAULT_TIMEOUT;
+ cancelTimeout();
+ _event=null;
+ }
+ }
+
+ protected void scheduleDispatch()
+ {
+ _channel.execute(_channel);
+ }
+
+ protected void scheduleTimeout()
+ {
+ Scheduler scheduler = _channel.getScheduler();
+ if (scheduler!=null && _timeoutMs>0)
+ _event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS));
+ }
+
+ protected void cancelTimeout()
+ {
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ event=_event;
+ }
+ if (event!=null)
+ event.cancelTimeoutTask();
+ }
+
+ public boolean isExpired()
+ {
+ synchronized (this)
+ {
+ return _async==Async.EXPIRED;
+ }
+ }
+
+ public boolean isInitial()
+ {
+ synchronized(this)
+ {
+ return _initial;
+ }
+ }
+
+ public boolean isSuspended()
+ {
+ synchronized(this)
+ {
+ return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED;
+ }
+ }
+
+ boolean isCompleting()
+ {
+ synchronized (this)
+ {
+ return _state==State.COMPLETING;
+ }
+ }
+
+ boolean isCompleted()
+ {
+ synchronized (this)
+ {
+ return _state == State.COMPLETED;
+ }
+ }
+
+ public boolean isAsyncStarted()
+ {
+ synchronized (this)
+ {
+ if (_state==State.DISPATCHED)
+ return _async!=null;
+ return _async==Async.STARTED || _async==Async.EXPIRING;
+ }
+ }
+
+ public boolean isAsync()
+ {
+ synchronized (this)
+ {
+ return !_initial || _async!=null;
+ }
+ }
+
+ public Request getBaseRequest()
+ {
+ return _channel.getRequest();
+ }
+
+ public HttpChannel<?> getHttpChannel()
+ {
+ return _channel;
+ }
+
+ public ContextHandler getContextHandler()
+ {
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ event=_event;
+ }
+
+ if (event!=null)
+ {
+ Context context=((Context)event.getServletContext());
+ if (context!=null)
+ return context.getContextHandler();
+ }
+ return null;
+ }
+
+ public ServletResponse getServletResponse()
+ {
+ final AsyncContextEvent event;
+ synchronized (this)
+ {
+ event=_event;
+ }
+ if (event!=null && event.getSuppliedResponse()!=null)
+ return event.getSuppliedResponse();
+ return _channel.getResponse();
+ }
+
+ public Object getAttribute(String name)
+ {
+ return _channel.getRequest().getAttribute(name);
+ }
+
+ public void removeAttribute(String name)
+ {
+ _channel.getRequest().removeAttribute(name);
+ }
+
+ public void setAttribute(String name, Object attribute)
+ {
+ _channel.getRequest().setAttribute(name,attribute);
+ }
+
+ public void onReadPossible()
+ {
+ boolean handle=false;
+
+ synchronized (this)
+ {
+ _asyncRead=true;
+ if (_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ handle=true;
+ }
+ }
+
+ if (handle)
+ _channel.execute(_channel);
+ }
+
+ public void onWritePossible()
+ {
+ boolean handle=false;
+
+ synchronized (this)
+ {
+ _asyncWrite=true;
+ if (_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ handle=true;
+ }
+ }
+
+ if (handle)
+ _channel.execute(_channel);
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+
+/* ------------------------------------------------------------ */
+/** HTTP Configuration.
+ * <p>This class is a holder of HTTP configuration for use by the
+ * {@link HttpChannel} class. Typically a HTTPConfiguration instance
+ * is instantiated and passed to a {@link ConnectionFactory} that can
+ * create HTTP channels (eg HTTP, AJP or SPDY).</p>
+ * <p>The configuration held by this class is not for the wire protocol,
+ * but for the interpretation and handling of HTTP requests that could
+ * be transported by a variety of protocols.
+ * </p>
+ */
+@ManagedObject("HTTP Configuration")
+public class HttpConfiguration
+{
+ public static final String SERVER_VERSION = "Jetty(" + Jetty.VERSION + ")";
+
+ private List<Customizer> _customizers=new CopyOnWriteArrayList<>();
+ private int _outputBufferSize=32*1024;
+ private int _requestHeaderSize=8*1024;
+ private int _responseHeaderSize=8*1024;
+ private int _headerCacheSize=512;
+ private int _securePort;
+ private String _secureScheme = HttpScheme.HTTPS.asString();
+ private boolean _sendServerVersion = true; //send Server: header
+ private boolean _sendXPoweredBy = false; //send X-Powered-By: header
+ private boolean _sendDateHeader = true; //send Date: header
+
+ public interface Customizer
+ {
+ public void customize(Connector connector, HttpConfiguration channelConfig, Request request);
+ }
+
+ public interface ConnectionFactory
+ {
+ HttpConfiguration getHttpConfiguration();
+ }
+
+ public HttpConfiguration()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Create a configuration from another.
+ * @param config The configuration to copy.
+ */
+ public HttpConfiguration(HttpConfiguration config)
+ {
+ _customizers.addAll(config._customizers);
+ _outputBufferSize=config._outputBufferSize;
+ _requestHeaderSize=config._requestHeaderSize;
+ _responseHeaderSize=config._responseHeaderSize;
+ _securePort=config._securePort;
+ _secureScheme=config._secureScheme;
+ _sendDateHeader=config._sendDateHeader;
+ _sendServerVersion=config._sendServerVersion;
+ _headerCacheSize=config._headerCacheSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Add a {@link Customizer} that is invoked for every
+ * request received.</p>
+ * <p>Customiser are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or
+ * optional protocol semantics (eg {@link SecureRequestCustomizer}).
+ * @param customizer A request customizer
+ */
+ public void addCustomizer(Customizer customizer)
+ {
+ _customizers.add(customizer);
+ }
+
+ /* ------------------------------------------------------------ */
+ public List<Customizer> getCustomizers()
+ {
+ return _customizers;
+ }
+
+ public <T> T getCustomizer(Class<T> type)
+ {
+ for (Customizer c : _customizers)
+ if (type.isAssignableFrom(c.getClass()))
+ return (T)c;
+ return null;
+ }
+
+ @ManagedAttribute("The size in bytes of the output buffer used to aggregate HTTP output")
+ public int getOutputBufferSize()
+ {
+ return _outputBufferSize;
+ }
+
+ @ManagedAttribute("The maximum allowed size in bytes for a HTTP request header")
+ public int getRequestHeaderSize()
+ {
+ return _requestHeaderSize;
+ }
+
+ @ManagedAttribute("The maximum allowed size in bytes for a HTTP response header")
+ public int getResponseHeaderSize()
+ {
+ return _responseHeaderSize;
+ }
+
+ @ManagedAttribute("The maximum allowed size in bytes for a HTTP header field cache")
+ public int getHeaderCacheSize()
+ {
+ return _headerCacheSize;
+ }
+
+ @ManagedAttribute("The port to which Integral or Confidential security constraints are redirected")
+ public int getSecurePort()
+ {
+ return _securePort;
+ }
+
+ @ManagedAttribute("The scheme with which Integral or Confidential security constraints are redirected")
+ public String getSecureScheme()
+ {
+ return _secureScheme;
+ }
+
+ public void setSendServerVersion (boolean sendServerVersion)
+ {
+ _sendServerVersion = sendServerVersion;
+ }
+
+ @ManagedAttribute("if true, send the Server header in responses")
+ public boolean getSendServerVersion()
+ {
+ return _sendServerVersion;
+ }
+
+ public void setSendXPoweredBy (boolean sendXPoweredBy)
+ {
+ _sendXPoweredBy=sendXPoweredBy;
+ }
+
+ @ManagedAttribute("if true, send the X-Powered-By header in responses")
+ public boolean getSendXPoweredBy()
+ {
+ return _sendXPoweredBy;
+ }
+
+ public void setSendDateHeader(boolean sendDateHeader)
+ {
+ _sendDateHeader = sendDateHeader;
+ }
+
+ @ManagedAttribute("if true, include the date in HTTP headers")
+ public boolean getSendDateHeader()
+ {
+ return _sendDateHeader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * <p>Set the {@link Customizer}s that are invoked for every
+ * request received.</p>
+ * <p>Customisers are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or
+ * optional protocol semantics (eg {@link SecureRequestCustomizer}).
+ * @param customizers
+ */
+ public void setCustomizers(List<Customizer> customizers)
+ {
+ _customizers.clear();
+ _customizers.addAll(customizers);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the size of the buffer into which response content is aggregated
+ * before being sent to the client. A larger buffer can improve performance by allowing
+ * a content producer to run without blocking, however larger buffers consume more memory and
+ * may induce some latency before a client starts processing the content.
+ * @param responseBufferSize buffer size in bytes.
+ */
+ public void setOutputBufferSize(int responseBufferSize)
+ {
+ _outputBufferSize = responseBufferSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the maximum size of a request header.
+ * <p>Larger headers will allow for more and/or larger cookies plus larger form content encoded
+ * in a URL. However, larger headers consume more memory and can make a server more vulnerable to denial of service
+ * attacks.</p>
+ * @param requestHeaderSize Max header size in bytes
+ */
+ public void setRequestHeaderSize(int requestHeaderSize)
+ {
+ _requestHeaderSize = requestHeaderSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the maximum size of a response header.
+ *
+ * <p>Larger headers will allow for more and/or larger cookies and longer HTTP headers (eg for redirection).
+ * However, larger headers will also consume more memory.</p>
+ * @param responseHeaderSize Response header size in bytes.
+ */
+ public void setResponseHeaderSize(int responseHeaderSize)
+ {
+ _responseHeaderSize = responseHeaderSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the header field cache size.
+ * @param headerCacheSize The size in bytes of the header field cache.
+ */
+ public void setHeaderCacheSize(int headerCacheSize)
+ {
+ _headerCacheSize = headerCacheSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the TCP/IP port used for CONFIDENTIAL and INTEGRAL
+ * redirections.
+ * @param confidentialPort
+ */
+ public void setSecurePort(int confidentialPort)
+ {
+ _securePort = confidentialPort;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the URI scheme used for CONFIDENTIAL and INTEGRAL
+ * redirections.
+ * @param confidentialScheme A string like"https"
+ */
+ public void setSecureScheme(String confidentialScheme)
+ {
+ _secureScheme = confidentialScheme;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%d,%d/%d,%s://:%d,%s}",this.getClass().getSimpleName(),hashCode(),_outputBufferSize,_requestHeaderSize,_responseHeaderSize,_secureScheme,_securePort,_customizers);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>A {@link Connection} that handles the HTTP protocol.</p>
+ */
+public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport
+{
+ public static final String UPGRADE_CONNECTION_ATTRIBUTE = "org.eclipse.jetty.server.HttpConnection.UPGRADE";
+ private static final boolean REQUEST_BUFFER_DIRECT=false;
+ private static final boolean HEADER_BUFFER_DIRECT=false;
+ private static final boolean CHUNK_BUFFER_DIRECT=false;
+ private static final Logger LOG = Log.getLogger(HttpConnection.class);
+ private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<>();
+
+ private final HttpConfiguration _config;
+ private final Connector _connector;
+ private final ByteBufferPool _bufferPool;
+ private final HttpGenerator _generator;
+ private final HttpChannelOverHttp _channel;
+ private final HttpParser _parser;
+ private volatile ByteBuffer _requestBuffer = null;
+ private volatile ByteBuffer _chunk = null;
+
+
+ /* ------------------------------------------------------------ */
+ /** Get the current connection that this thread is dispatched to.
+ * Note that a thread may be processing a request asynchronously and
+ * thus not be dispatched to the connection.
+ * @see Request#getAttribute(String) for a more general way to access the HttpConnection
+ * @return the current HttpConnection or null
+ */
+ public static HttpConnection getCurrentConnection()
+ {
+ return __currentConnection.get();
+ }
+
+ protected static HttpConnection setCurrentConnection(HttpConnection connection)
+ {
+ HttpConnection last=__currentConnection.get();
+ if (connection==null)
+ __currentConnection.remove();
+ else
+ __currentConnection.set(connection);
+ return last;
+ }
+
+ public HttpConfiguration getHttpConfiguration()
+ {
+ return _config;
+ }
+
+ public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint)
+ {
+ // Tell AbstractConnector executeOnFillable==true because we want the same thread that
+ // does the HTTP parsing to handle the request so its cache is hot
+ super(endPoint, connector.getExecutor(),true);
+
+ _config = config;
+ _connector = connector;
+ _bufferPool = _connector.getByteBufferPool();
+ _generator = newHttpGenerator();
+ HttpInput<ByteBuffer> input = newHttpInput();
+ _channel = newHttpChannel(input);
+ _parser = newHttpParser();
+ LOG.debug("New HTTP Connection {}", this);
+ }
+
+ protected HttpGenerator newHttpGenerator()
+ {
+ return new HttpGenerator(_config.getSendServerVersion(),_config.getSendXPoweredBy());
+ }
+
+ protected HttpInput<ByteBuffer> newHttpInput()
+ {
+ return new HttpInputOverHTTP(this);
+ }
+
+ protected HttpChannelOverHttp newHttpChannel(HttpInput<ByteBuffer> httpInput)
+ {
+ return new HttpChannelOverHttp(_connector, _config, getEndPoint(), this, httpInput);
+ }
+
+ protected HttpParser newHttpParser()
+ {
+ return new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize());
+ }
+
+ protected HttpParser.RequestHandler<ByteBuffer> newRequestHandler()
+ {
+ return _channel;
+ }
+
+ public Server getServer()
+ {
+ return _connector.getServer();
+ }
+
+ public Connector getConnector()
+ {
+ return _connector;
+ }
+
+ public HttpChannel<?> getHttpChannel()
+ {
+ return _channel;
+ }
+
+ public HttpParser getParser()
+ {
+ return _parser;
+ }
+
+ @Override
+ public int getMessagesIn()
+ {
+ return getHttpChannel().getRequests();
+ }
+
+ @Override
+ public int getMessagesOut()
+ {
+ return getHttpChannel().getRequests();
+ }
+
+ void releaseRequestBuffer()
+ {
+ if (_requestBuffer != null && !_requestBuffer.hasRemaining())
+ {
+ ByteBuffer buffer=_requestBuffer;
+ _requestBuffer=null;
+ _bufferPool.release(buffer);
+ }
+ }
+
+ public ByteBuffer getRequestBuffer()
+ {
+ if (_requestBuffer == null)
+ _requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
+ return _requestBuffer;
+ }
+
+ /**
+ * <p>Parses and handles HTTP messages.</p>
+ * <p>This method is called when this {@link Connection} is ready to read bytes from the {@link EndPoint}.
+ * However, it can also be called if there is unconsumed data in the _requestBuffer, as a result of
+ * resuming a suspended request when there is a pipelined request already read into the buffer.</p>
+ * <p>This method fills bytes and parses them until either: EOF is filled; 0 bytes are filled;
+ * the HttpChannel finishes handling; or the connection has changed.</p>
+ */
+ @Override
+ public void onFillable()
+ {
+ LOG.debug("{} onFillable {}", this, _channel.getState());
+
+ final HttpConnection last=setCurrentConnection(this);
+ int filled=Integer.MAX_VALUE;
+ boolean suspended=false;
+ try
+ {
+ // while not suspended and not upgraded
+ while (!suspended && getEndPoint().getConnection()==this)
+ {
+ // Do we need some data to parse
+ if (BufferUtil.isEmpty(_requestBuffer))
+ {
+ // If the previous iteration filled 0 bytes or saw a close, then break here
+ if (filled<=0)
+ break;
+
+ // Can we fill?
+ if(getEndPoint().isInputShutdown())
+ {
+ // No pretend we read -1
+ filled=-1;
+ _parser.atEOF();
+ }
+ else
+ {
+ // Get a buffer
+ // We are not in a race here for the request buffer as we have not yet received a request,
+ // so there are not an possible legal threads calling #parseContent or #completed.
+ _requestBuffer = getRequestBuffer();
+
+ // fill
+ filled = getEndPoint().fill(_requestBuffer);
+ if (filled==0) // Do a retry on fill 0 (optimization for SSL connections)
+ filled = getEndPoint().fill(_requestBuffer);
+
+ // tell parser
+ if (filled < 0)
+ _parser.atEOF();
+ }
+ }
+
+ // Parse the buffer
+ if (_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
+ {
+ // The parser returned true, which indicates the channel is ready to handle a request.
+ // Call the channel and this will either handle the request/response to completion OR,
+ // if the request suspends, the request/response will be incomplete so the outer loop will exit.
+ // Not that onFillable no longer manipulates the request buffer from this point and that is
+ // left to threads calling #completed or #parseContent (which may be this thread inside handle())
+ suspended = !_channel.handle();
+ }
+ else
+ {
+ // We parsed what we could, recycle the request buffer
+ // We are not in a race here for the request buffer as we have not yet received a request,
+ // so there are not an possible legal threads calling #parseContent or #completed.
+ releaseRequestBuffer();
+ }
+ }
+ }
+ catch (EofException e)
+ {
+ LOG.debug(e);
+ }
+ catch (Exception e)
+ {
+ if (_parser.isIdle())
+ LOG.debug(e);
+ else
+ LOG.warn(this.toString(), e);
+ close();
+ }
+ finally
+ {
+ setCurrentConnection(last);
+ if (!suspended && getEndPoint().isOpen() && getEndPoint().getConnection()==this)
+ {
+ fillInterested();
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Fill and parse data looking for content
+ * @throws IOException
+ */
+ protected void parseContent() throws IOException
+ {
+ // Not in a race here for the request buffer with #onFillable because an async consumer of
+ // content would only be started after onFillable has given up control.
+ // In a little bit of a race with #completed, but then not sure if it is legal to be doing
+ // async calls to IO and have a completed call at the same time.
+ ByteBuffer requestBuffer = getRequestBuffer();
+
+ while (_parser.inContentState())
+ {
+ // Can the parser progress (even with an empty buffer)
+ boolean parsed = _parser.parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer);
+
+ // No, we can we try reading some content?
+ if (BufferUtil.isEmpty(requestBuffer) && getEndPoint().isInputShutdown())
+ {
+ _parser.atEOF();
+ if (parsed)
+ break;
+ continue;
+ }
+
+ if (parsed)
+ break;
+
+ // OK lets read some data
+ int filled=getEndPoint().fill(requestBuffer);
+ if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled'
+ LOG.debug("{} filled {}",this,filled);
+ if (filled<=0)
+ {
+ if (filled<0)
+ {
+ _parser.atEOF();
+ continue;
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void completed()
+ {
+ // Handle connection upgrades
+ if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
+ {
+ Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
+ if (connection != null)
+ {
+ LOG.debug("Upgrade from {} to {}", this, connection);
+ onClose();
+ getEndPoint().setConnection(connection);
+ connection.onOpen();
+ _channel.reset();
+ _parser.reset();
+ _generator.reset();
+ releaseRequestBuffer();
+ return;
+ }
+ }
+
+ // Finish consuming the request
+ // If we are still expecting
+ if (_channel.isExpecting100Continue())
+ // close to seek EOF
+ _parser.close();
+ else if (_parser.inContentState() && _generator.isPersistent())
+ // Complete reading the request
+ _channel.getRequest().getHttpInput().consumeAll();
+
+ // Reset the channel, parsers and generator
+ _channel.reset();
+ if (_generator.isPersistent() && !_parser.isClosed())
+ _parser.reset();
+ else
+ _parser.close();
+
+ // Not in a race here with onFillable, because it has given up control before calling handle.
+ // in a slight race with #completed, but not sure what to do with that anyway.
+ releaseRequestBuffer();
+ if (_chunk!=null)
+ _bufferPool.release(_chunk);
+ _chunk=null;
+ _generator.reset();
+
+ // if we are not called from the onfillable thread, schedule completion
+ if (getCurrentConnection()!=this)
+ {
+ // If we are looking for the next request
+ if (_parser.isStart())
+ {
+ // if the buffer is empty
+ if (BufferUtil.isEmpty(_requestBuffer))
+ {
+ // look for more data
+ fillInterested();
+ }
+ // else if we are still running
+ else if (getConnector().isRunning())
+ {
+ // Dispatched to handle a pipelined request
+ try
+ {
+ getExecutor().execute(this);
+ }
+ catch (RejectedExecutionException e)
+ {
+ if (getConnector().isRunning())
+ LOG.warn(e);
+ else
+ LOG.ignore(e);
+ getEndPoint().close();
+ }
+ }
+ else
+ {
+ getEndPoint().close();
+ }
+ }
+ // else the parser must be closed, so seek the EOF if we are still open
+ else if (getEndPoint().isOpen())
+ fillInterested();
+ }
+ }
+
+ @Override
+ protected void onFillInterestedFailed(Throwable cause)
+ {
+ _parser.close();
+ super.onFillInterestedFailed(cause);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+ @Override
+ public void run()
+ {
+ onFillable();
+ }
+
+
+ @Override
+ public void send(ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
+ {
+ if (info==null)
+ new ContentCallback(content,lastContent,callback).iterate();
+ else
+ {
+ // If we are still expecting a 100 continues
+ if (_channel.isExpecting100Continue())
+ // then we can't be persistent
+ _generator.setPersistent(false);
+ new CommitCallback(info,content,lastContent,callback).iterate();
+ }
+ }
+
+ @Override
+ public void send(ByteBuffer content, boolean lastContent, Callback callback)
+ {
+ new ContentCallback(content,lastContent,callback).iterate();
+ }
+
+
+ protected class HttpChannelOverHttp extends HttpChannel<ByteBuffer>
+ {
+ public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
+ {
+ super(connector,config,endPoint,transport,input);
+ }
+
+ @Override
+ public void earlyEOF()
+ {
+ // If we have no request yet, just close
+ if (getRequest().getMethod()==null)
+ close();
+ else
+ super.earlyEOF();
+ }
+
+ @Override
+ public boolean content(ByteBuffer item)
+ {
+ super.content(item);
+ return true;
+ }
+
+ @Override
+ public void badMessage(int status, String reason)
+ {
+ _generator.setPersistent(false);
+ super.badMessage(status,reason);
+ }
+
+ @Override
+ public boolean headerComplete()
+ {
+ boolean persistent;
+ HttpVersion version = getHttpVersion();
+
+ switch (version)
+ {
+ case HTTP_0_9:
+ {
+ persistent = false;
+ break;
+ }
+ case HTTP_1_0:
+ {
+ persistent = getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
+ if (!persistent)
+ persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+ if (persistent)
+ getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
+ break;
+ }
+ case HTTP_1_1:
+ {
+ persistent = !getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
+ if (!persistent)
+ persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
+ if (!persistent)
+ getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+
+ if (!persistent)
+ _generator.setPersistent(false);
+
+ return super.headerComplete();
+ }
+
+ @Override
+ protected void handleException(Throwable x)
+ {
+ _generator.setPersistent(false);
+ super.handleException(x);
+ }
+
+ @Override
+ public void failed()
+ {
+ getEndPoint().shutdownOutput();
+ }
+
+
+ @Override
+ public boolean messageComplete()
+ {
+ super.messageComplete();
+ return false;
+ }
+ }
+
+ private class CommitCallback extends IteratingCallback
+ {
+ final ByteBuffer _content;
+ final boolean _lastContent;
+ final ResponseInfo _info;
+ final Callback _callback;
+ ByteBuffer _header;
+
+ CommitCallback(ResponseInfo info, ByteBuffer content, boolean last, Callback callback)
+ {
+ _info=info;
+ _content=content;
+ _lastContent=last;
+ _callback=callback;
+ }
+
+ @Override
+ public Action process() throws Exception
+ {
+ ByteBuffer chunk = _chunk;
+ while (true)
+ {
+ HttpGenerator.Result result = _generator.generateResponse(_info, _header, chunk, _content, _lastContent);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} generate: {} ({},{},{})@{}",
+ this,
+ result,
+ BufferUtil.toSummaryString(_header),
+ BufferUtil.toSummaryString(_content),
+ _lastContent,
+ _generator.getState());
+
+ switch (result)
+ {
+ case NEED_HEADER:
+ {
+ // Look for optimisation to avoid allocating a _header buffer
+ /*
+ Cannot use this optimisation unless we work out how not to overwrite data in user passed arrays.
+ if (_lastContent && _content!=null && !_content.isReadOnly() && _content.hasArray() && BufferUtil.space(_content)>_config.getResponseHeaderSize() )
+ {
+ // use spare space in content buffer for header buffer
+ int p=_content.position();
+ int l=_content.limit();
+ _content.position(l);
+ _content.limit(l+_config.getResponseHeaderSize());
+ _header=_content.slice();
+ _header.limit(0);
+ _content.position(p);
+ _content.limit(l);
+ }
+ else
+ */
+ _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
+
+ continue;
+ }
+ case NEED_CHUNK:
+ {
+ chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
+ continue;
+ }
+ case FLUSH:
+ {
+ // Don't write the chunk or the content if this is a HEAD response
+ if (_channel.getRequest().isHead())
+ {
+ BufferUtil.clear(chunk);
+ BufferUtil.clear(_content);
+ }
+
+ // If we have a header
+ if (BufferUtil.hasContent(_header))
+ {
+ if (BufferUtil.hasContent(_content))
+ {
+ if (BufferUtil.hasContent(chunk))
+ getEndPoint().write(this, _header, chunk, _content);
+ else
+ getEndPoint().write(this, _header, _content);
+ }
+ else
+ getEndPoint().write(this, _header);
+ }
+ else if (BufferUtil.hasContent(chunk))
+ {
+ if (BufferUtil.hasContent(_content))
+ getEndPoint().write(this, chunk, _content);
+ else
+ getEndPoint().write(this, chunk);
+ }
+ else if (BufferUtil.hasContent(_content))
+ {
+ getEndPoint().write(this, _content);
+ }
+ else
+ continue;
+ return Action.SCHEDULED;
+ }
+ case SHUTDOWN_OUT:
+ {
+ getEndPoint().shutdownOutput();
+ continue;
+ }
+ case DONE:
+ {
+ if (_header!=null)
+ {
+ // don't release header in spare content buffer
+ if (!_lastContent || _content==null || !_content.hasArray() || !_header.hasArray() || _content.array()!=_header.array())
+ _bufferPool.release(_header);
+ }
+ return Action.SUCCEEDED;
+ }
+ case CONTINUE:
+ {
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException("generateResponse="+result);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void completed()
+ {
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ super.failed(x);
+ failedCallback(_callback,x);
+ }
+ }
+
+ private class ContentCallback extends IteratingCallback
+ {
+ final ByteBuffer _content;
+ final boolean _lastContent;
+ final Callback _callback;
+
+ ContentCallback(ByteBuffer content, boolean last, Callback callback)
+ {
+ _content=content;
+ _lastContent=last;
+ _callback=callback;
+ }
+
+ @Override
+ public Action process() throws Exception
+ {
+ ByteBuffer chunk = _chunk;
+ while (true)
+ {
+ HttpGenerator.Result result = _generator.generateResponse(null, null, chunk, _content, _lastContent);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} generate: {} ({},{})@{}",
+ this,
+ result,
+ BufferUtil.toSummaryString(_content),
+ _lastContent,
+ _generator.getState());
+
+ switch (result)
+ {
+ case NEED_HEADER:
+ throw new IllegalStateException();
+ case NEED_CHUNK:
+ {
+ chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT);
+ continue;
+ }
+ case FLUSH:
+ {
+ // Don't write the chunk or the content if this is a HEAD response
+ if (_channel.getRequest().isHead())
+ {
+ BufferUtil.clear(chunk);
+ BufferUtil.clear(_content);
+ continue;
+ }
+ else if (BufferUtil.hasContent(chunk))
+ {
+ if (BufferUtil.hasContent(_content))
+ getEndPoint().write(this, chunk, _content);
+ else
+ getEndPoint().write(this, chunk);
+ }
+ else if (BufferUtil.hasContent(_content))
+ {
+ getEndPoint().write(this, _content);
+ }
+ else
+ continue;
+ return Action.SCHEDULED;
+ }
+ case SHUTDOWN_OUT:
+ {
+ getEndPoint().shutdownOutput();
+ continue;
+ }
+ case DONE:
+ {
+ return Action.SUCCEEDED;
+ }
+ case CONTINUE:
+ {
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException("generateResponse="+result);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void completed()
+ {
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(final Throwable x)
+ {
+ super.failed(x);
+ failedCallback(_callback,x);
+ }
+ }
+
+ @Override
+ public void abort()
+ {
+ // Do a direct close of the output, as this may indicate to a client that the
+ // response is bad either with RST or by abnormal completion of chunked response.
+ getEndPoint().close();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.Name;
+
+
+/* ------------------------------------------------------------ */
+/** A Connection Factory for HTTP Connections.
+ * <p>Accepts connections either directly or via SSL and/or NPN chained connection factories. The accepted
+ * {@link HttpConnection}s are configured by a {@link HttpConfiguration} instance that is either created by
+ * default or passed in to the constructor.
+ */
+public class HttpConnectionFactory extends AbstractConnectionFactory implements HttpConfiguration.ConnectionFactory
+{
+ private final HttpConfiguration _config;
+
+ public HttpConnectionFactory()
+ {
+ this(new HttpConfiguration());
+ setInputBufferSize(16384);
+ }
+
+ public HttpConnectionFactory(@Name("config") HttpConfiguration config)
+ {
+ super(HttpVersion.HTTP_1_1.toString());
+ _config=config;
+ addBean(_config);
+ }
+
+ @Override
+ public HttpConfiguration getHttpConfiguration()
+ {
+ return _config;
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ return configure(new HttpConnection(_config, connector, endPoint), connector, endPoint);
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.util.Objects;
+import javax.servlet.ReadListener;
+import javax.servlet.ServletInputStream;
+
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link HttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.
+ * <p/>
+ * Content may arrive in patterns such as [content(), content(), messageComplete()] so that this class
+ * maintains two states: the content state that tells whether there is content to consume and the EOF
+ * state that tells whether an EOF has arrived.
+ * Only once the content has been consumed the content state is moved to the EOF state.
+ */
+public abstract class HttpInput<T> extends ServletInputStream implements Runnable
+{
+ private final static Logger LOG = Log.getLogger(HttpInput.class);
+
+ private final byte[] _oneByteBuffer = new byte[1];
+ private final Object _lock;
+ private HttpChannelState _channelState;
+ private ReadListener _listener;
+ private Throwable _onError;
+ private boolean _notReady;
+ private State _contentState = STREAM;
+ private State _eofState;
+ private long _contentRead;
+
+ protected HttpInput()
+ {
+ this(null);
+ }
+
+ protected HttpInput(Object lock)
+ {
+ _lock = lock == null ? this : lock;
+ }
+
+ public void init(HttpChannelState state)
+ {
+ synchronized (lock())
+ {
+ _channelState = state;
+ }
+ }
+
+ public final Object lock()
+ {
+ return _lock;
+ }
+
+ public void recycle()
+ {
+ synchronized (lock())
+ {
+ _listener = null;
+ _onError = null;
+ _notReady = false;
+ _contentState = STREAM;
+ _eofState = null;
+ _contentRead = 0;
+ }
+ }
+
+ @Override
+ public int available()
+ {
+ try
+ {
+ synchronized (lock())
+ {
+ T item = getNextContent();
+ return item == null ? 0 : remaining(item);
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeIOException(e);
+ }
+ }
+
+ @Override
+ public int read() throws IOException
+ {
+ int read = read(_oneByteBuffer, 0, 1);
+ return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ synchronized (lock())
+ {
+ T item = getNextContent();
+ if (item == null)
+ {
+ _contentState.waitForContent(this);
+ item = getNextContent();
+ if (item == null)
+ return _contentState.noContent();
+ }
+ int l = get(item, b, off, len);
+ _contentRead += l;
+ return l;
+ }
+ }
+
+ /**
+ * A convenience method to call nextContent and to check the return value, which if null then the
+ * a check is made for EOF and the state changed accordingly.
+ *
+ * @return Content or null if none available.
+ * @throws IOException
+ * @see #nextContent()
+ */
+ protected T getNextContent() throws IOException
+ {
+ T content = nextContent();
+ if (content == null)
+ {
+ synchronized (lock())
+ {
+ if (_eofState != null)
+ {
+ LOG.debug("{} eof {}", this, _eofState);
+ _contentState = _eofState;
+ }
+ }
+ }
+ return content;
+ }
+
+ /**
+ * Access the next content to be consumed from. Returning the next item does not consume it
+ * and it may be returned multiple times until it is consumed.
+ * <p/>
+ * Calls to {@link #get(Object, byte[], int, int)}
+ * or {@link #consume(Object, int)} are required to consume data from the content.
+ *
+ * @return the content or null if none available.
+ * @throws IOException if retrieving the content fails
+ */
+ protected abstract T nextContent() throws IOException;
+
+ /**
+ * @param item the content
+ * @return how many bytes remain in the given content
+ */
+ protected abstract int remaining(T item);
+
+ /**
+ * Copies the given content into the given byte buffer.
+ *
+ * @param item the content to copy from
+ * @param buffer the buffer to copy into
+ * @param offset the buffer offset to start copying from
+ * @param length the space available in the buffer
+ * @return the number of bytes actually copied
+ */
+ protected abstract int get(T item, byte[] buffer, int offset, int length);
+
+ /**
+ * Consumes the given content.
+ *
+ * @param item the content to consume
+ * @param length the number of bytes to consume
+ */
+ protected abstract void consume(T item, int length);
+
+ /**
+ * Blocks until some content or some end-of-file event arrives.
+ *
+ * @throws IOException if the wait is interrupted
+ */
+ protected abstract void blockForContent() throws IOException;
+
+ /**
+ * Adds some content to this input stream.
+ *
+ * @param item the content to add
+ */
+ public abstract void content(T item);
+
+ protected boolean onAsyncRead()
+ {
+ synchronized (lock())
+ {
+ if (_listener == null)
+ return false;
+ }
+ _channelState.onReadPossible();
+ return true;
+ }
+
+ public long getContentRead()
+ {
+ synchronized (lock())
+ {
+ return _contentRead;
+ }
+ }
+
+ /**
+ * This method should be called to signal that an EOF has been
+ * detected before all the expected content arrived.
+ * <p/>
+ * Typically this will result in an EOFException being thrown
+ * from a subsequent read rather than a -1 return.
+ */
+ public void earlyEOF()
+ {
+ synchronized (lock())
+ {
+ if (!isEOF())
+ {
+ LOG.debug("{} early EOF", this);
+ _eofState = EARLY_EOF;
+ if (_listener == null)
+ return;
+ }
+ }
+ _channelState.onReadPossible();
+ }
+
+ /**
+ * This method should be called to signal that all the expected
+ * content arrived.
+ */
+ public void messageComplete()
+ {
+ synchronized (lock())
+ {
+ if (!isEOF())
+ {
+ LOG.debug("{} EOF", this);
+ _eofState = EOF;
+ if (_listener == null)
+ return;
+ }
+ }
+ _channelState.onReadPossible();
+ }
+
+ public void consumeAll()
+ {
+ synchronized (lock())
+ {
+ try
+ {
+ while (!isFinished())
+ {
+ T item = getNextContent();
+ if (item == null)
+ _contentState.waitForContent(this);
+ else
+ consume(item, remaining(item));
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ }
+ }
+
+ public boolean isAsync()
+ {
+ synchronized (lock())
+ {
+ return _contentState==ASYNC;
+ }
+ }
+
+ /**
+ * @return whether an EOF has been detected, even though there may be content to consume.
+ */
+ public boolean isEOF()
+ {
+ synchronized (lock())
+ {
+ return _eofState != null && _eofState.isEOF();
+ }
+ }
+
+ @Override
+ public boolean isFinished()
+ {
+ synchronized (lock())
+ {
+ return _contentState.isEOF();
+ }
+ }
+
+ @Override
+ public boolean isReady()
+ {
+ boolean finished;
+ synchronized (lock())
+ {
+ if (_contentState.isEOF())
+ return true;
+ if (_listener == null )
+ return true;
+ if (available() > 0)
+ return true;
+ if (_notReady)
+ return false;
+ _notReady = true;
+ finished = isFinished();
+ }
+ if (finished)
+ _channelState.onReadPossible();
+ else
+ unready();
+ return false;
+ }
+
+ protected void unready()
+ {
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener)
+ {
+ readListener = Objects.requireNonNull(readListener);
+ synchronized (lock())
+ {
+ if (_contentState != STREAM)
+ throw new IllegalStateException("state=" + _contentState);
+ _contentState = ASYNC;
+ _listener = readListener;
+ _notReady = true;
+ }
+ _channelState.onReadPossible();
+ }
+
+ public void failed(Throwable x)
+ {
+ synchronized (lock())
+ {
+ if (_onError != null)
+ LOG.warn(x);
+ else
+ _onError = x;
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ final Throwable error;
+ final ReadListener listener;
+ boolean available = false;
+ final boolean eof;
+
+ synchronized (lock())
+ {
+ if (!_notReady || _listener == null)
+ return;
+
+ error = _onError;
+ listener = _listener;
+
+ try
+ {
+ T item = getNextContent();
+ available = item != null && remaining(item) > 0;
+ }
+ catch (Exception e)
+ {
+ failed(e);
+ }
+
+ eof = !available && isFinished();
+ _notReady = !available && !eof;
+ }
+
+ try
+ {
+ if (error != null)
+ listener.onError(error);
+ else if (available)
+ listener.onDataAvailable();
+ else if (eof)
+ listener.onAllDataRead();
+ else
+ unready();
+ }
+ catch (Throwable e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ listener.onError(e);
+ }
+ }
+
+ protected static abstract class State
+ {
+ public void waitForContent(HttpInput<?> in) throws IOException
+ {
+ }
+
+ public int noContent() throws IOException
+ {
+ return -1;
+ }
+
+ public boolean isEOF()
+ {
+ return false;
+ }
+ }
+
+ protected static final State STREAM = new State()
+ {
+ @Override
+ public void waitForContent(HttpInput<?> input) throws IOException
+ {
+ input.blockForContent();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "STREAM";
+ }
+ };
+
+ protected static final State ASYNC = new State()
+ {
+ @Override
+ public int noContent() throws IOException
+ {
+ return 0;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "ASYNC";
+ }
+ };
+
+ protected static final State EARLY_EOF = new State()
+ {
+ @Override
+ public int noContent() throws IOException
+ {
+ throw new EofException();
+ }
+
+ @Override
+ public boolean isEOF()
+ {
+ return true;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "EARLY_EOF";
+ }
+ };
+
+ protected static final State EOF = new State()
+ {
+ @Override
+ public boolean isEOF()
+ {
+ return true;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "EOF";
+ }
+ };
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpInputOverHTTP extends HttpInput<ByteBuffer> implements Callback
+{
+ private static final Logger LOG = Log.getLogger(HttpInputOverHTTP.class);
+ private final SharedBlockingCallback _readBlocker = new SharedBlockingCallback();
+ private final HttpConnection _httpConnection;
+ private ByteBuffer _content;
+
+ /**
+ * @param httpConnection
+ */
+ public HttpInputOverHTTP(HttpConnection httpConnection)
+ {
+ _httpConnection = httpConnection;
+ }
+
+ @Override
+ public void recycle()
+ {
+ synchronized (lock())
+ {
+ super.recycle();
+ _content=null;
+ }
+ }
+
+ @Override
+ protected void blockForContent() throws IOException
+ {
+ while(true)
+ {
+ try (Blocker blocker=_readBlocker.acquire())
+ {
+ _httpConnection.fillInterested(blocker);
+ LOG.debug("{} block readable on {}",this,blocker);
+ blocker.block();
+ }
+
+ Object content=getNextContent();
+ if (content!=null || isFinished())
+ break;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+ }
+
+ @Override
+ protected ByteBuffer nextContent() throws IOException
+ {
+ // If we have some content available, return it
+ if (BufferUtil.hasContent(_content))
+ return _content;
+
+ // No - then we are going to need to parse some more content
+ _content=null;
+ _httpConnection.parseContent();
+
+ // If we have some content available, return it
+ if (BufferUtil.hasContent(_content))
+ return _content;
+
+ return null;
+
+ }
+
+ @Override
+ protected int remaining(ByteBuffer item)
+ {
+ return item.remaining();
+ }
+
+ @Override
+ protected int get(ByteBuffer item, byte[] buffer, int offset, int length)
+ {
+ int l = Math.min(item.remaining(), length);
+ item.get(buffer, offset, l);
+ return l;
+ }
+
+ @Override
+ protected void consume(ByteBuffer item, int length)
+ {
+ item.position(item.position()+length);
+ }
+
+ @Override
+ public void content(ByteBuffer item)
+ {
+ if (BufferUtil.hasContent(_content))
+ throw new IllegalStateException();
+ _content=item;
+ }
+
+ @Override
+ protected void unready()
+ {
+ _httpConnection.fillInterested(this);
+ }
+
+ @Override
+ public void succeeded()
+ {
+ _httpConnection.getHttpChannel().getState().onReadPossible();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ super.failed(x);
+ _httpConnection.getHttpChannel().getState().onReadPossible();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritePendingException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.WriteListener;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.IteratingNestedCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback;
+import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link HttpOutput} implements {@link ServletOutputStream}
+ * as required by the Servlet specification.</p>
+ * <p>{@link HttpOutput} buffers content written by the application until a
+ * further write will overflow the buffer, at which point it triggers a commit
+ * of the response.</p>
+ * <p>{@link HttpOutput} can be closed and reopened, to allow requests included
+ * via {@link RequestDispatcher#include(ServletRequest, ServletResponse)} to
+ * close the stream, to be reopened after the inclusion ends.</p>
+ */
+public class HttpOutput extends ServletOutputStream implements Runnable
+{
+ private static Logger LOG = Log.getLogger(HttpOutput.class);
+ private final HttpChannel<?> _channel;
+ private final SharedBlockingCallback _writeblock=new SharedBlockingCallback();
+ private long _written;
+ private ByteBuffer _aggregate;
+ private int _bufferSize;
+ private int _commitSize;
+ private WriteListener _writeListener;
+ private volatile Throwable _onError;
+
+ /*
+ ACTION OPEN ASYNC READY PENDING UNREADY CLOSED
+ -----------------------------------------------------------------------------------------------------
+ setWriteListener() READY->owp ise ise ise ise ise
+ write() OPEN ise PENDING wpe wpe eof
+ flush() OPEN ise PENDING wpe wpe eof
+ close() CLOSED CLOSED CLOSED CLOSED wpe CLOSED
+ isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
+ write completed - - - ASYNC READY->owp -
+
+ */
+ enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED }
+ private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN);
+
+ public HttpOutput(HttpChannel<?> channel)
+ {
+ _channel = channel;
+ _bufferSize = _channel.getHttpConfiguration().getOutputBufferSize();
+ _commitSize=_bufferSize/4;
+ }
+
+ public HttpChannel<?> getHttpChannel()
+ {
+ return _channel;
+ }
+
+ public boolean isWritten()
+ {
+ return _written > 0;
+ }
+
+ public long getWritten()
+ {
+ return _written;
+ }
+
+ public void reset()
+ {
+ _written = 0;
+ reopen();
+ }
+
+ public void reopen()
+ {
+ _state.set(OutputState.OPEN);
+ }
+
+ public boolean isAllContentWritten()
+ {
+ return _channel.getResponse().isAllContentWritten(_written);
+ }
+
+ protected Blocker acquireWriteBlockingCallback() throws IOException
+ {
+ return _writeblock.acquire();
+ }
+
+ protected void write(ByteBuffer content, boolean complete) throws IOException
+ {
+ try (Blocker blocker=_writeblock.acquire())
+ {
+ write(content,complete,blocker);
+ blocker.block();
+ }
+ }
+
+ protected void write(ByteBuffer content, boolean complete, Callback callback)
+ {
+ _channel.write(content,complete,callback);
+ }
+
+ @Override
+ public void close()
+ {
+ loop: while(true)
+ {
+ OutputState state=_state.get();
+ switch (state)
+ {
+ case CLOSED:
+ break loop;
+
+ case UNREADY:
+ if (_state.compareAndSet(state,OutputState.ERROR))
+ _writeListener.onError(_onError==null?new EofException("Async close"):_onError);
+ continue;
+
+ default:
+ if (_state.compareAndSet(state,OutputState.CLOSED))
+ {
+ try
+ {
+ write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER,!_channel.getResponse().isIncluding());
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ _channel.failed();
+ }
+ releaseBuffer();
+ return;
+ }
+ }
+ }
+ }
+
+ /* Called to indicated that the output is already closed (write with last==true performed) and the state needs to be updated to match */
+ void closed()
+ {
+ loop: while(true)
+ {
+ OutputState state=_state.get();
+ switch (state)
+ {
+ case CLOSED:
+ break loop;
+
+ case UNREADY:
+ if (_state.compareAndSet(state,OutputState.ERROR))
+ _writeListener.onError(_onError==null?new EofException("Async closed"):_onError);
+ continue;
+
+ default:
+ if (_state.compareAndSet(state,OutputState.CLOSED))
+ {
+ try
+ {
+ _channel.getResponse().closeOutput();
+ }
+ catch(IOException e)
+ {
+ LOG.debug(e);
+ _channel.failed();
+ }
+ releaseBuffer();
+ return;
+ }
+ }
+ }
+ }
+
+ private void releaseBuffer()
+ {
+ if (_aggregate != null)
+ {
+ _channel.getConnector().getByteBufferPool().release(_aggregate);
+ _aggregate = null;
+ }
+ }
+
+ public boolean isClosed()
+ {
+ return _state.get()==OutputState.CLOSED;
+ }
+
+ @Override
+ public void flush() throws IOException
+ {
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ write(BufferUtil.hasContent(_aggregate)?_aggregate:BufferUtil.EMPTY_BUFFER, false);
+ return;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+ continue;
+ new AsyncFlush().iterate();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case ERROR:
+ throw new EofException(_onError);
+
+ case CLOSED:
+ return;
+ }
+ break;
+ }
+ }
+
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException
+ {
+ _written+=len;
+ boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+ // Async or Blocking ?
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ // process blocking below
+ break;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+ continue;
+
+ // Should we aggregate?
+ if (!complete && len<=_commitSize)
+ {
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+
+ // YES - fill the aggregate with content from the buffer
+ int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+ // return if we are not complete, not full and filled all the content
+ if (filled==len && !BufferUtil.isFull(_aggregate))
+ {
+ if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+ throw new IllegalStateException();
+ return;
+ }
+
+ // adjust offset/length
+ off+=filled;
+ len-=filled;
+ }
+
+ // Do the asynchronous writing from the callback
+ new AsyncWrite(b,off,len,complete).iterate();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case ERROR:
+ throw new EofException(_onError);
+
+ case CLOSED:
+ throw new EofException("Closed");
+ }
+ break;
+ }
+
+
+ // handle blocking write
+
+ // Should we aggregate?
+ int capacity = getBufferSize();
+ if (!complete && len<=_commitSize)
+ {
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(capacity, false);
+
+ // YES - fill the aggregate with content from the buffer
+ int filled = BufferUtil.fill(_aggregate, b, off, len);
+
+ // return if we are not complete, not full and filled all the content
+ if (filled==len && !BufferUtil.isFull(_aggregate))
+ return;
+
+ // adjust offset/length
+ off+=filled;
+ len-=filled;
+ }
+
+ // flush any content from the aggregate
+ if (BufferUtil.hasContent(_aggregate))
+ {
+ write(_aggregate, complete && len==0);
+
+ // should we fill aggregate again from the buffer?
+ if (len>0 && !complete && len<=_commitSize)
+ {
+ BufferUtil.append(_aggregate, b, off, len);
+ return;
+ }
+ }
+
+ // write any remaining content in the buffer directly
+ if (len>0)
+ {
+ ByteBuffer wrap = ByteBuffer.wrap(b, off, len);
+ ByteBuffer view = wrap.duplicate();
+
+ // write a buffer capacity at a time to avoid JVM pooling large direct buffers
+ // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6210541
+ while (len>getBufferSize())
+ {
+ int p=view.position();
+ int l=p+getBufferSize();
+ view.limit(p+getBufferSize());
+ write(view,false);
+ len-=getBufferSize();
+ view.limit(l+Math.min(len,getBufferSize()));
+ view.position(l);
+ }
+ write(view,complete);
+ }
+ else if (complete)
+ write(BufferUtil.EMPTY_BUFFER,complete);
+
+ if (complete)
+ closed();
+
+ }
+
+ public void write(ByteBuffer buffer) throws IOException
+ {
+ _written+=buffer.remaining();
+ boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+ // Async or Blocking ?
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ // process blocking below
+ break;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+ continue;
+
+ // Do the asynchronous writing from the callback
+ new AsyncWrite(buffer,complete).iterate();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case ERROR:
+ throw new EofException(_onError);
+
+ case CLOSED:
+ throw new EofException("Closed");
+ }
+ break;
+ }
+
+
+ // handle blocking write
+ int len=BufferUtil.length(buffer);
+
+ // flush any content from the aggregate
+ if (BufferUtil.hasContent(_aggregate))
+ write(_aggregate, complete && len==0);
+
+ // write any remaining content in the buffer directly
+ if (len>0)
+ write(buffer, complete);
+ else if (complete)
+ write(BufferUtil.EMPTY_BUFFER,complete);
+
+ if (complete)
+ closed();
+ }
+
+ @Override
+ public void write(int b) throws IOException
+ {
+ _written+=1;
+ boolean complete=_channel.getResponse().isAllContentWritten(_written);
+
+ // Async or Blocking ?
+ while(true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+ BufferUtil.append(_aggregate, (byte)b);
+
+ // Check if all written or full
+ if (complete || BufferUtil.isFull(_aggregate))
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ write(_aggregate, complete, blocker);
+ blocker.block();
+ }
+ if (complete)
+ closed();
+ }
+ break;
+
+ case ASYNC:
+ throw new IllegalStateException("isReady() not called");
+
+ case READY:
+ if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
+ continue;
+
+ if (_aggregate == null)
+ _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+ BufferUtil.append(_aggregate, (byte)b);
+
+ // Check if all written or full
+ if (!complete && !BufferUtil.isFull(_aggregate))
+ {
+ if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+ throw new IllegalStateException();
+ return;
+ }
+
+ // Do the asynchronous writing from the callback
+ new AsyncFlush().iterate();
+ return;
+
+ case PENDING:
+ case UNREADY:
+ throw new WritePendingException();
+
+ case ERROR:
+ throw new EofException(_onError);
+
+ case CLOSED:
+ throw new EofException("Closed");
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void print(String s) throws IOException
+ {
+ if (isClosed())
+ throw new IOException("Closed");
+
+ write(s.getBytes(_channel.getResponse().getCharacterEncoding()));
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Blocking send of content.
+ * @param content The content to send.
+ * @throws IOException
+ */
+ public void sendContent(ByteBuffer content) throws IOException
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ write(content,true,blocker);
+ blocker.block();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Blocking send of content.
+ * @param in The content to send
+ * @throws IOException
+ */
+ public void sendContent(InputStream in) throws IOException
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ new InputStreamWritingCB(in,blocker).iterate();
+ blocker.block();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Blocking send of content.
+ * @param in The content to send
+ * @throws IOException
+ */
+ public void sendContent(ReadableByteChannel in) throws IOException
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ new ReadableByteChannelWritingCB(in,blocker).iterate();
+ blocker.block();
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Blocking send of content.
+ * @param content The content to send
+ * @throws IOException
+ */
+ public void sendContent(HttpContent content) throws IOException
+ {
+ try(Blocker blocker=_writeblock.acquire())
+ {
+ sendContent(content,blocker);
+ blocker.block();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Asynchronous send of content.
+ * @param content The content to send
+ * @param callback The callback to use to notify success or failure
+ */
+ public void sendContent(ByteBuffer content, final Callback callback)
+ {
+ write(content,true,new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ closed();
+ callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ callback.failed(x);
+ }
+ });
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Asynchronous send of content.
+ * @param in The content to send as a stream. The stream will be closed
+ * after reading all content.
+ * @param callback The callback to use to notify success or failure
+ */
+ public void sendContent(InputStream in, Callback callback)
+ {
+ new InputStreamWritingCB(in,callback).iterate();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Asynchronous send of content.
+ * @param in The content to send as a channel. The channel will be closed
+ * after reading all content.
+ * @param callback The callback to use to notify success or failure
+ */
+ public void sendContent(ReadableByteChannel in, Callback callback)
+ {
+ new ReadableByteChannelWritingCB(in,callback).iterate();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Asynchronous send of content.
+ * @param httpContent The content to send
+ * @param callback The callback to use to notify success or failure
+ */
+ public void sendContent(HttpContent httpContent, Callback callback) throws IOException
+ {
+ if (BufferUtil.hasContent(_aggregate))
+ throw new IOException("written");
+ if (_channel.isCommitted())
+ throw new IOException("committed");
+
+ while (true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
+ continue;
+ break;
+ case ERROR:
+ throw new EofException(_onError);
+ case CLOSED:
+ throw new EofException("Closed");
+ default:
+ throw new IllegalStateException();
+ }
+ break;
+ }
+ ByteBuffer buffer= _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
+ if (buffer == null)
+ buffer = httpContent.getIndirectBuffer();
+
+ if (buffer!=null)
+ {
+ sendContent(buffer,callback);
+ return;
+ }
+
+ ReadableByteChannel rbc=httpContent.getReadableByteChannel();
+ if (rbc!=null)
+ {
+ // Close of the rbc is done by the async sendContent
+ sendContent(rbc,callback);
+ return;
+ }
+
+ InputStream in = httpContent.getInputStream();
+ if ( in!=null )
+ {
+ sendContent(in,callback);
+ return;
+ }
+
+ callback.failed(new IllegalArgumentException("unknown content for "+httpContent));
+ }
+
+ public int getBufferSize()
+ {
+ return _bufferSize;
+ }
+
+ public void setBufferSize(int size)
+ {
+ _bufferSize = size;
+ _commitSize = size;
+ }
+
+ public void resetBuffer()
+ {
+ if (BufferUtil.hasContent(_aggregate))
+ BufferUtil.clear(_aggregate);
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener)
+ {
+ if (!_channel.getState().isAsync())
+ throw new IllegalStateException("!ASYNC");
+
+ if (_state.compareAndSet(OutputState.OPEN, OutputState.READY))
+ {
+ _writeListener = writeListener;
+ _channel.getState().onWritePossible();
+ }
+ else
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @see javax.servlet.ServletOutputStream#isReady()
+ */
+ @Override
+ public boolean isReady()
+ {
+ while (true)
+ {
+ switch(_state.get())
+ {
+ case OPEN:
+ return true;
+ case ASYNC:
+ if (!_state.compareAndSet(OutputState.ASYNC, OutputState.READY))
+ continue;
+ return true;
+ case READY:
+ return true;
+ case PENDING:
+ if (!_state.compareAndSet(OutputState.PENDING, OutputState.UNREADY))
+ continue;
+ return false;
+ case UNREADY:
+ return false;
+
+ case ERROR:
+ return true;
+
+ case CLOSED:
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ loop: while (true)
+ {
+ OutputState state = _state.get();
+
+ if(_onError!=null)
+ {
+ switch(state)
+ {
+ case CLOSED:
+ case ERROR:
+ _onError=null;
+ break loop;
+
+ default:
+ if (_state.compareAndSet(state, OutputState.ERROR))
+ {
+ Throwable th=_onError;
+ _onError=null;
+ LOG.debug("onError",th);
+ _writeListener.onError(th);
+ close();
+
+ break loop;
+ }
+
+ }
+ continue loop;
+ }
+
+ switch(_state.get())
+ {
+ case READY:
+ case CLOSED:
+ // even though a write is not possible, because a close has
+ // occurred, we need to call onWritePossible to tell async
+ // producer that the last write completed.
+ try
+ {
+ _writeListener.onWritePossible();
+ break loop;
+ }
+ catch (Throwable e)
+ {
+ _onError=e;
+ }
+ break;
+ default:
+
+ }
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),_state.get());
+ }
+
+ private abstract class AsyncICB extends IteratingCallback
+ {
+ @Override
+ protected void completed()
+ {
+ while(true)
+ {
+ OutputState last=_state.get();
+ switch(last)
+ {
+ case PENDING:
+ if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
+ continue;
+ break;
+
+ case UNREADY:
+ if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
+ continue;
+ _channel.getState().onWritePossible();
+ break;
+
+ case CLOSED:
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void failed(Throwable e)
+ {
+ super.failed(e);
+ _onError=e;
+ _channel.getState().onWritePossible();
+ }
+ }
+
+
+ private class AsyncFlush extends AsyncICB
+ {
+ protected volatile boolean _flushed;
+
+ public AsyncFlush()
+ {
+ }
+
+ @Override
+ protected Action process()
+ {
+ if (BufferUtil.hasContent(_aggregate))
+ {
+ _flushed=true;
+ write(_aggregate, false, this);
+ return Action.SCHEDULED;
+ }
+
+ if (!_flushed)
+ {
+ _flushed=true;
+ write(BufferUtil.EMPTY_BUFFER,false,this);
+ return Action.SCHEDULED;
+ }
+
+ return Action.SUCCEEDED;
+ }
+ }
+
+
+
+ private class AsyncWrite extends AsyncICB
+ {
+ private final ByteBuffer _buffer;
+ private final ByteBuffer _slice;
+ private final boolean _complete;
+ private final int _len;
+ protected volatile boolean _completed;
+
+ public AsyncWrite(byte[] b, int off, int len, boolean complete)
+ {
+ _buffer=ByteBuffer.wrap(b, off, len);
+ _len=len;
+ // always use a view for large byte arrays to avoid JVM pooling large direct buffers
+ _slice=_len<getBufferSize()?null:_buffer.duplicate();
+ _complete=complete;
+ }
+
+ public AsyncWrite(ByteBuffer buffer, boolean complete)
+ {
+ _buffer=buffer;
+ _len=buffer.remaining();
+ // Use a slice buffer for large indirect to avoid JVM pooling large direct buffers
+ _slice=_buffer.isDirect()||_len<getBufferSize()?null:_buffer.duplicate();
+ _complete=complete;
+ }
+
+ @Override
+ protected Action process()
+ {
+ // flush any content from the aggregate
+ if (BufferUtil.hasContent(_aggregate))
+ {
+ _completed=_len==0;
+ write(_aggregate, _complete && _completed, this);
+ return Action.SCHEDULED;
+ }
+
+ // Can we just aggregate the remainder?
+ if (!_complete && _len<BufferUtil.space(_aggregate) && _len<_commitSize)
+ {
+ int position = BufferUtil.flipToFill(_aggregate);
+ BufferUtil.put(_buffer,_aggregate);
+ BufferUtil.flipToFlush(_aggregate, position);
+ return Action.SUCCEEDED;
+ }
+
+ // Is there data left to write?
+ if (_buffer.hasRemaining())
+ {
+ // if there is no slice, just write it
+ if (_slice==null)
+ {
+ _completed=true;
+ write(_buffer, _complete, this);
+ return Action.SCHEDULED;
+ }
+
+ // otherwise take a slice
+ int p=_buffer.position();
+ int l=Math.min(getBufferSize(),_buffer.remaining());
+ int pl=p+l;
+ _slice.limit(pl);
+ _buffer.position(pl);
+ _slice.position(p);
+ _completed=!_buffer.hasRemaining();
+ write(_slice, _complete && _completed, this);
+ return Action.SCHEDULED;
+ }
+
+ // all content written, but if we have not yet signal completion, we
+ // need to do so
+ if (_complete && !_completed)
+ {
+ _completed=true;
+ write(BufferUtil.EMPTY_BUFFER, _complete, this);
+ return Action.SCHEDULED;
+ }
+
+ return Action.SUCCEEDED;
+ }
+
+ @Override
+ protected void completed()
+ {
+ super.completed();
+ if (_complete)
+ closed();
+ }
+
+
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** An iterating callback that will take content from an
+ * InputStream and write it to the associated {@link HttpChannel}.
+ * A non direct buffer of size {@link HttpOutput#getBufferSize()} is used.
+ * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
+ * be notified as each buffer is written and only once all the input is consumed will the
+ * wrapped {@link Callback#succeeded()} method be called.
+ */
+ private class InputStreamWritingCB extends IteratingNestedCallback
+ {
+ private final InputStream _in;
+ private final ByteBuffer _buffer;
+ private boolean _eof;
+
+ public InputStreamWritingCB(InputStream in, Callback callback)
+ {
+ super(callback);
+ _in=in;
+ _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
+ }
+
+ @Override
+ protected Action process() throws Exception
+ {
+ // Only return if EOF has previously been read and thus
+ // a write done with EOF=true
+ if (_eof)
+ {
+ // Handle EOF
+ _in.close();
+ closed();
+ _channel.getByteBufferPool().release(_buffer);
+ return Action.SUCCEEDED;
+ }
+
+ // Read until buffer full or EOF
+ int len=0;
+ while (len<_buffer.capacity() && !_eof)
+ {
+ int r=_in.read(_buffer.array(),_buffer.arrayOffset()+len,_buffer.capacity()-len);
+ if (r<0)
+ _eof=true;
+ else
+ len+=r;
+ }
+
+ // write what we have
+ _buffer.position(0);
+ _buffer.limit(len);
+ write(_buffer,_eof,this);
+ return Action.SCHEDULED;
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ super.failed(x);
+ _channel.getByteBufferPool().release(_buffer);
+ try
+ {
+ _in.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /** An iterating callback that will take content from a
+ * ReadableByteChannel and write it to the {@link HttpChannel}.
+ * A {@link ByteBuffer} of size {@link HttpOutput#getBufferSize()} is used that will be direct if
+ * {@link HttpChannel#useDirectBuffers()} is true.
+ * This callback is passed to the {@link HttpChannel#write(ByteBuffer, boolean, Callback)} to
+ * be notified as each buffer is written and only once all the input is consumed will the
+ * wrapped {@link Callback#succeeded()} method be called.
+ */
+ private class ReadableByteChannelWritingCB extends IteratingNestedCallback
+ {
+ private final ReadableByteChannel _in;
+ private final ByteBuffer _buffer;
+ private boolean _eof;
+
+ public ReadableByteChannelWritingCB(ReadableByteChannel in, Callback callback)
+ {
+ super(callback);
+ _in=in;
+ _buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
+ }
+
+ @Override
+ protected Action process() throws Exception
+ {
+ // Only return if EOF has previously been read and thus
+ // a write done with EOF=true
+ if (_eof)
+ {
+ _in.close();
+ closed();
+ _channel.getByteBufferPool().release(_buffer);
+ return Action.SUCCEEDED;
+ }
+
+ // Read from stream until buffer full or EOF
+ _buffer.clear();
+ while (_buffer.hasRemaining() && !_eof)
+ _eof = (_in.read(_buffer)) < 0;
+
+ // write what we have
+ _buffer.flip();
+ write(_buffer,_eof,this);
+
+ return Action.SCHEDULED;
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ super.failed(x);
+ _channel.getByteBufferPool().release(_buffer);
+ try
+ {
+ _in.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.util.Callback;
+
+public interface HttpTransport
+{
+ void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback);
+
+ void send(ByteBuffer content, boolean lastContent, Callback callback);
+
+ void completed();
+
+ /* ------------------------------------------------------------ */
+ /** Abort transport.
+ * This is called when an error response needs to be sent, but the response is already committed.
+ * Abort to should terminate the transport in a way that can indicate abnormal response to the client.
+ */
+ void abort();
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.ByteArrayOutputStream2;
+
+/**
+ *
+ */
+public abstract class HttpWriter extends Writer
+{
+ public static final int MAX_OUTPUT_CHARS = 512;
+
+ final HttpOutput _out;
+ final ByteArrayOutputStream2 _bytes;
+ final char[] _chars;
+
+ /* ------------------------------------------------------------ */
+ public HttpWriter(HttpOutput out)
+ {
+ _out=out;
+ _chars=new char[MAX_OUTPUT_CHARS];
+ _bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void close() throws IOException
+ {
+ _out.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void flush() throws IOException
+ {
+ _out.flush();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (String s,int offset, int length) throws IOException
+ {
+ while (length > MAX_OUTPUT_CHARS)
+ {
+ write(s, offset, MAX_OUTPUT_CHARS);
+ offset += MAX_OUTPUT_CHARS;
+ length -= MAX_OUTPUT_CHARS;
+ }
+
+ s.getChars(offset, offset + length, _chars, 0);
+ write(_chars, 0, length);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (char[] s,int offset, int length) throws IOException
+ {
+ throw new AbstractMethodError();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Byte range inclusive of end points.
+ * <PRE>
+ *
+ * parses the following types of byte ranges:
+ *
+ * bytes=100-499
+ * bytes=-300
+ * bytes=100-
+ * bytes=1-2,2-3,6-,-2
+ *
+ * given an entity length, converts range to string
+ *
+ * bytes 100-499/500
+ *
+ * </PRE>
+ *
+ * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
+ * <p>
+ * And yes the spec does strangely say that while 10-20, is bytes 10 to 20 and 10- is bytes 10 until the end that -20 IS NOT bytes 0-20, but the last 20 bytes of the content.
+ *
+ * @version $version$
+ *
+ */
+public class InclusiveByteRange
+{
+ private static final Logger LOG = Log.getLogger(InclusiveByteRange.class);
+
+ long first = 0;
+ long last = 0;
+
+ public InclusiveByteRange(long first, long last)
+ {
+ this.first = first;
+ this.last = last;
+ }
+
+ public long getFirst()
+ {
+ return first;
+ }
+
+ public long getLast()
+ {
+ return last;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param headers Enumeration of Range header fields.
+ * @param size Size of the resource.
+ * @return LazyList of satisfiable ranges
+ */
+ public static List<InclusiveByteRange> satisfiableRanges(Enumeration headers, long size)
+ {
+ Object satRanges=null;
+
+ // walk through all Range headers
+ headers:
+ while (headers.hasMoreElements())
+ {
+ String header = (String) headers.nextElement();
+ StringTokenizer tok = new StringTokenizer(header,"=,",false);
+ String t=null;
+ try
+ {
+ // read all byte ranges for this header
+ while (tok.hasMoreTokens())
+ {
+ try
+ {
+ t = tok.nextToken().trim();
+
+ long first = -1;
+ long last = -1;
+ int d = t.indexOf('-');
+ if (d < 0 || t.indexOf("-",d + 1) >= 0)
+ {
+ if ("bytes".equals(t))
+ continue;
+ LOG.warn("Bad range format: {}",t);
+ continue headers;
+ }
+ else if (d == 0)
+ {
+ if (d + 1 < t.length())
+ last = Long.parseLong(t.substring(d + 1).trim());
+ else
+ {
+ LOG.warn("Bad range format: {}",t);
+ continue;
+ }
+ }
+ else if (d + 1 < t.length())
+ {
+ first = Long.parseLong(t.substring(0,d).trim());
+ last = Long.parseLong(t.substring(d + 1).trim());
+ }
+ else
+ first = Long.parseLong(t.substring(0,d).trim());
+
+ if (first == -1 && last == -1)
+ continue headers;
+
+ if (first != -1 && last != -1 && (first > last))
+ continue headers;
+
+ if (first < size)
+ {
+ InclusiveByteRange range = new InclusiveByteRange(first,last);
+ satRanges = LazyList.add(satRanges,range);
+ }
+ }
+ catch (NumberFormatException e)
+ {
+ LOG.warn("Bad range format: {}",t);
+ LOG.ignore(e);
+ continue;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Bad range format: {}",t);
+ LOG.ignore(e);
+ }
+ }
+ return LazyList.getList(satRanges,true);
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getFirst(long size)
+ {
+ if (first<0)
+ {
+ long tf=size-last;
+ if (tf<0)
+ tf=0;
+ return tf;
+ }
+ return first;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getLast(long size)
+ {
+ if (first<0)
+ return size-1;
+
+ if (last<0 ||last>=size)
+ return size-1;
+ return last;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getSize(long size)
+ {
+ return getLast(size)-getFirst(size)+1;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String toHeaderRangeString(long size)
+ {
+ StringBuilder sb = new StringBuilder(40);
+ sb.append("bytes ");
+ sb.append(getFirst(size));
+ sb.append('-');
+ sb.append(getLast(size));
+ sb.append("/");
+ sb.append(size);
+ return sb.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String to416HeaderRangeString(long size)
+ {
+ StringBuilder sb = new StringBuilder(40);
+ sb.append("bytes */");
+ sb.append(size);
+ return sb.toString();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(60);
+ sb.append(Long.toString(first));
+ sb.append(":");
+ sb.append(Long.toString(last));
+ return sb.toString();
+ }
+
+
+}
+
+
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+
+/**
+ */
+public class Iso88591HttpWriter extends HttpWriter
+{
+ /* ------------------------------------------------------------ */
+ public Iso88591HttpWriter(HttpOutput out)
+ {
+ super(out);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (char[] s,int offset, int length) throws IOException
+ {
+ HttpOutput out = _out;
+ if (length==0 && out.isAllContentWritten())
+ {
+ close();
+ return;
+ }
+
+ if (length==1)
+ {
+ int c=s[offset];
+ out.write(c<256?c:'?');
+ return;
+ }
+
+ while (length > 0)
+ {
+ _bytes.reset();
+ int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+ byte[] buffer=_bytes.getBuf();
+ int bytes=_bytes.getCount();
+
+ if (chars>buffer.length-bytes)
+ chars=buffer.length-bytes;
+
+ for (int i = 0; i < chars; i++)
+ {
+ int c = s[offset+i];
+ buffer[bytes++]=(byte)(c<256?c:'?');
+ }
+ if (bytes>=0)
+ _bytes.setCount(bytes);
+
+ _bytes.writeTo(out);
+ length-=chars;
+ offset+=chars;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+public class LocalConnector extends AbstractConnector
+{
+ private final BlockingQueue<LocalEndPoint> _connects = new LinkedBlockingQueue<>();
+
+
+ public LocalConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories)
+ {
+ super(server,executor,scheduler,pool,acceptors,factories);
+ setIdleTimeout(30000);
+ }
+
+ public LocalConnector(Server server)
+ {
+ this(server, null, null, null, -1, new HttpConnectionFactory());
+ }
+
+ public LocalConnector(Server server, SslContextFactory sslContextFactory)
+ {
+ this(server, null, null, null, -1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+ }
+
+ public LocalConnector(Server server, ConnectionFactory connectionFactory)
+ {
+ this(server, null, null, null, -1, connectionFactory);
+ }
+
+ public LocalConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+ {
+ this(server, null, null, null, -1, AbstractConnectionFactory.getFactories(sslContextFactory,connectionFactory));
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return this;
+ }
+
+ /** Sends requests and get responses based on thread activity.
+ * Returns all the responses received once the thread activity has
+ * returned to the level it was before the requests.
+ * <p>
+ * This methods waits until the connection is closed or
+ * is idle for 1s before returning the responses.
+ * @param requests the requests
+ * @return the responses
+ * @throws Exception if the requests fail
+ */
+ public String getResponses(String requests) throws Exception
+ {
+ return getResponses(requests, 5, TimeUnit.SECONDS);
+ }
+
+ /** Sends requests and get responses based on thread activity.
+ * Returns all the responses received once the thread activity has
+ * returned to the level it was before the requests.
+ * <p>
+ * This methods waits until the connection is closed or
+ * an idle period before returning the responses.
+ * @param requests the requests
+ * @param idleFor The time the response stream must be idle for before returning
+ * @param units The units of idleFor
+ * @return the responses
+ * @throws Exception if the requests fail
+ */
+ public String getResponses(String requests,long idleFor,TimeUnit units) throws Exception
+ {
+ ByteBuffer result = getResponses(BufferUtil.toBuffer(requests,StandardCharsets.UTF_8),idleFor,units);
+ return result==null?null:BufferUtil.toString(result,StandardCharsets.UTF_8);
+ }
+
+ /** Sends requests and get's responses based on thread activity.
+ * Returns all the responses received once the thread activity has
+ * returned to the level it was before the requests.
+ * <p>
+ * This methods waits until the connection is closed or
+ * is idle for 1s before returning the responses.
+ * @param requestsBuffer the requests
+ * @return the responses
+ * @throws Exception if the requests fail
+ */
+ public ByteBuffer getResponses(ByteBuffer requestsBuffer) throws Exception
+ {
+ return getResponses(requestsBuffer, 5, TimeUnit.SECONDS);
+ }
+
+ /** Sends requests and get's responses based on thread activity.
+ * Returns all the responses received once the thread activity has
+ * returned to the level it was before the requests.
+ * <p>
+ * This methods waits until the connection is closed or
+ * an idle period before returning the responses.
+ * @param requestsBuffer the requests
+ * @param idleFor The time the response stream must be idle for before returning
+ * @param units The units of idleFor
+ * @return the responses
+ * @throws Exception if the requests fail
+ */
+ public ByteBuffer getResponses(ByteBuffer requestsBuffer,long idleFor,TimeUnit units) throws Exception
+ {
+ LOG.debug("requests {}", BufferUtil.toUTF8String(requestsBuffer));
+ LocalEndPoint endp = executeRequest(requestsBuffer);
+ endp.waitUntilClosedOrIdleFor(idleFor,units);
+ ByteBuffer responses = endp.takeOutput();
+ endp.getConnection().close();
+ LOG.debug("responses {}", BufferUtil.toUTF8String(responses));
+ return responses;
+ }
+
+ /**
+ * Execute a request and return the EndPoint through which
+ * responses can be received.
+ * @param rawRequest the request
+ * @return the local endpoint
+ */
+ public LocalEndPoint executeRequest(String rawRequest)
+ {
+ return executeRequest(BufferUtil.toBuffer(rawRequest, StandardCharsets.UTF_8));
+ }
+
+ private LocalEndPoint executeRequest(ByteBuffer rawRequest)
+ {
+ LocalEndPoint endp = new LocalEndPoint();
+ endp.setInput(rawRequest);
+ _connects.add(endp);
+ return endp;
+ }
+
+ @Override
+ protected void accept(int acceptorID) throws IOException, InterruptedException
+ {
+ LOG.debug("accepting {}", acceptorID);
+ LocalEndPoint endPoint = _connects.take();
+ endPoint.onOpen();
+ onEndPointOpened(endPoint);
+
+ Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint);
+ endPoint.setConnection(connection);
+
+ connection.onOpen();
+ }
+
+ public class LocalEndPoint extends ByteArrayEndPoint
+ {
+ private final CountDownLatch _closed = new CountDownLatch(1);
+
+ public LocalEndPoint()
+ {
+ super(getScheduler(), LocalConnector.this.getIdleTimeout());
+ setGrowOutput(true);
+ }
+
+ public void addInput(String s)
+ {
+ // TODO this is a busy wait
+ while(getIn()==null || BufferUtil.hasContent(getIn()))
+ Thread.yield();
+ setInput(BufferUtil.toBuffer(s, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public void close()
+ {
+ boolean wasOpen=isOpen();
+ super.close();
+ if (wasOpen)
+ {
+// connectionClosed(getConnection());
+ getConnection().onClose();
+ onClose();
+ }
+ }
+
+ @Override
+ public void onClose()
+ {
+ LocalConnector.this.onEndPointClosed(this);
+ super.onClose();
+ _closed.countDown();
+ }
+
+ @Override
+ public void shutdownOutput()
+ {
+ super.shutdownOutput();
+ close();
+ }
+
+ public void waitUntilClosed()
+ {
+ while (isOpen())
+ {
+ try
+ {
+ if (!_closed.await(10,TimeUnit.SECONDS))
+ break;
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+
+ public void waitUntilClosedOrIdleFor(long idleFor,TimeUnit units)
+ {
+ Thread.yield();
+ int size=getOutput().remaining();
+ while (isOpen())
+ {
+ try
+ {
+ if (!_closed.await(idleFor,units))
+ {
+ if (size==getOutput().remaining())
+ {
+ LOG.debug("idle for {} {}",idleFor,units);
+ return;
+ }
+ size=getOutput().remaining();
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+
+/* ------------------------------------------------------------ */
+/** A monitor for low resources
+ * <p>An instance of this class will monitor all the connectors of a server (or a set of connectors
+ * configured with {@link #setMonitoredConnectors(Collection)}) for a low resources state.
+ * Low resources can be detected by:<ul>
+ * <li>{@link ThreadPool#isLowOnThreads()} if {@link Connector#getExecutor()} is
+ * an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.<li>
+ * <li>If {@link #setMaxMemory(long)} is non zero then low resources is detected if the JVMs
+ * {@link Runtime} instance has {@link Runtime#totalMemory()} minus {@link Runtime#freeMemory()}
+ * greater than {@link #getMaxMemory()}</li>
+ * <li>If {@link #setMaxConnections(int)} is non zero then low resources is dected if the total number
+ * of connections exceeds {@link #getMaxConnections()}</li>
+ * </ul>
+ * </p>
+ * <p>Once low resources state is detected, the cause is logged and all existing connections returned
+ * by {@link Connector#getConnectedEndPoints()} have {@link EndPoint#setIdleTimeout(long)} set
+ * to {@link #getLowResourcesIdleTimeout()}. New connections are not affected, however if the low
+ * resources state persists for more than {@link #getMaxLowResourcesTime()}, then the
+ * {@link #getLowResourcesIdleTimeout()} to all connections again. Once the low resources state is
+ * cleared, the idle timeout is reset to the connector default given by {@link Connector#getIdleTimeout()}.
+ * </p>
+ */
+@ManagedObject ("Monitor for low resource conditions and activate a low resource mode if detected")
+public class LowResourceMonitor extends AbstractLifeCycle
+{
+ private static final Logger LOG = Log.getLogger(LowResourceMonitor.class);
+ private final Server _server;
+ private Scheduler _scheduler;
+ private Connector[] _monitoredConnectors;
+ private int _period=1000;
+ private int _maxConnections;
+ private long _maxMemory;
+ private int _lowResourcesIdleTimeout=1000;
+ private int _maxLowResourcesTime=0;
+ private boolean _monitorThreads=true;
+ private final AtomicBoolean _low = new AtomicBoolean();
+ private String _cause;
+ private String _reasons;
+ private long _lowStarted;
+
+
+ private final Runnable _monitor = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ if (isRunning())
+ {
+ monitor();
+ _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+ }
+ }
+ };
+
+ public LowResourceMonitor(@Name("server") Server server)
+ {
+ _server=server;
+ }
+
+ @ManagedAttribute("Are the monitored connectors low on resources?")
+ public boolean isLowOnResources()
+ {
+ return _low.get();
+ }
+
+ @ManagedAttribute("The reason(s) the monitored connectors are low on resources")
+ public String getLowResourcesReasons()
+ {
+ return _reasons;
+ }
+
+ @ManagedAttribute("Get the timestamp in ms since epoch that low resources state started")
+ public long getLowResourcesStarted()
+ {
+ return _lowStarted;
+ }
+
+ @ManagedAttribute("The monitored connectors. If null then all server connectors are monitored")
+ public Collection<Connector> getMonitoredConnectors()
+ {
+ if (_monitoredConnectors==null)
+ return Collections.emptyList();
+ return Arrays.asList(_monitoredConnectors);
+ }
+
+ /**
+ * @param monitoredConnectors The collections of Connectors that should be monitored for low resources.
+ */
+ public void setMonitoredConnectors(Collection<Connector> monitoredConnectors)
+ {
+ if (monitoredConnectors==null || monitoredConnectors.size()==0)
+ _monitoredConnectors=null;
+ else
+ _monitoredConnectors = monitoredConnectors.toArray(new Connector[monitoredConnectors.size()]);
+ }
+
+ @ManagedAttribute("The monitor period in ms")
+ public int getPeriod()
+ {
+ return _period;
+ }
+
+ /**
+ * @param periodMS The period in ms to monitor for low resources
+ */
+ public void setPeriod(int periodMS)
+ {
+ _period = periodMS;
+ }
+
+ @ManagedAttribute("True if low available threads status is monitored")
+ public boolean getMonitorThreads()
+ {
+ return _monitorThreads;
+ }
+
+ /**
+ * @param monitorThreads If true, check connectors executors to see if they are
+ * {@link ThreadPool} instances that are low on threads.
+ */
+ public void setMonitorThreads(boolean monitorThreads)
+ {
+ _monitorThreads = monitorThreads;
+ }
+
+ @ManagedAttribute("The maximum connections allowed for the monitored connectors before low resource handling is activated")
+ public int getMaxConnections()
+ {
+ return _maxConnections;
+ }
+
+ /**
+ * @param maxConnections The maximum connections before low resources state is triggered
+ */
+ public void setMaxConnections(int maxConnections)
+ {
+ _maxConnections = maxConnections;
+ }
+
+ @ManagedAttribute("The maximum memory (in bytes) that can be used before low resources is triggered. Memory used is calculated as (totalMemory-freeMemory).")
+ public long getMaxMemory()
+ {
+ return _maxMemory;
+ }
+
+ /**
+ * @param maxMemoryBytes The maximum memory in bytes in use before low resources is triggered.
+ */
+ public void setMaxMemory(long maxMemoryBytes)
+ {
+ _maxMemory = maxMemoryBytes;
+ }
+
+ @ManagedAttribute("The idletimeout in ms to apply to all existing connections when low resources is detected")
+ public int getLowResourcesIdleTimeout()
+ {
+ return _lowResourcesIdleTimeout;
+ }
+
+ /**
+ * @param lowResourcesIdleTimeoutMS The timeout in ms to apply to EndPoints when in the low resources state.
+ */
+ public void setLowResourcesIdleTimeout(int lowResourcesIdleTimeoutMS)
+ {
+ _lowResourcesIdleTimeout = lowResourcesIdleTimeoutMS;
+ }
+
+ @ManagedAttribute("The maximum time in ms that low resources condition can persist before lowResourcesIdleTimeout is applied to new connections as well as existing connections")
+ public int getMaxLowResourcesTime()
+ {
+ return _maxLowResourcesTime;
+ }
+
+ /**
+ * @param maxLowResourcesTimeMS The time in milliseconds that a low resource state can persist before the low resource idle timeout is reapplied to all connections
+ */
+ public void setMaxLowResourcesTime(int maxLowResourcesTimeMS)
+ {
+ _maxLowResourcesTime = maxLowResourcesTimeMS;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ _scheduler = _server.getBean(Scheduler.class);
+
+ if (_scheduler==null)
+ {
+ _scheduler=new LRMScheduler();
+ _scheduler.start();
+ }
+ super.doStart();
+
+ _scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ if (_scheduler instanceof LRMScheduler)
+ _scheduler.stop();
+ super.doStop();
+ }
+
+ protected Connector[] getMonitoredOrServerConnectors()
+ {
+ if (_monitoredConnectors!=null && _monitoredConnectors.length>0)
+ return _monitoredConnectors;
+ return _server.getConnectors();
+ }
+
+ protected void monitor()
+ {
+ String reasons=null;
+ String cause="";
+ int connections=0;
+
+ for(Connector connector : getMonitoredOrServerConnectors())
+ {
+ connections+=connector.getConnectedEndPoints().size();
+
+ Executor executor = connector.getExecutor();
+ if (executor instanceof ThreadPool)
+ {
+ ThreadPool threadpool=(ThreadPool) executor;
+ if (_monitorThreads && threadpool.isLowOnThreads())
+ {
+ reasons=low(reasons,"Low on threads: "+threadpool);
+ cause+="T";
+ }
+ }
+ }
+
+ if (_maxConnections>0 && connections>_maxConnections)
+ {
+ reasons=low(reasons,"Max Connections exceeded: "+connections+">"+_maxConnections);
+ cause+="C";
+ }
+
+ long memory=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
+ if (_maxMemory>0 && memory>_maxMemory)
+ {
+ reasons=low(reasons,"Max memory exceeded: "+memory+">"+_maxMemory);
+ cause+="M";
+ }
+
+
+ if (reasons!=null)
+ {
+ // Log the reasons if there is any change in the cause
+ if (!cause.equals(_cause))
+ {
+ LOG.warn("Low Resources: {}",reasons);
+ _cause=cause;
+ }
+
+ // Enter low resources state?
+ if (_low.compareAndSet(false,true))
+ {
+ _reasons=reasons;
+ _lowStarted=System.currentTimeMillis();
+ setLowResources();
+ }
+
+ // Too long in low resources state?
+ if (_maxLowResourcesTime>0 && (System.currentTimeMillis()-_lowStarted)>_maxLowResourcesTime)
+ setLowResources();
+ }
+ else
+ {
+ if (_low.compareAndSet(true,false))
+ {
+ LOG.info("Low Resources cleared");
+ _reasons=null;
+ _lowStarted=0;
+ _cause=null;
+ clearLowResources();
+ }
+ }
+ }
+
+ protected void setLowResources()
+ {
+ for(Connector connector : getMonitoredOrServerConnectors())
+ {
+ for (EndPoint endPoint : connector.getConnectedEndPoints())
+ endPoint.setIdleTimeout(_lowResourcesIdleTimeout);
+ }
+ }
+
+ protected void clearLowResources()
+ {
+ for(Connector connector : getMonitoredOrServerConnectors())
+ {
+ for (EndPoint endPoint : connector.getConnectedEndPoints())
+ endPoint.setIdleTimeout(connector.getIdleTimeout());
+ }
+ }
+
+ private String low(String reasons, String newReason)
+ {
+ if (reasons==null)
+ return newReason;
+ return reasons+", "+newReason;
+ }
+
+
+ private static class LRMScheduler extends ScheduledExecutorScheduler
+ {
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/**
+ * This {@link RequestLog} implementation outputs logs in the pseudo-standard
+ * NCSA common log format. Configuration options allow a choice between the
+ * standard Common Log Format (as used in the 3 log format) and the Combined Log
+ * Format (single log format). This log format can be output by most web
+ * servers, and almost all web log analysis software can understand these
+ * formats.
+ */
+@ManagedObject("NCSA standard format request log")
+public class NCSARequestLog extends AbstractNCSARequestLog implements RequestLog
+{
+ private String _filename;
+ private boolean _append;
+ private int _retainDays;
+ private boolean _closeOut;
+ private String _filenameDateFormat = null;
+ private transient OutputStream _out;
+ private transient OutputStream _fileOut;
+ private transient Writer _writer;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create request log object with default settings.
+ */
+ public NCSARequestLog()
+ {
+ setExtended(true);
+ _append = true;
+ _retainDays = 31;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create request log object with specified output file name.
+ *
+ * @param filename the file name for the request log.
+ * This may be in the format expected
+ * by {@link RolloverFileOutputStream}
+ */
+ public NCSARequestLog(String filename)
+ {
+ setExtended(true);
+ _append = true;
+ _retainDays = 31;
+ setFilename(filename);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the output file name of the request log.
+ * The file name may be in the format expected by
+ * {@link RolloverFileOutputStream}.
+ *
+ * @param filename file name of the request log
+ *
+ */
+ public void setFilename(String filename)
+ {
+ if (filename != null)
+ {
+ filename = filename.trim();
+ if (filename.length() == 0)
+ filename = null;
+ }
+ _filename = filename;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the output file name of the request log.
+ *
+ * @return file name of the request log
+ */
+ @ManagedAttribute("file of log")
+ public String getFilename()
+ {
+ return _filename;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the file name of the request log with the expanded
+ * date wildcard if the output is written to the disk using
+ * {@link RolloverFileOutputStream}.
+ *
+ * @return file name of the request log, or null if not applicable
+ */
+ public String getDatedFilename()
+ {
+ if (_fileOut instanceof RolloverFileOutputStream)
+ return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected boolean isEnabled()
+ {
+ return (_fileOut != null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the number of days before rotated log files are deleted.
+ *
+ * @param retainDays number of days to keep a log file
+ */
+ public void setRetainDays(int retainDays)
+ {
+ _retainDays = retainDays;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the number of days before rotated log files are deleted.
+ *
+ * @return number of days to keep a log file
+ */
+ @ManagedAttribute("number of days that log files are kept")
+ public int getRetainDays()
+ {
+ return _retainDays;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set append to log flag.
+ *
+ * @param append true - request log file will be appended after restart,
+ * false - request log file will be overwritten after restart
+ */
+ public void setAppend(boolean append)
+ {
+ _append = append;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve append to log flag.
+ *
+ * @return value of the flag
+ */
+ @ManagedAttribute("existing log files are appends to the new one")
+ public boolean isAppend()
+ {
+ return _append;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the log file name date format.
+ * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
+ *
+ * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
+ */
+ public void setFilenameDateFormat(String logFileDateFormat)
+ {
+ _filenameDateFormat = logFileDateFormat;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the file name date format string.
+ *
+ * @return the log File Date Format
+ */
+ public String getFilenameDateFormat()
+ {
+ return _filenameDateFormat;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(String requestEntry) throws IOException
+ {
+ synchronized(this)
+ {
+ if (_writer==null)
+ return;
+ _writer.write(requestEntry);
+ _writer.write(StringUtil.__LINE_SEPARATOR);
+ _writer.flush();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set up request logging and open log file.
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected synchronized void doStart() throws Exception
+ {
+ if (_filename != null)
+ {
+ _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(getLogTimeZone()),_filenameDateFormat,null);
+ _closeOut = true;
+ LOG.info("Opened " + getDatedFilename());
+ }
+ else
+ _fileOut = System.err;
+
+ _out = _fileOut;
+
+ synchronized(this)
+ {
+ _writer = new OutputStreamWriter(_out);
+ }
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Close the log file and perform cleanup.
+ *
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ synchronized (this)
+ {
+ super.doStop();
+ try
+ {
+ if (_writer != null)
+ _writer.flush();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ if (_out != null && _closeOut)
+ try
+ {
+ _out.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+
+ _out = null;
+ _fileOut = null;
+ _closeOut = false;
+ _writer = null;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class NegotiatingServerConnection extends AbstractConnection
+{
+ private static final Logger LOG = Log.getLogger(NegotiatingServerConnection.class);
+
+ private final Connector connector;
+ private final SSLEngine engine;
+ private final List<String> protocols;
+ private final String defaultProtocol;
+ private String protocol; // No need to be volatile: it is modified and read by the same thread
+
+ protected NegotiatingServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol)
+ {
+ super(endPoint, connector.getExecutor());
+ this.connector = connector;
+ this.protocols = protocols;
+ this.defaultProtocol = defaultProtocol;
+ this.engine = engine;
+ }
+
+ protected List<String> getProtocols()
+ {
+ return protocols;
+ }
+
+ protected String getDefaultProtocol()
+ {
+ return defaultProtocol;
+ }
+
+ protected SSLEngine getSSLEngine()
+ {
+ return engine;
+ }
+
+ protected String getProtocol()
+ {
+ return protocol;
+ }
+
+ protected void setProtocol(String protocol)
+ {
+ this.protocol = protocol;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ int filled = fill();
+
+ if (filled == 0)
+ {
+ if (protocol == null)
+ {
+ if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING)
+ {
+ // Here the SSL handshake is finished, but the protocol has not been negotiated.
+ LOG.debug("{} could not negotiate protocol, SSLEngine: {}", this, engine);
+ close();
+ }
+ else
+ {
+ // Here the SSL handshake is not finished yet but we filled 0 bytes,
+ // so we need to read more.
+ fillInterested();
+ }
+ }
+ else
+ {
+ ConnectionFactory connectionFactory = connector.getConnectionFactory(protocol);
+ if (connectionFactory == null)
+ {
+ LOG.debug("{} application selected protocol '{}', but no correspondent {} has been configured",
+ this, protocol, ConnectionFactory.class.getName());
+ close();
+ }
+ else
+ {
+ EndPoint endPoint = getEndPoint();
+ Connection oldConnection = endPoint.getConnection();
+ Connection newConnection = connectionFactory.newConnection(connector, endPoint);
+ LOG.debug("{} switching from {} to {}", this, oldConnection, newConnection);
+ oldConnection.onClose();
+ endPoint.setConnection(newConnection);
+ getEndPoint().getConnection().onOpen();
+ }
+ }
+ }
+ else if (filled < 0)
+ {
+ // Something went bad, we need to close.
+ LOG.debug("{} closing on client close", this);
+ close();
+ }
+ else
+ {
+ // Must never happen, since we fill using an empty buffer
+ throw new IllegalStateException();
+ }
+ }
+
+ private int fill()
+ {
+ try
+ {
+ return getEndPoint().fill(BufferUtil.EMPTY_BUFFER);
+ }
+ catch (IOException x)
+ {
+ LOG.debug(x);
+ close();
+ return -1;
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ // Gentler close for SSL.
+ getEndPoint().shutdownOutput();
+ super.close();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+
+public abstract class NegotiatingServerConnectionFactory extends AbstractConnectionFactory
+{
+ private final List<String> protocols;
+ private String defaultProtocol;
+
+ public NegotiatingServerConnectionFactory(String protocol, String... protocols)
+ {
+ super(protocol);
+ this.protocols = Arrays.asList(protocols);
+ }
+
+ public String getDefaultProtocol()
+ {
+ return defaultProtocol;
+ }
+
+ public void setDefaultProtocol(String defaultProtocol)
+ {
+ this.defaultProtocol = defaultProtocol;
+ }
+
+ public List<String> getProtocols()
+ {
+ return protocols;
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ List<String> protocols = this.protocols;
+ if (protocols.isEmpty())
+ {
+ protocols = connector.getProtocols();
+ Iterator<String> i = protocols.iterator();
+ while (i.hasNext())
+ {
+ String protocol = i.next();
+ String prefix = "ssl-";
+ if (protocol.regionMatches(true, 0, prefix, 0, prefix.length()) || protocol.equalsIgnoreCase("alpn"))
+ {
+ i.remove();
+ }
+ }
+ }
+
+ String dft = defaultProtocol;
+ if (dft == null && !protocols.isEmpty())
+ dft = protocols.get(0);
+
+ SSLEngine engine = null;
+ EndPoint ep = endPoint;
+ while (engine == null && ep != null)
+ {
+ // TODO make more generic
+ if (ep instanceof SslConnection.DecryptedEndPoint)
+ engine = ((SslConnection.DecryptedEndPoint)ep).getSslConnection().getSSLEngine();
+ else
+ ep = null;
+ }
+
+ return configure(newServerConnection(connector, endPoint, engine, protocols, dft), connector, endPoint);
+ }
+
+ protected abstract AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List<String> protocols, String defaultProtocol);
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s,%s,%s}", getClass().getSimpleName(), hashCode(), getProtocol(), getDefaultProtocol(), getProtocols());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * <p>A {@link Connector} for TCP/IP network connectors</p>
+ */
+public interface NetworkConnector extends Connector, Closeable
+{
+ /**
+ * <p>Performs the activities needed to open the network communication
+ * (for example, to start accepting incoming network connections).</p>
+ *
+ * @throws IOException if this connector cannot be opened
+ * @see #close()
+ */
+ void open() throws IOException;
+
+ /**
+ * <p>Performs the activities needed to close the network communication
+ * (for example, to stop accepting network connections).</p>
+ * Once a connector has been closed, it cannot be opened again without first
+ * calling {@link #stop()} and it will not be active again until a subsequent call to {@link #start()}
+ */
+ @Override
+ void close();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * A Connector may be opened and not started (to reserve a port)
+ * or closed and running (to allow graceful shutdown of existing connections)
+ * @return True if the connector is Open.
+ */
+ boolean isOpen();
+
+ /**
+ * @return The hostname representing the interface to which
+ * this connector will bind, or null for all interfaces.
+ */
+ String getHost();
+
+ /**
+ * @return The configured port for the connector or 0 if any available
+ * port may be used.
+ */
+ int getPort();
+
+ /**
+ * @return The actual port the connector is listening on, or
+ * -1 if it has not been opened, or -2 if it has been closed.
+ */
+ int getLocalPort();
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.NetworkTrafficListener;
+import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * <p>A specialized version of {@link ServerConnector} that supports {@link NetworkTrafficListener}s.</p>
+ * <p>{@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has
+ * been started without causing {@link java.util.ConcurrentModificationException}s.</p>
+ */
+public class NetworkTrafficServerConnector extends ServerConnector
+{
+ private final List<NetworkTrafficListener> listeners = new CopyOnWriteArrayList<>();
+
+ public NetworkTrafficServerConnector(Server server)
+ {
+ this(server, null, null, null, 0, 0, new HttpConnectionFactory());
+ }
+
+ public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+ {
+ super(server, sslContextFactory, connectionFactory);
+ }
+
+ public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory)
+ {
+ super(server, connectionFactory);
+ }
+
+ public NetworkTrafficServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
+ {
+ super(server, executor, scheduler, pool, acceptors, selectors, factories);
+ }
+
+ public NetworkTrafficServerConnector(Server server, SslContextFactory sslContextFactory)
+ {
+ super(server, sslContextFactory);
+ }
+
+ /**
+ * @param listener the listener to add
+ */
+ public void addNetworkTrafficListener(NetworkTrafficListener listener)
+ {
+ listeners.add(listener);
+ }
+
+ /**
+ * @param listener the listener to remove
+ */
+ public void removeNetworkTrafficListener(NetworkTrafficListener listener)
+ {
+ listeners.remove(listener);
+ }
+
+ @Override
+ protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
+ {
+ NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
+ return endPoint;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * {@link QueuedHttpInput} holds a queue of items passed to it by calls to {@link #content(Object)}.
+ * <p/>
+ * {@link QueuedHttpInput} stores the items directly; if the items contain byte buffers, it does not copy them
+ * but simply holds references to the item, thus the caller must organize for those buffers to valid while
+ * held by this class.
+ * <p/>
+ * To assist the caller, subclasses may override methods {@link #onAsyncRead()}, {@link #onContentConsumed(Object)}
+ * that can be implemented so that the caller will know when buffers are queued and consumed.
+ */
+public abstract class QueuedHttpInput<T> extends HttpInput<T>
+{
+ private final static Logger LOG = Log.getLogger(QueuedHttpInput.class);
+
+ private final ArrayQueue<T> _inputQ = new ArrayQueue<>(lock());
+
+ public QueuedHttpInput()
+ {
+ }
+
+ public void content(T item)
+ {
+ // The buffer is not copied here. This relies on the caller not recycling the buffer
+ // until the it is consumed. The onContentConsumed and onAllContentConsumed() callbacks are
+ // the signals to the caller that the buffers can be recycled.
+
+ synchronized (lock())
+ {
+ boolean wasEmpty = _inputQ.isEmpty();
+ _inputQ.add(item);
+ LOG.debug("{} queued {}", this, item);
+ if (wasEmpty)
+ {
+ if (!onAsyncRead())
+ lock().notify();
+ }
+ }
+ }
+
+ public void recycle()
+ {
+ synchronized (lock())
+ {
+ T item = _inputQ.pollUnsafe();
+ while (item != null)
+ {
+ onContentConsumed(item);
+ item = _inputQ.pollUnsafe();
+ }
+ super.recycle();
+ }
+ }
+
+ @Override
+ protected T nextContent()
+ {
+ synchronized (lock())
+ {
+ // Items are removed only when they are fully consumed.
+ T item = _inputQ.peekUnsafe();
+ // Skip consumed items at the head of the queue.
+ while (item != null && remaining(item) == 0)
+ {
+ _inputQ.pollUnsafe();
+ onContentConsumed(item);
+ LOG.debug("{} consumed {}", this, item);
+ item = _inputQ.peekUnsafe();
+ }
+ return item;
+ }
+ }
+
+ protected void blockForContent() throws IOException
+ {
+ synchronized (lock())
+ {
+ while (_inputQ.isEmpty() && !isFinished() && !isEOF())
+ {
+ try
+ {
+ LOG.debug("{} waiting for content", this);
+ lock().wait();
+ }
+ catch (InterruptedException e)
+ {
+ throw (IOException)new InterruptedIOException().initCause(e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback that signals that the given content has been consumed.
+ *
+ * @param item the consumed content
+ */
+ protected abstract void onContentConsumed(T item);
+
+ public void earlyEOF()
+ {
+ synchronized (lock())
+ {
+ super.earlyEOF();
+ lock().notify();
+ }
+ }
+
+ public void messageComplete()
+ {
+ synchronized (lock())
+ {
+ super.messageComplete();
+ lock().notify();
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import javax.servlet.ServletException;
+
+
+/* ------------------------------------------------------------ */
+/** A ServletException that is logged less verbosely than
+ * a normal ServletException.
+ * <p>
+ * Used for container generated exceptions that need only a message rather
+ * than a stack trace.
+ * </p>
+ */
+public class QuietServletException extends ServletException
+{
+ public QuietServletException()
+ {
+ super();
+ }
+
+ public QuietServletException(String message, Throwable rootCause)
+ {
+ super(message,rootCause);
+ }
+
+ public QuietServletException(String message)
+ {
+ super(message);
+ }
+
+ public QuietServletException(Throwable rootCause)
+ {
+ super(rootCause);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.MultiPartInputStreamParser;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Jetty Request.
+ * <p>
+ * Implements {@link javax.servlet.http.HttpServletRequest} from the <code>javax.servlet.http</code> package.
+ * </p>
+ * <p>
+ * The standard interface of mostly getters, is extended with setters so that the request is mutable by the handlers that it is passed to. This allows the
+ * request object to be as lightweight as possible and not actually implement any significant behavior. For example
+ * <ul>
+ *
+ * <li>The {@link Request#getContextPath()} method will return null, until the request has been passed to a {@link ContextHandler} which matches the
+ * {@link Request#getPathInfo()} with a context path and calls {@link Request#setContextPath(String)} as a result.</li>
+ *
+ * <li>the HTTP session methods will all return null sessions until such time as a request has been passed to a
+ * {@link org.eclipse.jetty.server.session.SessionHandler} which checks for session cookies and enables the ability to create new sessions.</li>
+ *
+ * <li>The {@link Request#getServletPath()} method will return null until the request has been passed to a <code>org.eclipse.jetty.servlet.ServletHandler</code>
+ * and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result.</li>
+ * </ul>
+ *
+ * A request instance is created for each connection accepted by the server and recycled for each HTTP request received via that connection.
+ * An effort is made to avoid reparsing headers and cookies that are likely to be the same for requests from the same connection.
+ *
+ * <p>
+ * The form content that a request can process is limited to protect from Denial of Service attacks. The size in bytes is limited by
+ * {@link ContextHandler#getMaxFormContentSize()} or if there is no context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server}
+ * attribute. The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no context then the
+ * "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
+ *
+ *
+ */
+public class Request implements HttpServletRequest
+{
+ public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
+ public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.jetty.multiPartInputStream";
+ public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext";
+
+ private static final Logger LOG = Log.getLogger(Request.class);
+ private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
+ private static final int __NONE = 0, _STREAM = 1, __READER = 2;
+
+ private final HttpChannel<?> _channel;
+ private final HttpFields _fields=new HttpFields();
+ private final List<ServletRequestAttributeListener> _requestAttributeListeners=new ArrayList<>();
+ private final HttpInput<?> _input;
+
+ public static class MultiPartCleanerListener implements ServletRequestListener
+ {
+ @Override
+ public void requestDestroyed(ServletRequestEvent sre)
+ {
+ //Clean up any tmp files created by MultiPartInputStream
+ MultiPartInputStreamParser mpis = (MultiPartInputStreamParser)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
+ if (mpis != null)
+ {
+ ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT);
+
+ //Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files
+ if (context == sre.getServletContext())
+ {
+ try
+ {
+ mpis.deleteParts();
+ }
+ catch (MultiException e)
+ {
+ sre.getServletContext().log("Errors deleting multipart tmp files", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void requestInitialized(ServletRequestEvent sre)
+ {
+ //nothing to do, multipart config set up by ServletHolder.handle()
+ }
+
+ }
+
+
+
+ private boolean _secure;
+ private boolean _asyncSupported = true;
+ private boolean _newContext;
+ private boolean _cookiesExtracted = false;
+ private boolean _handled = false;
+ private boolean _paramsExtracted;
+ private boolean _requestedSessionIdFromCookie = false;
+ private volatile Attributes _attributes;
+ private Authentication _authentication;
+ private String _characterEncoding;
+ private ContextHandler.Context _context;
+ private String _contextPath;
+ private CookieCutter _cookies;
+ private DispatcherType _dispatcherType;
+ private int _inputState = __NONE;
+ private HttpMethod _httpMethod;
+ private String _httpMethodString;
+ private MultiMap<String> _queryParameters;
+ private MultiMap<String> _contentParameters;
+ private MultiMap<String> _parameters;
+ private String _pathInfo;
+ private int _port;
+ private HttpVersion _httpVersion = HttpVersion.HTTP_1_1;
+ private String _queryEncoding;
+ private String _queryString;
+ private BufferedReader _reader;
+ private String _readerEncoding;
+ private InetSocketAddress _remote;
+ private String _requestedSessionId;
+ private String _requestURI;
+ private Map<Object, HttpSession> _savedNewSessions;
+ private String _scheme = URIUtil.HTTP;
+ private UserIdentity.Scope _scope;
+ private String _serverName;
+ private String _servletPath;
+ private HttpSession _session;
+ private SessionManager _sessionManager;
+ private long _timeStamp;
+ private HttpURI _uri;
+ private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
+ private AsyncContextState _async;
+
+ /* ------------------------------------------------------------ */
+ public Request(HttpChannel<?> channel, HttpInput<?> input)
+ {
+ _channel = channel;
+ _input = input;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpFields getHttpFields()
+ {
+ return _fields;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpInput<?> getHttpInput()
+ {
+ return _input;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addEventListener(final EventListener listener)
+ {
+ if (listener instanceof ServletRequestAttributeListener)
+ _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
+ if (listener instanceof AsyncListener)
+ throw new IllegalArgumentException(listener.getClass().toString());
+ }
+
+ public void extractParameters()
+ {
+ if (_paramsExtracted)
+ return;
+
+ _paramsExtracted = true;
+
+ // Extract query string parameters; these may be replaced by a forward()
+ // and may have already been extracted by mergeQueryParameters().
+ if (_queryParameters == null)
+ _queryParameters = extractQueryParameters();
+
+ // Extract content parameters; these cannot be replaced by a forward()
+ // once extracted and may have already been extracted by getParts() or
+ // by a processing happening after a form-based authentication.
+ if (_contentParameters == null)
+ _contentParameters = extractContentParameters();
+
+ _parameters = restoreParameters();
+ }
+
+ private MultiMap<String> extractQueryParameters()
+ {
+ MultiMap<String> result = new MultiMap<>();
+ if (_uri != null && _uri.hasQuery())
+ {
+ if (_queryEncoding == null)
+ {
+ _uri.decodeQueryTo(result);
+ }
+ else
+ {
+ try
+ {
+ _uri.decodeQueryTo(result, _queryEncoding);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ else
+ LOG.warn(e.toString());
+ }
+ }
+ }
+ return result;
+ }
+
+ private MultiMap<String> extractContentParameters()
+ {
+ MultiMap<String> result = new MultiMap<>();
+
+ String contentType = getContentType();
+ if (contentType != null && !contentType.isEmpty())
+ {
+ contentType = HttpFields.valueParameters(contentType, null);
+ int contentLength = getContentLength();
+ if (contentLength != 0)
+ {
+ if (MimeTypes.Type.FORM_ENCODED.is(contentType) && _inputState == __NONE &&
+ (HttpMethod.POST.is(getMethod()) || HttpMethod.PUT.is(getMethod())))
+ {
+ extractFormParameters(result);
+ }
+ else if (contentType.startsWith("multipart/form-data") &&
+ getAttribute(__MULTIPART_CONFIG_ELEMENT) != null &&
+ _multiPartInputStream == null)
+ {
+ extractMultipartParameters(result);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void extractFormParameters(MultiMap<String> params)
+ {
+ try
+ {
+ int maxFormContentSize = -1;
+ int maxFormKeys = -1;
+
+ if (_context != null)
+ {
+ maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
+ maxFormKeys = _context.getContextHandler().getMaxFormKeys();
+ }
+
+ if (maxFormContentSize < 0)
+ {
+ Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
+ if (obj == null)
+ maxFormContentSize = 200000;
+ else if (obj instanceof Number)
+ {
+ Number size = (Number)obj;
+ maxFormContentSize = size.intValue();
+ }
+ else if (obj instanceof String)
+ {
+ maxFormContentSize = Integer.valueOf((String)obj);
+ }
+ }
+
+ if (maxFormKeys < 0)
+ {
+ Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
+ if (obj == null)
+ maxFormKeys = 1000;
+ else if (obj instanceof Number)
+ {
+ Number keys = (Number)obj;
+ maxFormKeys = keys.intValue();
+ }
+ else if (obj instanceof String)
+ {
+ maxFormKeys = Integer.valueOf((String)obj);
+ }
+ }
+
+ int contentLength = getContentLength();
+ if (contentLength > maxFormContentSize && maxFormContentSize > 0)
+ {
+ throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize);
+ }
+ InputStream in = getInputStream();
+ if (_input.isAsync())
+ throw new IllegalStateException("Cannot extract parameters with async IO");
+
+ UrlEncoded.decodeTo(in,params,getCharacterEncoding(),contentLength<0?maxFormContentSize:-1,maxFormKeys);
+ }
+ catch (IOException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ else
+ LOG.warn(e.toString());
+ }
+ }
+
+ private void extractMultipartParameters(MultiMap<String> result)
+ {
+ try
+ {
+ getParts(result);
+ }
+ catch (IOException | ServletException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ else
+ LOG.warn(e.toString());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public AsyncContext getAsyncContext()
+ {
+ HttpChannelState state = getHttpChannelState();
+ if (_async==null || state.isInitial() && !state.isAsync())
+ throw new IllegalStateException(state.getStatusString());
+
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpChannelState getHttpChannelState()
+ {
+ return _channel.getState();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get Request Attribute.
+ * <p>Also supports jetty specific attributes to gain access to Jetty APIs:
+ * <dl>
+ * <dt>org.eclipse.jetty.server.Server</dt><dd>The Jetty Server instance</dd>
+ * <dt>org.eclipse.jetty.server.HttpChannel</dt><dd>The HttpChannel for this request</dd>
+ * <dt>org.eclipse.jetty.server.HttpConnection</dt><dd>The HttpConnection or null if another transport is used</dd>
+ * </dl>
+ * While these attributes may look like security problems, they are exposing nothing that is not already
+ * available via reflection from a Request instance.
+ * </p>
+ * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
+ */
+ @Override
+ public Object getAttribute(String name)
+ {
+ if (name.startsWith("org.eclipse.jetty"))
+ {
+ if ("org.eclipse.jetty.server.Server".equals(name))
+ return _channel.getServer();
+ if ("org.eclipse.jetty.server.HttpChannel".equals(name))
+ return _channel;
+ if ("org.eclipse.jetty.server.HttpConnection".equals(name) &&
+ _channel.getHttpTransport() instanceof HttpConnection)
+ return _channel.getHttpTransport();
+ }
+ return (_attributes == null)?null:_attributes.getAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getAttributeNames()
+ */
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ if (_attributes == null)
+ return Collections.enumeration(Collections.<String>emptyList());
+
+ return AttributesMap.getAttributeNamesCopy(_attributes);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Attributes getAttributes()
+ {
+ if (_attributes == null)
+ _attributes = new AttributesMap();
+ return _attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the authentication.
+ *
+ * @return the authentication
+ */
+ public Authentication getAuthentication()
+ {
+ return _authentication;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getAuthType()
+ */
+ @Override
+ public String getAuthType()
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+ if (_authentication instanceof Authentication.User)
+ return ((Authentication.User)_authentication).getAuthMethod();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getCharacterEncoding()
+ */
+ @Override
+ public String getCharacterEncoding()
+ {
+ return _characterEncoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connection.
+ */
+ public HttpChannel<?> getHttpChannel()
+ {
+ return _channel;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getContentLength()
+ */
+ @Override
+ public int getContentLength()
+ {
+ return (int)_fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest.getContentLengthLong()
+ */
+ @Override
+ public long getContentLengthLong()
+ {
+ return _fields.getLongField(HttpHeader.CONTENT_LENGTH.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentRead()
+ {
+ return _input.getContentRead();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getContentType()
+ */
+ @Override
+ public String getContentType()
+ {
+ return _fields.getStringField(HttpHeader.CONTENT_TYPE);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The current {@link Context context} used for this request, or <code>null</code> if {@link #setContext} has not yet been called.
+ */
+ public Context getContext()
+ {
+ return _context;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getContextPath()
+ */
+ @Override
+ public String getContextPath()
+ {
+ return _contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getCookies()
+ */
+ @Override
+ public Cookie[] getCookies()
+ {
+ if (_cookiesExtracted)
+ {
+ if (_cookies == null || _cookies.getCookies().length == 0)
+ return null;
+
+ return _cookies.getCookies();
+ }
+
+ _cookiesExtracted = true;
+
+ Enumeration<?> enm = _fields.getValues(HttpHeader.COOKIE.toString());
+
+ // Handle no cookies
+ if (enm != null)
+ {
+ if (_cookies == null)
+ _cookies = new CookieCutter();
+
+ while (enm.hasMoreElements())
+ {
+ String c = (String)enm.nextElement();
+ _cookies.addCookieField(c);
+ }
+ }
+
+ //Javadoc for Request.getCookies() stipulates null for no cookies
+ if (_cookies == null || _cookies.getCookies().length == 0)
+ return null;
+
+ return _cookies.getCookies();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String)
+ */
+ @Override
+ public long getDateHeader(String name)
+ {
+ return _fields.getDateField(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public DispatcherType getDispatcherType()
+ {
+ return _dispatcherType;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
+ */
+ @Override
+ public String getHeader(String name)
+ {
+ return _fields.getStringField(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
+ */
+ @Override
+ public Enumeration<String> getHeaderNames()
+ {
+ return _fields.getFieldNames();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
+ */
+ @Override
+ public Enumeration<String> getHeaders(String name)
+ {
+ Enumeration<String> e = _fields.getValues(name);
+ if (e == null)
+ return Collections.enumeration(Collections.<String>emptyList());
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the inputState.
+ */
+ public int getInputState()
+ {
+ return _inputState;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getInputStream()
+ */
+ @Override
+ public ServletInputStream getInputStream() throws IOException
+ {
+ if (_inputState != __NONE && _inputState != _STREAM)
+ throw new IllegalStateException("READER");
+ _inputState = _STREAM;
+
+ if (_channel.isExpecting100Continue())
+ _channel.continue100(_input.available());
+
+ return _input;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String)
+ */
+ @Override
+ public int getIntHeader(String name)
+ {
+ return (int)_fields.getLongField(name);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocale()
+ */
+ @Override
+ public Locale getLocale()
+ {
+ Enumeration<String> enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
+
+ // handle no locale
+ if (enm == null || !enm.hasMoreElements())
+ return Locale.getDefault();
+
+ // sort the list in quality order
+ List<?> acceptLanguage = HttpFields.qualityList(enm);
+ if (acceptLanguage.size() == 0)
+ return Locale.getDefault();
+
+ int size = acceptLanguage.size();
+
+ if (size > 0)
+ {
+ String language = (String)acceptLanguage.get(0);
+ language = HttpFields.valueParameters(language,null);
+ String country = "";
+ int dash = language.indexOf('-');
+ if (dash > -1)
+ {
+ country = language.substring(dash + 1).trim();
+ language = language.substring(0,dash).trim();
+ }
+ return new Locale(language,country);
+ }
+
+ return Locale.getDefault();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocales()
+ */
+ @Override
+ public Enumeration<Locale> getLocales()
+ {
+
+ Enumeration<String> enm = _fields.getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
+
+ // handle no locale
+ if (enm == null || !enm.hasMoreElements())
+ return Collections.enumeration(__defaultLocale);
+
+ // sort the list in quality order
+ List<String> acceptLanguage = HttpFields.qualityList(enm);
+
+ if (acceptLanguage.size() == 0)
+ return Collections.enumeration(__defaultLocale);
+
+ List<Locale> langs = new ArrayList<>();
+
+ // convert to locals
+ for (String language : acceptLanguage)
+ {
+ language = HttpFields.valueParameters(language, null);
+ String country = "";
+ int dash = language.indexOf('-');
+ if (dash > -1)
+ {
+ country = language.substring(dash + 1).trim();
+ language = language.substring(0, dash).trim();
+ }
+ langs.add(new Locale(language, country));
+ }
+
+ if (langs.size() == 0)
+ return Collections.enumeration(__defaultLocale);
+
+ return Collections.enumeration(langs);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocalAddr()
+ */
+ @Override
+ public String getLocalAddr()
+ {
+ InetSocketAddress local=_channel.getLocalAddress();
+ if (local==null)
+ return "";
+ InetAddress address = local.getAddress();
+ if (address==null)
+ return local.getHostString();
+ return address.getHostAddress();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocalName()
+ */
+ @Override
+ public String getLocalName()
+ {
+ InetSocketAddress local=_channel.getLocalAddress();
+ return local.getHostString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getLocalPort()
+ */
+ @Override
+ public int getLocalPort()
+ {
+ InetSocketAddress local=_channel.getLocalAddress();
+ return local.getPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getMethod()
+ */
+ @Override
+ public String getMethod()
+ {
+ return _httpMethodString;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
+ */
+ @Override
+ public String getParameter(String name)
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ if (_parameters == null)
+ _parameters = restoreParameters();
+ return _parameters.getValue(name,0);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameterMap()
+ */
+ @Override
+ public Map<String, String[]> getParameterMap()
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ if (_parameters == null)
+ _parameters = restoreParameters();
+ return Collections.unmodifiableMap(_parameters.toStringArrayMap());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameterNames()
+ */
+ @Override
+ public Enumeration<String> getParameterNames()
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ if (_parameters == null)
+ _parameters = restoreParameters();
+ return Collections.enumeration(_parameters.keySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
+ */
+ @Override
+ public String[] getParameterValues(String name)
+ {
+ if (!_paramsExtracted)
+ extractParameters();
+ if (_parameters == null)
+ _parameters = restoreParameters();
+ List<String> vals = _parameters.getValues(name);
+ if (vals == null)
+ return null;
+ return vals.toArray(new String[vals.size()]);
+ }
+
+ private MultiMap<String> restoreParameters()
+ {
+ MultiMap<String> result = new MultiMap<>();
+ if (_queryParameters == null)
+ _queryParameters = extractQueryParameters();
+ result.addAllValues(_queryParameters);
+ result.addAllValues(_contentParameters);
+ return result;
+ }
+
+ public MultiMap<String> getQueryParameters()
+ {
+ return _queryParameters;
+ }
+
+ public void setQueryParameters(MultiMap<String> queryParameters)
+ {
+ _queryParameters = queryParameters;
+ }
+
+ public void setContentParameters(MultiMap<String> contentParameters)
+ {
+ _contentParameters = contentParameters;
+ }
+
+ public void resetParameters()
+ {
+ _parameters = null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getPathInfo()
+ */
+ @Override
+ public String getPathInfo()
+ {
+ return _pathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
+ */
+ @Override
+ public String getPathTranslated()
+ {
+ if (_pathInfo == null || _context == null)
+ return null;
+ return _context.getRealPath(_pathInfo);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getProtocol()
+ */
+ @Override
+ public String getProtocol()
+ {
+ return _httpVersion.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getProtocol()
+ */
+ public HttpVersion getHttpVersion()
+ {
+ return _httpVersion;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getQueryEncoding()
+ {
+ return _queryEncoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getQueryString()
+ */
+ @Override
+ public String getQueryString()
+ {
+ if (_queryString == null && _uri != null)
+ {
+ if (_queryEncoding == null)
+ _queryString = _uri.getQuery();
+ else
+ _queryString = _uri.getQuery(_queryEncoding);
+ }
+ return _queryString;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getReader()
+ */
+ @Override
+ public BufferedReader getReader() throws IOException
+ {
+ if (_inputState != __NONE && _inputState != __READER)
+ throw new IllegalStateException("STREAMED");
+
+ if (_inputState == __READER)
+ return _reader;
+
+ String encoding = getCharacterEncoding();
+ if (encoding == null)
+ encoding = StringUtil.__ISO_8859_1;
+
+ if (_reader == null || !encoding.equalsIgnoreCase(_readerEncoding))
+ {
+ final ServletInputStream in = getInputStream();
+ _readerEncoding = encoding;
+ _reader = new BufferedReader(new InputStreamReader(in,encoding))
+ {
+ @Override
+ public void close() throws IOException
+ {
+ in.close();
+ }
+ };
+ }
+ _inputState = __READER;
+ return _reader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
+ */
+ @Override
+ public String getRealPath(String path)
+ {
+ if (_context == null)
+ return null;
+ return _context.getRealPath(path);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Access the underlying Remote {@link InetSocketAddress} for this request.
+ *
+ * @return the remote {@link InetSocketAddress} for this request, or null if the request has no remote (see {@link ServletRequest#getRemoteAddr()} for
+ * conditions that result in no remote address)
+ */
+ public InetSocketAddress getRemoteInetSocketAddress()
+ {
+ InetSocketAddress remote = _remote;
+ if (remote == null)
+ remote = _channel.getRemoteAddress();
+
+ return remote;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRemoteAddr()
+ */
+ @Override
+ public String getRemoteAddr()
+ {
+ InetSocketAddress remote=_remote;
+ if (remote==null)
+ remote=_channel.getRemoteAddress();
+
+ if (remote==null)
+ return "";
+
+ InetAddress address = remote.getAddress();
+ if (address==null)
+ return remote.getHostString();
+
+ return address.getHostAddress();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRemoteHost()
+ */
+ @Override
+ public String getRemoteHost()
+ {
+ InetSocketAddress remote=_remote;
+ if (remote==null)
+ remote=_channel.getRemoteAddress();
+ return remote==null?"":remote.getHostString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRemotePort()
+ */
+ @Override
+ public int getRemotePort()
+ {
+ InetSocketAddress remote=_remote;
+ if (remote==null)
+ remote=_channel.getRemoteAddress();
+ return remote==null?0:remote.getPort();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
+ */
+ @Override
+ public String getRemoteUser()
+ {
+ Principal p = getUserPrincipal();
+ if (p == null)
+ return null;
+ return p.getName();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
+ */
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path)
+ {
+ if (path == null || _context == null)
+ return null;
+
+ // handle relative path
+ if (!path.startsWith("/"))
+ {
+ String relTo = URIUtil.addPaths(_servletPath,_pathInfo);
+ int slash = relTo.lastIndexOf("/");
+ if (slash > 1)
+ relTo = relTo.substring(0,slash + 1);
+ else
+ relTo = "/";
+ path = URIUtil.addPaths(relTo,path);
+ }
+
+ return _context.getRequestDispatcher(path);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
+ */
+ @Override
+ public String getRequestedSessionId()
+ {
+ return _requestedSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRequestURI()
+ */
+ @Override
+ public String getRequestURI()
+ {
+ if (_requestURI == null && _uri != null)
+ _requestURI = _uri.getPathAndParam();
+ return _requestURI;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getRequestURL()
+ */
+ @Override
+ public StringBuffer getRequestURL()
+ {
+ final StringBuffer url = new StringBuffer(128);
+ URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
+ url.append(getRequestURI());
+ return url;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Response getResponse()
+ {
+ return _channel.getResponse();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and, but it does not include a
+ * path.
+ * <p>
+ * Because this method returns a <code>StringBuffer</code>, not a string, you can modify the URL easily, for example, to append path and query parameters.
+ *
+ * This method is useful for creating redirect messages and for reporting errors.
+ *
+ * @return "scheme://host:port"
+ */
+ public StringBuilder getRootURL()
+ {
+ StringBuilder url = new StringBuilder(128);
+ URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
+ return url;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getScheme()
+ */
+ @Override
+ public String getScheme()
+ {
+ return _scheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getServerName()
+ */
+ @Override
+ public String getServerName()
+ {
+ // Return already determined host
+ if (_serverName != null)
+ return _serverName;
+
+ if (_uri == null)
+ throw new IllegalStateException("No uri");
+
+ // Return host from absolute URI
+ _serverName = _uri.getHost();
+ if (_serverName != null)
+ {
+ _port = _uri.getPort();
+ return _serverName;
+ }
+
+ // Return host from header field
+ String hostPort = _fields.getStringField(HttpHeader.HOST);
+
+ _port=0;
+ if (hostPort != null)
+ {
+ int len=hostPort.length();
+ loop: for (int i = len; i-- > 0;)
+ {
+ char c2 = (char)(0xff & hostPort.charAt(i));
+ switch (c2)
+ {
+ case ']':
+ break loop;
+
+ case ':':
+ try
+ {
+ len=i;
+ _port = StringUtil.toInt(hostPort.substring(i+1));
+ }
+ catch (NumberFormatException e)
+ {
+ LOG.warn(e);
+ _serverName=hostPort;
+ _port=0;
+ return _serverName;
+ }
+ break loop;
+ }
+ }
+ if (hostPort.charAt(0)=='[')
+ {
+ if (hostPort.charAt(len-1)!=']')
+ {
+ LOG.warn("Bad IPv6 "+hostPort);
+ _serverName=hostPort;
+ _port=0;
+ return _serverName;
+ }
+ _serverName = hostPort.substring(1,len-1);
+ }
+ else if (len==hostPort.length())
+ _serverName=hostPort;
+ else
+ _serverName = hostPort.substring(0,len);
+
+ return _serverName;
+ }
+
+ // Return host from connection
+ if (_channel != null)
+ {
+ _serverName = getLocalName();
+ _port = getLocalPort();
+ if (_serverName != null && !StringUtil.ALL_INTERFACES.equals(_serverName))
+ return _serverName;
+ }
+
+ // Return the local host
+ try
+ {
+ _serverName = InetAddress.getLocalHost().getHostAddress();
+ }
+ catch (java.net.UnknownHostException e)
+ {
+ LOG.ignore(e);
+ }
+ return _serverName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getServerPort()
+ */
+ @Override
+ public int getServerPort()
+ {
+ if (_port <= 0)
+ {
+ if (_serverName == null)
+ getServerName();
+
+ if (_port <= 0)
+ {
+ if (_serverName != null && _uri != null)
+ _port = _uri.getPort();
+ else
+ {
+ InetSocketAddress local = _channel.getLocalAddress();
+ _port = local == null?0:local.getPort();
+ }
+ }
+ }
+
+ if (_port <= 0)
+ {
+ if (getScheme().equalsIgnoreCase(URIUtil.HTTPS))
+ return 443;
+ return 80;
+ }
+ return _port;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ServletContext getServletContext()
+ {
+ return _context;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public String getServletName()
+ {
+ if (_scope != null)
+ return _scope.getName();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getServletPath()
+ */
+ @Override
+ public String getServletPath()
+ {
+ if (_servletPath == null)
+ _servletPath = "";
+ return _servletPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletResponse getServletResponse()
+ {
+ return _channel.getResponse();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Add @override when 3.1 api is available
+ */
+ public String changeSessionId()
+ {
+ HttpSession session = getSession(false);
+ if (session == null)
+ throw new IllegalStateException("No session");
+
+ if (session instanceof AbstractSession)
+ {
+ AbstractSession abstractSession = ((AbstractSession)session);
+ abstractSession.renewId(this);
+ if (getRemoteUser() != null)
+ abstractSession.setAttribute(AbstractSession.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+ if (abstractSession.isIdChanged())
+ _channel.getResponse().addCookie(_sessionManager.getSessionCookie(abstractSession, getContextPath(), isSecure()));
+ }
+
+ return session.getId();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getSession()
+ */
+ @Override
+ public HttpSession getSession()
+ {
+ return getSession(true);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
+ */
+ @Override
+ public HttpSession getSession(boolean create)
+ {
+ if (_session != null)
+ {
+ if (_sessionManager != null && !_sessionManager.isValid(_session))
+ _session = null;
+ else
+ return _session;
+ }
+
+ if (!create)
+ return null;
+
+ if (getResponse().isCommitted())
+ throw new IllegalStateException("Response is committed");
+
+ if (_sessionManager == null)
+ throw new IllegalStateException("No SessionManager");
+
+ _session = _sessionManager.newHttpSession(this);
+ HttpCookie cookie = _sessionManager.getSessionCookie(_session,getContextPath(),isSecure());
+ if (cookie != null)
+ _channel.getResponse().addCookie(cookie);
+
+ return _session;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionManager.
+ */
+ public SessionManager getSessionManager()
+ {
+ return _sessionManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get Request TimeStamp
+ *
+ * @return The time that the request was received.
+ */
+ public long getTimeStamp()
+ {
+ return _timeStamp;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the uri.
+ */
+ public HttpURI getUri()
+ {
+ return _uri;
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity getUserIdentity()
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+ if (_authentication instanceof Authentication.User)
+ return ((Authentication.User)_authentication).getUserIdentity();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The resolved user Identity, which may be null if the {@link Authentication} is not {@link Authentication.User} (eg.
+ * {@link Authentication.Deferred}).
+ */
+ public UserIdentity getResolvedUserIdentity()
+ {
+ if (_authentication instanceof Authentication.User)
+ return ((Authentication.User)_authentication).getUserIdentity();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public UserIdentity.Scope getUserIdentityScope()
+ {
+ return _scope;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
+ */
+ @Override
+ public Principal getUserPrincipal()
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+ if (_authentication instanceof Authentication.User)
+ {
+ UserIdentity user = ((Authentication.User)_authentication).getUserIdentity();
+ return user.getUserPrincipal();
+ }
+
+ return null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean isHandled()
+ {
+ return _handled;
+ }
+
+ @Override
+ public boolean isAsyncStarted()
+ {
+ return getHttpChannelState().isAsyncStarted();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isAsyncSupported()
+ {
+ return _asyncSupported;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie()
+ */
+ @Override
+ public boolean isRequestedSessionIdFromCookie()
+ {
+ return _requestedSessionId != null && _requestedSessionIdFromCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl()
+ */
+ @Override
+ public boolean isRequestedSessionIdFromUrl()
+ {
+ return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL()
+ */
+ @Override
+ public boolean isRequestedSessionIdFromURL()
+ {
+ return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
+ */
+ @Override
+ public boolean isRequestedSessionIdValid()
+ {
+ if (_requestedSessionId == null)
+ return false;
+
+ HttpSession session = getSession(false);
+ return (session != null && _sessionManager.getSessionIdManager().getClusterId(_requestedSessionId).equals(_sessionManager.getClusterId(session)));
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#isSecure()
+ */
+ @Override
+ public boolean isSecure()
+ {
+ return _secure;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSecure(boolean secure)
+ {
+ _secure=secure;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
+ */
+ @Override
+ public boolean isUserInRole(String role)
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+ if (_authentication instanceof Authentication.User)
+ return ((Authentication.User)_authentication).isUserInRole(_scope,role);
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpSession recoverNewSession(Object key)
+ {
+ if (_savedNewSessions == null)
+ return null;
+ return _savedNewSessions.get(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void recycle()
+ {
+ if (_context != null)
+ throw new IllegalStateException("Request in context!");
+
+ if (_inputState == __READER)
+ {
+ try
+ {
+ int r = _reader.read();
+ while (r != -1)
+ r = _reader.read();
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ _reader = null;
+ }
+ }
+
+ _dispatcherType=null;
+ setAuthentication(Authentication.NOT_CHECKED);
+ getHttpChannelState().recycle();
+ if (_async!=null)
+ _async.reset();
+ _async=null;
+ _asyncSupported = true;
+ _handled = false;
+ if (_attributes != null)
+ _attributes.clearAttributes();
+ _characterEncoding = null;
+ _contextPath = null;
+ if (_cookies != null)
+ _cookies.reset();
+ _cookiesExtracted = false;
+ _context = null;
+ _newContext=false;
+ _serverName = null;
+ _httpMethod=null;
+ _httpMethodString = null;
+ _pathInfo = null;
+ _port = 0;
+ _httpVersion = HttpVersion.HTTP_1_1;
+ _queryEncoding = null;
+ _queryString = null;
+ _requestedSessionId = null;
+ _requestedSessionIdFromCookie = false;
+ _secure=false;
+ _session = null;
+ _sessionManager = null;
+ _requestURI = null;
+ _scope = null;
+ _scheme = URIUtil.HTTP;
+ _servletPath = null;
+ _timeStamp = 0;
+ _uri = null;
+ _queryParameters = null;
+ _contentParameters = null;
+ _parameters = null;
+ _paramsExtracted = false;
+ _inputState = __NONE;
+
+ if (_savedNewSessions != null)
+ _savedNewSessions.clear();
+ _savedNewSessions=null;
+ _multiPartInputStream = null;
+ _remote=null;
+ _fields.clear();
+ _input.recycle();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
+ */
+ @Override
+ public void removeAttribute(String name)
+ {
+ Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+ if (_attributes != null)
+ _attributes.removeAttribute(name);
+
+ if (old_value != null && !_requestAttributeListeners.isEmpty())
+ {
+ final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value);
+ for (ServletRequestAttributeListener listener : _requestAttributeListeners)
+ listener.attributeRemoved(event);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeEventListener(final EventListener listener)
+ {
+ _requestAttributeListeners.remove(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void saveNewSession(Object key, HttpSession session)
+ {
+ if (_savedNewSessions == null)
+ _savedNewSessions = new HashMap<>();
+ _savedNewSessions.put(key,session);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAsyncSupported(boolean supported)
+ {
+ _asyncSupported = supported;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to
+ * {@link #setQueryEncoding}.
+ *
+ * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public void setAttribute(String name, Object value)
+ {
+ Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+ if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
+ setQueryEncoding(value == null?null:value.toString());
+ else if ("org.eclipse.jetty.server.sendContent".equals(name))
+ LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
+
+ if (_attributes == null)
+ _attributes = new AttributesMap();
+ _attributes.setAttribute(name,value);
+
+ if (!_requestAttributeListeners.isEmpty())
+ {
+ final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value == null?value:old_value);
+ for (ServletRequestAttributeListener l : _requestAttributeListeners)
+ {
+ if (old_value == null)
+ l.attributeAdded(event);
+ else if (value == null)
+ l.attributeRemoved(event);
+ else
+ l.attributeReplaced(event);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public void setAttributes(Attributes attributes)
+ {
+ _attributes = attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the authentication.
+ *
+ * @param authentication
+ * the authentication to set
+ */
+ public void setAuthentication(Authentication authentication)
+ {
+ _authentication = authentication;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+ */
+ @Override
+ public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException
+ {
+ if (_inputState != __NONE)
+ return;
+
+ _characterEncoding = encoding;
+
+ // check encoding is supported
+ if (!StringUtil.isUTF8(encoding))
+ {
+ try
+ {
+ Charset.forName(encoding);
+ }
+ catch (UnsupportedCharsetException e)
+ {
+ throw new UnsupportedEncodingException(e.getMessage());
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+ */
+ public void setCharacterEncodingUnchecked(String encoding)
+ {
+ _characterEncoding = encoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletRequest#getContentType()
+ */
+ public void setContentType(String contentType)
+ {
+ _fields.put(HttpHeader.CONTENT_TYPE,contentType);
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set request context
+ *
+ * @param context
+ * context object
+ */
+ public void setContext(Context context)
+ {
+ _newContext = _context != context;
+ _context = context;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if this is the first call of {@link #takeNewContext()} since the last
+ * {@link #setContext(org.eclipse.jetty.server.handler.ContextHandler.Context)} call.
+ */
+ public boolean takeNewContext()
+ {
+ boolean nc = _newContext;
+ _newContext = false;
+ return nc;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the "context path" for this request
+ *
+ * @see HttpServletRequest#getContextPath()
+ */
+ public void setContextPath(String contextPath)
+ {
+ _contextPath = contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param cookies
+ * The cookies to set.
+ */
+ public void setCookies(Cookie[] cookies)
+ {
+ if (_cookies == null)
+ _cookies = new CookieCutter();
+ _cookies.setCookies(cookies);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setDispatcherType(DispatcherType type)
+ {
+ _dispatcherType = type;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setHandled(boolean h)
+ {
+ _handled = h;
+ Response r=getResponse();
+ if (_handled && r.getStatus()==0)
+ r.setStatus(200);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param method
+ * The method to set.
+ */
+ public void setMethod(HttpMethod httpMethod, String method)
+ {
+ _httpMethod=httpMethod;
+ _httpMethodString = method;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isHead()
+ {
+ return HttpMethod.HEAD==_httpMethod;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathInfo
+ * The pathInfo to set.
+ */
+ public void setPathInfo(String pathInfo)
+ {
+ _pathInfo = pathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param version
+ * The protocol to set.
+ */
+ public void setHttpVersion(HttpVersion version)
+ {
+ _httpVersion = version;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the character encoding used for the query string. This call will effect the return of getQueryString and getParamaters. It must be called before any
+ * getParameter methods.
+ *
+ * The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
+ *
+ * @param queryEncoding
+ */
+ public void setQueryEncoding(String queryEncoding)
+ {
+ _queryEncoding = queryEncoding;
+ _queryString = null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param queryString
+ * The queryString to set.
+ */
+ public void setQueryString(String queryString)
+ {
+ _queryString = queryString;
+ _queryEncoding = null; //assume utf-8
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param addr
+ * The address to set.
+ */
+ public void setRemoteAddr(InetSocketAddress addr)
+ {
+ _remote = addr;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestedSessionId
+ * The requestedSessionId to set.
+ */
+ public void setRequestedSessionId(String requestedSessionId)
+ {
+ _requestedSessionId = requestedSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestedSessionIdCookie
+ * The requestedSessionIdCookie to set.
+ */
+ public void setRequestedSessionIdFromCookie(boolean requestedSessionIdCookie)
+ {
+ _requestedSessionIdFromCookie = requestedSessionIdCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param requestURI
+ * The requestURI to set.
+ */
+ public void setRequestURI(String requestURI)
+ {
+ _requestURI = requestURI;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param scheme
+ * The scheme to set.
+ */
+ public void setScheme(String scheme)
+ {
+ _scheme = scheme;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param host
+ * The host to set.
+ */
+ public void setServerName(String host)
+ {
+ _serverName = host;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param port
+ * The port to set.
+ */
+ public void setServerPort(int port)
+ {
+ _port = port;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletPath
+ * The servletPath to set.
+ */
+ public void setServletPath(String servletPath)
+ {
+ _servletPath = servletPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param session
+ * The session to set.
+ */
+ public void setSession(HttpSession session)
+ {
+ _session = session;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionManager
+ * The sessionManager to set.
+ */
+ public void setSessionManager(SessionManager sessionManager)
+ {
+ _sessionManager = sessionManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setTimeStamp(long ts)
+ {
+ _timeStamp = ts;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param uri
+ * The uri to set.
+ */
+ public void setUri(HttpURI uri)
+ {
+ _uri = uri;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setUserIdentityScope(UserIdentity.Scope scope)
+ {
+ _scope = scope;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public AsyncContext startAsync() throws IllegalStateException
+ {
+ if (!_asyncSupported)
+ throw new IllegalStateException("!asyncSupported");
+ HttpChannelState state = getHttpChannelState();
+ if (_async==null)
+ _async=new AsyncContextState(state);
+ AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,this,getResponse());
+ state.startAsync(event);
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
+ {
+ if (!_asyncSupported)
+ throw new IllegalStateException("!asyncSupported");
+ HttpChannelState state = getHttpChannelState();
+ if (_async==null)
+ _async=new AsyncContextState(state);
+ AsyncContextEvent event = new AsyncContextEvent(_context,_async,state,this,servletRequest,servletResponse);
+ event.setDispatchContext(getServletContext());
+ event.setDispatchPath(URIUtil.addPaths(getServletPath(),getPathInfo()));
+ state.startAsync(event);
+ return _async;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return (_handled?"[":"(") + getMethod() + " " + _uri + (_handled?"]@":")@") + hashCode() + " " + super.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ {
+ setAuthentication(((Authentication.Deferred)_authentication).authenticate(this,response));
+ return !(_authentication instanceof Authentication.ResponseSent);
+ }
+ response.sendError(HttpStatus.UNAUTHORIZED_401);
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Part getPart(String name) throws IOException, ServletException
+ {
+ getParts();
+
+ return _multiPartInputStream.getPart(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Collection<Part> getParts() throws IOException, ServletException
+ {
+ if (getContentType() == null || !getContentType().startsWith("multipart/form-data"))
+ throw new ServletException("Content-Type != multipart/form-data");
+ return getParts(null);
+ }
+
+ private Collection<Part> getParts(MultiMap<String> params) throws IOException, ServletException
+ {
+ if (_multiPartInputStream == null)
+ _multiPartInputStream = (MultiPartInputStreamParser)getAttribute(__MULTIPART_INPUT_STREAM);
+
+ if (_multiPartInputStream == null)
+ {
+ MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
+
+ if (config == null)
+ throw new IllegalStateException("No multipart config for servlet");
+
+ _multiPartInputStream = new MultiPartInputStreamParser(getInputStream(),
+ getContentType(), config,
+ (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
+
+ setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
+ setAttribute(__MULTIPART_CONTEXT, _context);
+ Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
+ ByteArrayOutputStream os = null;
+ for (Part p:parts)
+ {
+ MultiPartInputStreamParser.MultiPart mp = (MultiPartInputStreamParser.MultiPart)p;
+ if (mp.getContentDispositionFilename() == null)
+ {
+ // Servlet Spec 3.0 pg 23, parts without filename must be put into params.
+ String charset = null;
+ if (mp.getContentType() != null)
+ charset = MimeTypes.getCharsetFromContentType(mp.getContentType());
+
+ try (InputStream is = mp.getInputStream())
+ {
+ if (os == null)
+ os = new ByteArrayOutputStream();
+ IO.copy(is, os);
+ String content=new String(os.toByteArray(),charset==null?StandardCharsets.UTF_8:Charset.forName(charset));
+ if (_contentParameters == null)
+ _contentParameters = params == null ? new MultiMap<String>() : params;
+ _contentParameters.add(mp.getName(), content);
+ }
+ os.reset();
+ }
+ }
+ }
+
+ return _multiPartInputStream.getParts();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void login(String username, String password) throws ServletException
+ {
+ if (_authentication instanceof Authentication.Deferred)
+ {
+ _authentication=((Authentication.Deferred)_authentication).login(username,password,this);
+ if (_authentication == null)
+ throw new Authentication.Failed("Authentication failed for username '"+username+"'");
+ }
+ else
+ {
+ throw new Authentication.Failed("Authenticated failed for username '"+username+"'. Already authenticated as "+_authentication);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void logout() throws ServletException
+ {
+ if (_authentication instanceof Authentication.User)
+ ((Authentication.User)_authentication).logout();
+ _authentication=Authentication.UNAUTHENTICATED;
+ }
+
+ public void mergeQueryParameters(String newQuery, boolean updateQueryString)
+ {
+ MultiMap<String> newQueryParams = new MultiMap<>();
+ // Have to assume ENCODING because we can't know otherwise.
+ UrlEncoded.decodeTo(newQuery, newQueryParams, UrlEncoded.ENCODING, -1);
+
+ MultiMap<String> oldQueryParams = _queryParameters;
+ if (oldQueryParams == null && _queryString != null)
+ {
+ oldQueryParams = new MultiMap<>();
+ UrlEncoded.decodeTo(_queryString, oldQueryParams, getQueryEncoding(), -1);
+ }
+
+ MultiMap<String> mergedQueryParams = newQueryParams;
+ if (oldQueryParams != null)
+ {
+ // Parameters values are accumulated.
+ mergedQueryParams = new MultiMap<>(newQueryParams);
+ mergedQueryParams.addAllValues(oldQueryParams);
+ }
+
+ setQueryParameters(mergedQueryParams);
+ resetParameters();
+
+ if (updateQueryString)
+ {
+ // Build the new merged query string, parameters in the
+ // new query string hide parameters in the old query string.
+ StringBuilder mergedQuery = new StringBuilder(newQuery);
+ for (Map.Entry<String, List<String>> entry : mergedQueryParams.entrySet())
+ {
+ if (newQueryParams.containsKey(entry.getKey()))
+ continue;
+ for (String value : entry.getValue())
+ mergedQuery.append("&").append(entry.getKey()).append("=").append(value);
+ }
+
+ setQueryString(mergedQuery.toString());
+ }
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+ */
+ @Override
+ public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+ {
+ if (getContext() == null)
+ throw new ServletException ("Unable to instantiate "+handlerClass);
+
+ try
+ {
+ //Instantiate an instance and inject it
+ T h = getContext().createInstance(handlerClass);
+
+ //TODO handle the rest of the upgrade process
+
+ return h;
+ }
+ catch (Exception e)
+ {
+ if (e instanceof ServletException)
+ throw (ServletException)e;
+ throw new ServletException(e);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/**
+ * A <code>RequestLog</code> can be attached to a {@link org.eclipse.jetty.server.handler.RequestLogHandler} to enable
+ * logging of requests/responses.
+ */
+public interface RequestLog extends LifeCycle
+{
+ public void log(Request request, Response response);
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Comparator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+/* ------------------------------------------------------------ */
+/**
+ *
+ */
+public class ResourceCache
+{
+ private static final Logger LOG = Log.getLogger(ResourceCache.class);
+
+ private final ConcurrentMap<String,Content> _cache;
+ private final AtomicInteger _cachedSize;
+ private final AtomicInteger _cachedFiles;
+ private final ResourceFactory _factory;
+ private final ResourceCache _parent;
+ private final MimeTypes _mimeTypes;
+ private final boolean _etagSupported;
+ private final boolean _useFileMappedBuffer;
+
+ private int _maxCachedFileSize =4*1024*1024;
+ private int _maxCachedFiles=2048;
+ private int _maxCacheSize =32*1024*1024;
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ * @param mimeTypes Mimetype to use for meta data
+ */
+ public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
+ {
+ _factory = factory;
+ _cache=new ConcurrentHashMap<String,Content>();
+ _cachedSize=new AtomicInteger();
+ _cachedFiles=new AtomicInteger();
+ _mimeTypes=mimeTypes;
+ _parent=parent;
+ _useFileMappedBuffer=useFileMappedBuffer;
+ _etagSupported=etags;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getCachedSize()
+ {
+ return _cachedSize.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getCachedFiles()
+ {
+ return _cachedFiles.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxCachedFileSize()
+ {
+ return _maxCachedFileSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxCachedFileSize(int maxCachedFileSize)
+ {
+ _maxCachedFileSize = maxCachedFileSize;
+ shrinkCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxCacheSize()
+ {
+ return _maxCacheSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMaxCacheSize(int maxCacheSize)
+ {
+ _maxCacheSize = maxCacheSize;
+ shrinkCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the maxCachedFiles.
+ */
+ public int getMaxCachedFiles()
+ {
+ return _maxCachedFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maxCachedFiles The maxCachedFiles to set.
+ */
+ public void setMaxCachedFiles(int maxCachedFiles)
+ {
+ _maxCachedFiles = maxCachedFiles;
+ shrinkCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isUseFileMappedBuffer()
+ {
+ return _useFileMappedBuffer;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void flushCache()
+ {
+ if (_cache!=null)
+ {
+ while (_cache.size()>0)
+ {
+ for (String path : _cache.keySet())
+ {
+ Content content = _cache.remove(path);
+ if (content!=null)
+ content.invalidate();
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get a Entry from the cache.
+ * Get either a valid entry object or create a new one if possible.
+ *
+ * @param pathInContext The key into the cache
+ * @return The entry matching <code>pathInContext</code>, or a new entry
+ * if no matching entry was found. If the content exists but is not cachable,
+ * then a {@link ResourceAsHttpContent} instance is return. If
+ * the resource does not exist, then null is returned.
+ * @throws IOException Problem loading the resource
+ */
+ public HttpContent lookup(String pathInContext)
+ throws IOException
+ {
+ // Is the content in this cache?
+ Content content =_cache.get(pathInContext);
+ if (content!=null && (content).isValid())
+ return content;
+
+ // try loading the content from our factory.
+ Resource resource=_factory.getResource(pathInContext);
+ HttpContent loaded = load(pathInContext,resource);
+ if (loaded!=null)
+ return loaded;
+
+ // Is the content in the parent cache?
+ if (_parent!=null)
+ {
+ HttpContent httpContent=_parent.lookup(pathInContext);
+ if (httpContent!=null)
+ return httpContent;
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param resource
+ * @return True if the resource is cacheable. The default implementation tests the cache sizes.
+ */
+ protected boolean isCacheable(Resource resource)
+ {
+ long len = resource.length();
+
+ // Will it fit in the cache?
+ return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
+ }
+
+ /* ------------------------------------------------------------ */
+ private HttpContent load(String pathInContext, Resource resource)
+ throws IOException
+ {
+ Content content=null;
+
+ if (resource==null || !resource.exists())
+ return null;
+
+ // Will it fit in the cache?
+ if (!resource.isDirectory() && isCacheable(resource))
+ {
+ // Create the Content (to increment the cache sizes before adding the content
+ content = new Content(pathInContext,resource);
+
+ // reduce the cache to an acceptable size.
+ shrinkCache();
+
+ // Add it to the cache.
+ Content added = _cache.putIfAbsent(pathInContext,content);
+ if (added!=null)
+ {
+ content.invalidate();
+ content=added;
+ }
+
+ return content;
+ }
+
+ return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
+
+ }
+
+ /* ------------------------------------------------------------ */
+ private void shrinkCache()
+ {
+ // While we need to shrink
+ while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
+ {
+ // Scan the entire cache and generate an ordered list by last accessed time.
+ SortedSet<Content> sorted= new TreeSet<Content>(
+ new Comparator<Content>()
+ {
+ public int compare(Content c1, Content c2)
+ {
+ if (c1._lastAccessed<c2._lastAccessed)
+ return -1;
+
+ if (c1._lastAccessed>c2._lastAccessed)
+ return 1;
+
+ if (c1._length<c2._length)
+ return -1;
+
+ return c1._key.compareTo(c2._key);
+ }
+ });
+ for (Content content : _cache.values())
+ sorted.add(content);
+
+ // Invalidate least recently used first
+ for (Content content : sorted)
+ {
+ if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
+ break;
+ if (content==_cache.remove(content.getKey()))
+ content.invalidate();
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected ByteBuffer getIndirectBuffer(Resource resource)
+ {
+ try
+ {
+ return BufferUtil.toBuffer(resource,true);
+ }
+ catch(IOException|IllegalArgumentException e)
+ {
+ LOG.warn(e);
+ return null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected ByteBuffer getDirectBuffer(Resource resource)
+ {
+ try
+ {
+ if (_useFileMappedBuffer && resource.getFile()!=null)
+ return BufferUtil.toMappedBuffer(resource.getFile());
+
+ return BufferUtil.toBuffer(resource,true);
+ }
+ catch(IOException|IllegalArgumentException e)
+ {
+ LOG.warn(e);
+ return null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /** MetaData associated with a context Resource.
+ */
+ public class Content implements HttpContent
+ {
+ final Resource _resource;
+ final int _length;
+ final String _key;
+ final long _lastModified;
+ final ByteBuffer _lastModifiedBytes;
+ final ByteBuffer _contentType;
+ final String _etag;
+
+ volatile long _lastAccessed;
+ AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
+ AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
+
+ /* ------------------------------------------------------------ */
+ Content(String pathInContext,Resource resource)
+ {
+ _key=pathInContext;
+ _resource=resource;
+
+ String mimeType = _mimeTypes.getMimeByExtension(_resource.toString());
+ _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType));
+ boolean exists=resource.exists();
+ _lastModified=exists?resource.lastModified():-1;
+ _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified));
+
+ _length=exists?(int)resource.length():0;
+ _cachedSize.addAndGet(_length);
+ _cachedFiles.incrementAndGet();
+ _lastAccessed=System.currentTimeMillis();
+
+ _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String getKey()
+ {
+ return _key;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isCached()
+ {
+ return _key!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isMiss()
+ {
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Resource getResource()
+ {
+ return _resource;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getETag()
+ {
+ return _etag;
+ }
+
+ /* ------------------------------------------------------------ */
+ boolean isValid()
+ {
+ if (_lastModified==_resource.lastModified() && _length==_resource.length())
+ {
+ _lastAccessed=System.currentTimeMillis();
+ return true;
+ }
+
+ if (this==_cache.remove(_key))
+ invalidate();
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void invalidate()
+ {
+ // Invalidate it
+ _cachedSize.addAndGet(-_length);
+ _cachedFiles.decrementAndGet();
+ _resource.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getLastModified()
+ {
+ return BufferUtil.toString(_lastModifiedBytes);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getContentType()
+ {
+ return BufferUtil.toString(_contentType);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void release()
+ {
+ // don't release while cached. Release when invalidated.
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ByteBuffer getIndirectBuffer()
+ {
+ ByteBuffer buffer = _indirectBuffer.get();
+ if (buffer==null)
+ {
+ ByteBuffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
+
+ if (buffer2==null)
+ LOG.warn("Could not load "+this);
+ else if (_indirectBuffer.compareAndSet(null,buffer2))
+ buffer=buffer2;
+ else
+ buffer=_indirectBuffer.get();
+ }
+ if (buffer==null)
+ return null;
+ return buffer.slice();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ByteBuffer getDirectBuffer()
+ {
+ ByteBuffer buffer = _directBuffer.get();
+ if (buffer==null)
+ {
+ ByteBuffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
+
+ if (buffer2==null)
+ LOG.warn("Could not load "+this);
+ else if (_directBuffer.compareAndSet(null,buffer2))
+ buffer=buffer2;
+ else
+ buffer=_directBuffer.get();
+ }
+ if (buffer==null)
+ return null;
+ return buffer.asReadOnlyBuffer();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public long getContentLength()
+ {
+ return _length;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public InputStream getInputStream() throws IOException
+ {
+ ByteBuffer indirect = getIndirectBuffer();
+ if (indirect!=null && indirect.hasArray())
+ return new ByteArrayInputStream(indirect.array(),indirect.arrayOffset()+indirect.position(),indirect.remaining());
+
+ return _resource.getInputStream();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ReadableByteChannel getReadableByteChannel() throws IOException
+ {
+ return _resource.getReadableByteChannel();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.channels.IllegalSelectorException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>{@link Response} provides the implementation for {@link HttpServletResponse}.</p>
+ */
+public class Response implements HttpServletResponse
+{
+ private static final Logger LOG = Log.getLogger(Response.class);
+ private static final String __COOKIE_DELIM="\",;\\ \t";
+ private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
+ private final static int __MIN_BUFFER_SIZE = 1;
+
+
+ // Cookie building buffer. Reduce garbage for cookie using applications
+ private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>()
+ {
+ @Override
+ protected StringBuilder initialValue()
+ {
+ return new StringBuilder(128);
+ }
+ };
+
+ /* ------------------------------------------------------------ */
+ public static Response getResponse(HttpServletResponse response)
+ {
+ if (response instanceof Response)
+ return (Response)response;
+ return HttpChannel.getCurrentHttpChannel().getResponse();
+ }
+
+
+ public enum OutputType
+ {
+ NONE, STREAM, WRITER
+ }
+
+ /**
+ * If a header name starts with this string, the header (stripped of the prefix)
+ * can be set during include using only {@link #setHeader(String, String)} or
+ * {@link #addHeader(String, String)}.
+ */
+ public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
+
+ /**
+ * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie
+ * will be set as HTTP ONLY.
+ */
+ public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
+
+ private final HttpChannel<?> _channel;
+ private final HttpFields _fields = new HttpFields();
+ private final AtomicInteger _include = new AtomicInteger();
+ private HttpOutput _out;
+ private int _status = HttpStatus.NOT_SET_000;
+ private String _reason;
+ private Locale _locale;
+ private MimeTypes.Type _mimeType;
+ private String _characterEncoding;
+ private boolean _explicitEncoding;
+ private String _contentType;
+ private OutputType _outputType = OutputType.NONE;
+ private ResponseWriter _writer;
+ private long _contentLength = -1;
+
+
+ public Response(HttpChannel<?> channel, HttpOutput out)
+ {
+ _channel = channel;
+ _out = out;
+ }
+
+ protected HttpChannel<?> getHttpChannel()
+ {
+ return _channel;
+ }
+
+ protected void recycle()
+ {
+ _status = HttpStatus.NOT_SET_000;
+ _reason = null;
+ _locale = null;
+ _mimeType = null;
+ _characterEncoding = null;
+ _contentType = null;
+ _outputType = OutputType.NONE;
+ _contentLength = -1;
+ _out.reset();
+ _fields.clear();
+ _explicitEncoding=false;
+ }
+
+ public void setHeaders(HttpContent httpContent)
+ {
+ Response response = _channel.getResponse();
+ String contentType = httpContent.getContentType();
+ if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString()))
+ setContentType(contentType);
+
+ if (httpContent.getContentLength() > 0)
+ setLongContentLength(httpContent.getContentLength());
+
+ String lm = httpContent.getLastModified();
+ if (lm != null)
+ response.getHttpFields().put(HttpHeader.LAST_MODIFIED, lm);
+ else if (httpContent.getResource() != null)
+ {
+ long lml = httpContent.getResource().lastModified();
+ if (lml != -1)
+ response.getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, lml);
+ }
+
+ String etag=httpContent.getETag();
+ if (etag!=null)
+ response.getHttpFields().put(HttpHeader.ETAG,etag);
+ }
+
+ public HttpOutput getHttpOutput()
+ {
+ return _out;
+ }
+
+ public void setHttpOutput(HttpOutput out)
+ {
+ _out=out;
+ }
+
+ public boolean isIncluding()
+ {
+ return _include.get() > 0;
+ }
+
+ public void include()
+ {
+ _include.incrementAndGet();
+ }
+
+ public void included()
+ {
+ _include.decrementAndGet();
+ if (_outputType == OutputType.WRITER)
+ {
+ _writer.reopen();
+ }
+ _out.reopen();
+ }
+
+ public void addCookie(HttpCookie cookie)
+ {
+ addSetCookie(
+ cookie.getName(),
+ cookie.getValue(),
+ cookie.getDomain(),
+ cookie.getPath(),
+ cookie.getMaxAge(),
+ cookie.getComment(),
+ cookie.isSecure(),
+ cookie.isHttpOnly(),
+ cookie.getVersion());;
+ }
+
+ @Override
+ public void addCookie(Cookie cookie)
+ {
+ String comment = cookie.getComment();
+ boolean httpOnly = false;
+
+ if (comment != null)
+ {
+ int i = comment.indexOf(HTTP_ONLY_COMMENT);
+ if (i >= 0)
+ {
+ httpOnly = true;
+ comment = comment.replace(HTTP_ONLY_COMMENT, "").trim();
+ if (comment.length() == 0)
+ comment = null;
+ }
+ }
+ addSetCookie(cookie.getName(),
+ cookie.getValue(),
+ cookie.getDomain(),
+ cookie.getPath(),
+ cookie.getMaxAge(),
+ comment,
+ cookie.getSecure(),
+ httpOnly || cookie.isHttpOnly(),
+ cookie.getVersion());
+ }
+
+
+ /**
+ * Format a set cookie value
+ *
+ * @param name the name
+ * @param value the value
+ * @param domain the domain
+ * @param path the path
+ * @param maxAge the maximum age
+ * @param comment the comment (only present on versions > 0)
+ * @param isSecure true if secure cookie
+ * @param isHttpOnly true if for http only
+ * @param version version of cookie logic to use (0 == default behavior)
+ */
+ public void addSetCookie(
+ final String name,
+ final String value,
+ final String domain,
+ final String path,
+ final long maxAge,
+ final String comment,
+ final boolean isSecure,
+ final boolean isHttpOnly,
+ int version)
+ {
+ // Check arguments
+ if (name == null || name.length() == 0)
+ throw new IllegalArgumentException("Bad cookie name");
+
+ // Format value and params
+ StringBuilder buf = __cookieBuilder.get();
+ buf.setLength(0);
+
+ // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
+ boolean quote_name=isQuoteNeededForCookie(name);
+ quoteOnlyOrAppend(buf,name,quote_name);
+
+ buf.append('=');
+
+ // Remember name= part to look for other matching set-cookie
+ String name_equals=buf.toString();
+
+ // Append the value
+ boolean quote_value=isQuoteNeededForCookie(value);
+ quoteOnlyOrAppend(buf,value,quote_value);
+
+ // Look for domain and path fields and check if they need to be quoted
+ boolean has_domain = domain!=null && domain.length()>0;
+ boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
+ boolean has_path = path!=null && path.length()>0;
+ boolean quote_path = has_path && isQuoteNeededForCookie(path);
+
+ // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
+ if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
+ version=1;
+
+ // Append version
+ if (version==1)
+ buf.append (";Version=1");
+ else if (version>1)
+ buf.append (";Version=").append(version);
+
+ // Append path
+ if (has_path)
+ {
+ buf.append(";Path=");
+ quoteOnlyOrAppend(buf,path,quote_path);
+ }
+
+ // Append domain
+ if (has_domain)
+ {
+ buf.append(";Domain=");
+ quoteOnlyOrAppend(buf,domain,quote_domain);
+ }
+
+ // Handle max-age and/or expires
+ if (maxAge >= 0)
+ {
+ // Always use expires
+ // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
+ buf.append(";Expires=");
+ if (maxAge == 0)
+ buf.append(__01Jan1970_COOKIE);
+ else
+ DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
+
+ // for v1 cookies, also send max-age
+ if (version>=1)
+ {
+ buf.append(";Max-Age=");
+ buf.append(maxAge);
+ }
+ }
+
+ // add the other fields
+ if (isSecure)
+ buf.append(";Secure");
+ if (isHttpOnly)
+ buf.append(";HttpOnly");
+ if (comment != null)
+ {
+ buf.append(";Comment=");
+ quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
+ }
+
+ // remove any existing set-cookie fields of same name
+ Iterator<HttpField> i=_fields.iterator();
+ while (i.hasNext())
+ {
+ HttpField field=i.next();
+ if (field.getHeader()==HttpHeader.SET_COOKIE)
+ {
+ String val = field.getValue();
+ if (val!=null && val.startsWith(name_equals))
+ {
+ //existing cookie has same name, does it also match domain and path?
+ if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
+ ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
+ {
+ i.remove();
+ }
+ }
+ }
+ }
+
+ // add the set cookie
+ _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString());
+
+ // Expire responses with set-cookie headers so they do not get cached.
+ _fields.put(HttpHeader.EXPIRES.toString(), DateGenerator.__01Jan1970);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Does a cookie value need to be quoted?
+ * @param s value string
+ * @return true if quoted;
+ * @throws IllegalArgumentException If there a control characters in the string
+ */
+ private static boolean isQuoteNeededForCookie(String s)
+ {
+ if (s==null || s.length()==0)
+ return true;
+
+ if (QuotedStringTokenizer.isQuoted(s))
+ return false;
+
+ for (int i=0;i<s.length();i++)
+ {
+ char c = s.charAt(i);
+ if (__COOKIE_DELIM.indexOf(c)>=0)
+ return true;
+
+ if (c<0x20 || c>=0x7f)
+ throw new IllegalArgumentException("Illegal character in cookie value");
+ }
+
+ return false;
+ }
+
+
+ private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
+ {
+ if (quote)
+ QuotedStringTokenizer.quoteOnly(buf,s);
+ else
+ buf.append(s);
+ }
+
+ @Override
+ public boolean containsHeader(String name)
+ {
+ return _fields.containsKey(name);
+ }
+
+ @Override
+ public String encodeURL(String url)
+ {
+ final Request request = _channel.getRequest();
+ SessionManager sessionManager = request.getSessionManager();
+ if (sessionManager == null)
+ return url;
+
+ HttpURI uri = null;
+ if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url))
+ {
+ uri = new HttpURI(url);
+ String path = uri.getPath();
+ path = (path == null ? "" : path);
+ int port = uri.getPort();
+ if (port < 0)
+ port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
+ if (!request.getServerName().equalsIgnoreCase(uri.getHost()) ||
+ request.getServerPort() != port ||
+ !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts
+ return url;
+ }
+
+ String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
+ if (sessionURLPrefix == null)
+ return url;
+
+ if (url == null)
+ return null;
+
+ // should not encode if cookies in evidence
+ if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs())
+ {
+ int prefix = url.indexOf(sessionURLPrefix);
+ if (prefix != -1)
+ {
+ int suffix = url.indexOf("?", prefix);
+ if (suffix < 0)
+ suffix = url.indexOf("#", prefix);
+
+ if (suffix <= prefix)
+ return url.substring(0, prefix);
+ return url.substring(0, prefix) + url.substring(suffix);
+ }
+ return url;
+ }
+
+ // get session;
+ HttpSession session = request.getSession(false);
+
+ // no session
+ if (session == null)
+ return url;
+
+ // invalid session
+ if (!sessionManager.isValid(session))
+ return url;
+
+ String id = sessionManager.getNodeId(session);
+
+ if (uri == null)
+ uri = new HttpURI(url);
+
+
+ // Already encoded
+ int prefix = url.indexOf(sessionURLPrefix);
+ if (prefix != -1)
+ {
+ int suffix = url.indexOf("?", prefix);
+ if (suffix < 0)
+ suffix = url.indexOf("#", prefix);
+
+ if (suffix <= prefix)
+ return url.substring(0, prefix + sessionURLPrefix.length()) + id;
+ return url.substring(0, prefix + sessionURLPrefix.length()) + id +
+ url.substring(suffix);
+ }
+
+ // edit the session
+ int suffix = url.indexOf('?');
+ if (suffix < 0)
+ suffix = url.indexOf('#');
+ if (suffix < 0)
+ {
+ return url +
+ ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path, insert the root path
+ sessionURLPrefix + id;
+ }
+
+
+ return url.substring(0, suffix) +
+ ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") + //if no path so insert the root path
+ sessionURLPrefix + id + url.substring(suffix);
+ }
+
+ @Override
+ public String encodeRedirectURL(String url)
+ {
+ return encodeURL(url);
+ }
+
+ @Override
+ @Deprecated
+ public String encodeUrl(String url)
+ {
+ return encodeURL(url);
+ }
+
+ @Override
+ @Deprecated
+ public String encodeRedirectUrl(String url)
+ {
+ return encodeRedirectURL(url);
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException
+ {
+ if (sc == 102)
+ sendProcessing();
+ else
+ sendError(sc, null);
+ }
+
+ @Override
+ public void sendError(int code, String message) throws IOException
+ {
+ if (isIncluding())
+ return;
+
+ if (isCommitted())
+ LOG.warn("Committed before "+code+" "+message);
+
+ resetBuffer();
+ _characterEncoding=null;
+ setHeader(HttpHeader.EXPIRES,null);
+ setHeader(HttpHeader.LAST_MODIFIED,null);
+ setHeader(HttpHeader.CACHE_CONTROL,null);
+ setHeader(HttpHeader.CONTENT_TYPE,null);
+ setHeader(HttpHeader.CONTENT_LENGTH,null);
+
+ _outputType = OutputType.NONE;
+ setStatus(code);
+ _reason=message;
+
+ Request request = _channel.getRequest();
+ Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
+ if (message==null)
+ message=cause==null?HttpStatus.getMessage(code):cause.toString();
+
+ // If we are allowed to have a body
+ if (code!=SC_NO_CONTENT &&
+ code!=SC_NOT_MODIFIED &&
+ code!=SC_PARTIAL_CONTENT &&
+ code>=SC_OK)
+ {
+ ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(),request.getContext()==null?null:request.getContext().getContextHandler());
+ if (error_handler!=null)
+ {
+ request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
+ request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
+ request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
+ request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
+ error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this );
+ }
+ else
+ {
+ setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
+ setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
+ try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);)
+ {
+ if (message != null)
+ {
+ message= StringUtil.replace(message, "&", "&");
+ message= StringUtil.replace(message, "<", "<");
+ message= StringUtil.replace(message, ">", ">");
+ }
+ String uri= request.getRequestURI();
+ if (uri!=null)
+ {
+ uri= StringUtil.replace(uri, "&", "&");
+ uri= StringUtil.replace(uri, "<", "<");
+ uri= StringUtil.replace(uri, ">", ">");
+ }
+
+ writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
+ writer.write("<title>Error ");
+ writer.write(Integer.toString(code));
+ writer.write(' ');
+ if (message==null)
+ writer.write(message);
+ writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
+ writer.write(Integer.toString(code));
+ writer.write("</h2>\n<p>Problem accessing ");
+ writer.write(uri);
+ writer.write(". Reason:\n<pre> ");
+ writer.write(message);
+ writer.write("</pre>");
+ writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
+ writer.write("\n</body>\n</html>\n");
+
+ writer.flush();
+ setContentLength(writer.size());
+ try (ServletOutputStream outputStream = getOutputStream())
+ {
+ writer.writeTo(outputStream);
+ writer.destroy();
+ }
+ }
+ }
+ }
+ else if (code!=SC_PARTIAL_CONTENT)
+ {
+ // TODO work out why this is required?
+ _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
+ _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
+ _characterEncoding=null;
+ _mimeType=null;
+ }
+
+ closeOutput();
+ }
+
+ /**
+ * Sends a 102-Processing response.
+ * If the connection is a HTTP connection, the version is 1.1 and the
+ * request has a Expect header starting with 102, then a 102 response is
+ * sent. This indicates that the request still be processed and real response
+ * can still be sent. This method is called by sendError if it is passed 102.
+ * @see javax.servlet.http.HttpServletResponse#sendError(int)
+ */
+ public void sendProcessing() throws IOException
+ {
+ if (_channel.isExpecting102Processing() && !isCommitted())
+ {
+ _channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
+ }
+ }
+
+ /**
+ * Sends a response with one of the 300 series redirection codes.
+ * @param code
+ * @param location
+ * @throws IOException
+ */
+ public void sendRedirect(int code, String location) throws IOException
+ {
+ if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST))
+ throw new IllegalArgumentException("Not a 3xx redirect code");
+
+ if (isIncluding())
+ return;
+
+ if (location == null)
+ throw new IllegalArgumentException();
+
+ if (!URIUtil.hasScheme(location))
+ {
+ StringBuilder buf = _channel.getRequest().getRootURL();
+
+ if (location.startsWith("//"))
+ {
+ buf.delete(0, buf.length());
+ buf.append(_channel.getRequest().getScheme());
+ buf.append(":");
+ buf.append(location);
+ }
+ else if (location.startsWith("/"))
+ buf.append(location);
+ else
+ {
+ String path = _channel.getRequest().getRequestURI();
+ String parent = (path.endsWith("/")) ? path : URIUtil.parentPath(path);
+ location = URIUtil.addPaths(parent, location);
+ if (location == null)
+ throw new IllegalStateException("path cannot be above root");
+ if (!location.startsWith("/"))
+ buf.append('/');
+ buf.append(location);
+ }
+
+ location = buf.toString();
+ HttpURI uri = new HttpURI(location);
+ String path = uri.getDecodedPath();
+ String canonical = URIUtil.canonicalPath(path);
+ if (canonical == null)
+ throw new IllegalArgumentException();
+ if (!canonical.equals(path))
+ {
+ buf = _channel.getRequest().getRootURL();
+ buf.append(URIUtil.encodePath(canonical));
+ String param=uri.getParam();
+ if (param!=null)
+ {
+ buf.append(';');
+ buf.append(param);
+ }
+ String query=uri.getQuery();
+ if (query!=null)
+ {
+ buf.append('?');
+ buf.append(query);
+ }
+ String fragment=uri.getFragment();
+ if (fragment!=null)
+ {
+ buf.append('#');
+ buf.append(fragment);
+ }
+ location = buf.toString();
+ }
+ }
+
+ resetBuffer();
+ setHeader(HttpHeader.LOCATION, location);
+ setStatus(code);
+ closeOutput();
+ }
+
+ @Override
+ public void sendRedirect(String location) throws IOException
+ {
+ sendRedirect(HttpServletResponse.SC_MOVED_TEMPORARILY, location);
+ }
+
+ @Override
+ public void setDateHeader(String name, long date)
+ {
+ if (!isIncluding())
+ _fields.putDateField(name, date);
+ }
+
+ @Override
+ public void addDateHeader(String name, long date)
+ {
+ if (!isIncluding())
+ _fields.addDateField(name, date);
+ }
+
+ public void setHeader(HttpHeader name, String value)
+ {
+ if (HttpHeader.CONTENT_TYPE == name)
+ setContentType(value);
+ else
+ {
+ if (isIncluding())
+ return;
+
+ _fields.put(name, value);
+
+ if (HttpHeader.CONTENT_LENGTH == name)
+ {
+ if (value == null)
+ _contentLength = -1l;
+ else
+ _contentLength = Long.parseLong(value);
+ }
+ }
+ }
+
+ @Override
+ public void setHeader(String name, String value)
+ {
+ if (HttpHeader.CONTENT_TYPE.is(name))
+ setContentType(value);
+ else
+ {
+ if (isIncluding())
+ {
+ if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+ name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+ else
+ return;
+ }
+ _fields.put(name, value);
+ if (HttpHeader.CONTENT_LENGTH.is(name))
+ {
+ if (value == null)
+ _contentLength = -1l;
+ else
+ _contentLength = Long.parseLong(value);
+ }
+ }
+ }
+
+ @Override
+ public Collection<String> getHeaderNames()
+ {
+ final HttpFields fields = _fields;
+ return fields.getFieldNamesCollection();
+ }
+
+ @Override
+ public String getHeader(String name)
+ {
+ return _fields.getStringField(name);
+ }
+
+ @Override
+ public Collection<String> getHeaders(String name)
+ {
+ final HttpFields fields = _fields;
+ Collection<String> i = fields.getValuesList(name);
+ if (i == null)
+ return Collections.emptyList();
+ return i;
+ }
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ if (isIncluding())
+ {
+ if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+ name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+ else
+ return;
+ }
+
+ if (HttpHeader.CONTENT_TYPE.is(name))
+ {
+ setContentType(value);
+ return;
+ }
+
+ if (HttpHeader.CONTENT_LENGTH.is(name))
+ {
+ setHeader(name,value);
+ return;
+ }
+
+ _fields.add(name, value);
+ }
+
+ @Override
+ public void setIntHeader(String name, int value)
+ {
+ if (!isIncluding())
+ {
+ _fields.putLongField(name, value);
+ if (HttpHeader.CONTENT_LENGTH.is(name))
+ _contentLength = value;
+ }
+ }
+
+ @Override
+ public void addIntHeader(String name, int value)
+ {
+ if (!isIncluding())
+ {
+ _fields.add(name, Integer.toString(value));
+ if (HttpHeader.CONTENT_LENGTH.is(name))
+ _contentLength = value;
+ }
+ }
+
+ @Override
+ public void setStatus(int sc)
+ {
+ if (sc <= 0)
+ throw new IllegalArgumentException();
+ if (!isIncluding())
+ {
+ _status = sc;
+ _reason = null;
+ }
+ }
+
+ @Override
+ @Deprecated
+ public void setStatus(int sc, String sm)
+ {
+ setStatusWithReason(sc,sm);
+ }
+
+ public void setStatusWithReason(int sc, String sm)
+ {
+ if (sc <= 0)
+ throw new IllegalArgumentException();
+ if (!isIncluding())
+ {
+ _status = sc;
+ _reason = sm;
+ }
+ }
+
+ @Override
+ public String getCharacterEncoding()
+ {
+ if (_characterEncoding == null)
+ _characterEncoding = StringUtil.__ISO_8859_1;
+ return _characterEncoding;
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return _contentType;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException
+ {
+ if (_outputType == OutputType.WRITER)
+ throw new IllegalStateException("WRITER");
+ _outputType = OutputType.STREAM;
+ return _out;
+ }
+
+ public boolean isWriting()
+ {
+ return _outputType == OutputType.WRITER;
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException
+ {
+ if (_outputType == OutputType.STREAM)
+ throw new IllegalStateException("STREAM");
+
+ if (_outputType == OutputType.NONE)
+ {
+ /* get encoding from Content-Type header */
+ String encoding = _characterEncoding;
+ if (encoding == null)
+ {
+ if (_mimeType!=null && _mimeType.isCharsetAssumed())
+ encoding=_mimeType.getCharset().toString();
+ else
+ {
+ encoding = MimeTypes.inferCharsetFromContentType(_contentType);
+ if (encoding == null)
+ encoding = StringUtil.__ISO_8859_1;
+ setCharacterEncoding(encoding,false);
+ }
+ }
+
+ if (_writer != null && _writer.isFor(encoding))
+ _writer.reopen();
+ else
+ {
+ if (StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
+ _writer = new ResponseWriter(new Iso88591HttpWriter(_out),encoding);
+ else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
+ _writer = new ResponseWriter(new Utf8HttpWriter(_out),encoding);
+ else
+ _writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),encoding);
+ }
+
+ // Set the output type at the end, because setCharacterEncoding() checks for it
+ _outputType = OutputType.WRITER;
+ }
+ return _writer;
+ }
+
+ @Override
+ public void setContentLength(int len)
+ {
+ // Protect from setting after committed as default handling
+ // of a servlet HEAD request ALWAYS sets _content length, even
+ // if the getHandling committed the response!
+ if (isCommitted() || isIncluding())
+ return;
+
+ _contentLength = len;
+ if (_contentLength > 0)
+ {
+ long written = _out.getWritten();
+ if (written > len)
+ throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
+
+ _fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
+ if (isAllContentWritten(written))
+ {
+ try
+ {
+ closeOutput();
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeIOException(e);
+ }
+ }
+ }
+ else if (_contentLength==0)
+ {
+ long written = _out.getWritten();
+ if (written > 0)
+ throw new IllegalArgumentException("setContentLength(0) when already written " + written);
+ _fields.put(HttpHeader.CONTENT_LENGTH, "0");
+ }
+ else
+ _fields.remove(HttpHeader.CONTENT_LENGTH);
+ }
+
+ public long getContentLength()
+ {
+ return _contentLength;
+ }
+
+ public boolean isAllContentWritten(long written)
+ {
+ return (_contentLength >= 0 && written >= _contentLength);
+ }
+
+ public void closeOutput() throws IOException
+ {
+ switch (_outputType)
+ {
+ case WRITER:
+ _writer.close();
+ if (!_out.isClosed())
+ _out.close();
+ break;
+ case STREAM:
+ getOutputStream().close();
+ break;
+ default:
+ _out.close();
+ }
+ }
+
+ public long getLongContentLength()
+ {
+ return _contentLength;
+ }
+
+ public void setLongContentLength(long len)
+ {
+ // Protect from setting after committed as default handling
+ // of a servlet HEAD request ALWAYS sets _content length, even
+ // if the getHandling committed the response!
+ if (isCommitted() || isIncluding())
+ return;
+ _contentLength = len;
+ _fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
+ }
+
+ @Override
+ public void setContentLengthLong(long length)
+ {
+ setLongContentLength(length);
+ }
+
+ @Override
+ public void setCharacterEncoding(String encoding)
+ {
+ setCharacterEncoding(encoding,true);
+ }
+
+ private void setCharacterEncoding(String encoding, boolean explicit)
+ {
+ if (isIncluding() || isWriting())
+ return;
+
+ if (_outputType == OutputType.NONE && !isCommitted())
+ {
+ if (encoding == null)
+ {
+ _explicitEncoding=false;
+
+ // Clear any encoding.
+ if (_characterEncoding != null)
+ {
+ _characterEncoding = null;
+
+ if (_mimeType!=null)
+ {
+ _mimeType=_mimeType.getBaseType();
+ _contentType=_mimeType.asString();
+ _fields.put(_mimeType.getContentTypeField());
+ }
+ else if (_contentType != null)
+ {
+ _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
+ _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+ }
+ }
+ }
+ else
+ {
+ // No, so just add this one to the mimetype
+ _explicitEncoding = explicit;
+ _characterEncoding = HttpGenerator.__STRICT?encoding:StringUtil.normalizeCharset(encoding);
+ if (_mimeType!=null)
+ {
+ _contentType=_mimeType.getBaseType().asString()+ "; charset=" + _characterEncoding;
+ _mimeType = MimeTypes.CACHE.get(_contentType);
+ if (_mimeType==null || HttpGenerator.__STRICT)
+ _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+ else
+ _fields.put(_mimeType.getContentTypeField());
+ }
+ else if (_contentType != null)
+ {
+ _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + "; charset=" + _characterEncoding;
+ _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setContentType(String contentType)
+ {
+ if (isCommitted() || isIncluding())
+ return;
+
+ if (contentType == null)
+ {
+ if (isWriting() && _characterEncoding != null)
+ throw new IllegalSelectorException();
+
+ if (_locale == null)
+ _characterEncoding = null;
+ _mimeType = null;
+ _contentType = null;
+ _fields.remove(HttpHeader.CONTENT_TYPE);
+ }
+ else
+ {
+ _contentType = contentType;
+ _mimeType = MimeTypes.CACHE.get(contentType);
+
+ String charset;
+ if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed())
+ charset=_mimeType.getCharset().toString();
+ else
+ charset = MimeTypes.getCharsetFromContentType(contentType);
+
+ if (charset == null)
+ {
+ if (_characterEncoding != null)
+ {
+ _contentType = contentType + "; charset=" + _characterEncoding;
+ _mimeType = null;
+ }
+ }
+ else if (isWriting() && !charset.equals(_characterEncoding))
+ {
+ // too late to change the character encoding;
+ _mimeType = null;
+ _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
+ if (_characterEncoding != null)
+ _contentType = _contentType + "; charset=" + _characterEncoding;
+ }
+ else
+ {
+ _characterEncoding = charset;
+ _explicitEncoding = true;
+ }
+
+ if (HttpGenerator.__STRICT || _mimeType==null)
+ _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
+ else
+ {
+ _contentType=_mimeType.asString();
+ _fields.put(_mimeType.getContentTypeField());
+ }
+ }
+
+ }
+
+ @Override
+ public void setBufferSize(int size)
+ {
+ if (isCommitted() || getContentCount() > 0)
+ throw new IllegalStateException("Committed or content written");
+ if (size <= 0)
+ size = __MIN_BUFFER_SIZE;
+ _out.setBufferSize(size);
+ }
+
+ @Override
+ public int getBufferSize()
+ {
+ return _out.getBufferSize();
+ }
+
+ @Override
+ public void flushBuffer() throws IOException
+ {
+ if (!_out.isClosed())
+ _out.flush();
+ }
+
+ @Override
+ public void reset()
+ {
+ resetForForward();
+ _status = 200;
+ _reason = null;
+ _contentLength = -1;
+ _fields.clear();
+
+ String connection = _channel.getRequest().getHttpFields().getStringField(HttpHeader.CONNECTION);
+ if (connection != null)
+ {
+ String[] values = connection.split(",");
+ for (int i = 0; values != null && i < values.length; i++)
+ {
+ HttpHeaderValue cb = HttpHeaderValue.CACHE.get(values[0].trim());
+
+ if (cb != null)
+ {
+ switch (cb)
+ {
+ case CLOSE:
+ _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.toString());
+ break;
+
+ case KEEP_ALIVE:
+ if (HttpVersion.HTTP_1_0.is(_channel.getRequest().getProtocol()))
+ _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString());
+ break;
+ case TE:
+ _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
+ break;
+ default:
+ }
+ }
+ }
+ }
+ }
+
+ public void reset(boolean preserveCookies)
+ {
+ if (!preserveCookies)
+ reset();
+ else
+ {
+ ArrayList<String> cookieValues = new ArrayList<String>(5);
+ Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
+ while (vals.hasMoreElements())
+ cookieValues.add(vals.nextElement());
+ reset();
+ for (String v:cookieValues)
+ _fields.add(HttpHeader.SET_COOKIE, v);
+ }
+ }
+
+ public void resetForForward()
+ {
+ resetBuffer();
+ _outputType = OutputType.NONE;
+ }
+
+ @Override
+ public void resetBuffer()
+ {
+ if (isCommitted())
+ throw new IllegalStateException("Committed");
+
+ switch (_outputType)
+ {
+ case STREAM:
+ case WRITER:
+ _out.reset();
+ break;
+ default:
+ }
+
+ _out.resetBuffer();
+ }
+
+ protected ResponseInfo newResponseInfo()
+ {
+ if (_status == HttpStatus.NOT_SET_000)
+ _status = HttpStatus.OK_200;
+ return new ResponseInfo(_channel.getRequest().getHttpVersion(), _fields, getLongContentLength(), getStatus(), getReason(), _channel.getRequest().isHead());
+ }
+
+ @Override
+ public boolean isCommitted()
+ {
+ return _channel.isCommitted();
+ }
+
+ @Override
+ public void setLocale(Locale locale)
+ {
+ if (locale == null || isCommitted() || isIncluding())
+ return;
+
+ _locale = locale;
+ _fields.put(HttpHeader.CONTENT_LANGUAGE, locale.toString().replace('_', '-'));
+
+ if (_outputType != OutputType.NONE)
+ return;
+
+ if (_channel.getRequest().getContext() == null)
+ return;
+
+ String charset = _channel.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
+
+ if (charset != null && charset.length() > 0 && !_explicitEncoding)
+ setCharacterEncoding(charset,false);
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ if (_locale == null)
+ return Locale.getDefault();
+ return _locale;
+ }
+
+ @Override
+ public int getStatus()
+ {
+ return _status;
+ }
+
+ public String getReason()
+ {
+ return _reason;
+ }
+
+ public HttpFields getHttpFields()
+ {
+ return _fields;
+ }
+
+ public long getContentCount()
+ {
+ return _out.getWritten();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields);
+ }
+
+
+ private static class ResponseWriter extends PrintWriter
+ {
+ private final String _encoding;
+ private final HttpWriter _httpWriter;
+
+ public ResponseWriter(HttpWriter httpWriter,String encoding)
+ {
+ super(httpWriter);
+ _httpWriter=httpWriter;
+ _encoding=encoding;
+ }
+
+ public boolean isFor(String encoding)
+ {
+ return _encoding.equalsIgnoreCase(encoding);
+ }
+
+ protected void reopen()
+ {
+ super.clearError();
+ out=_httpWriter;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+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();
+
+
+ @Override
+ public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
+ {
+ if (request.getHttpChannel().getEndPoint() instanceof DecryptedEndPoint)
+ {
+ request.setScheme(HttpScheme.HTTPS.asString());
+ request.setSecure(true);
+ SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)request.getHttpChannel().getEndPoint();
+ SslConnection sslConnection = ssl_endp.getSslConnection();
+ SSLEngine sslEngine=sslConnection.getSSLEngine();
+ customize(sslEngine,request);
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Allow the Listener a chance to customise the request. before the server
+ * does its stuff. <br>
+ * This allows the required attributes to be set for SSL requests. <br>
+ * The requirements of the Servlet specs are:
+ * <ul>
+ * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
+ * String (since Servlet Spec 3.0).</li>
+ * <li> an attribute named "javax.servlet.request.cipher_suite" of type
+ * String.</li>
+ * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
+ * <li> an attribute named "javax.servlet.request.X509Certificate" of type
+ * java.security.cert.X509Certificate[]. This is an array of objects of type
+ * X509Certificate, the order of this array is defined as being in ascending
+ * order of trust. The first certificate in the chain is the one set by the
+ * client, the next is the one used to authenticate the first, and so on.
+ * </li>
+ * </ul>
+ *
+ * @param request
+ * HttpRequest to be customised.
+ */
+ public void customize(SSLEngine sslEngine, Request request)
+ {
+ request.setScheme(HttpScheme.HTTPS.asString());
+ SSLSession sslSession = sslEngine.getSession();
+
+ 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=new Integer(SslContextFactory.deduceKeyLength(cipherSuite));
+ certs=SslContextFactory.getCertChain(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);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(Log.EXCEPTION,e);
+ }
+ }
+
+ @Override
+ 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 final X509Certificate[] _certs;
+ private final Integer _keySize;
+ private final String _idStr;
+
+ CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
+ {
+ this._keySize=keySize;
+ this._certs=certs;
+ this._idStr=idStr;
+ }
+
+ X509Certificate[] getCerts()
+ {
+ return _certs;
+ }
+
+ Integer getKeySize()
+ {
+ return _keySize;
+ }
+
+ String getIdStr()
+ {
+ return _idStr;
+ }
+ }
+
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
+
+/* ------------------------------------------------------------ */
+/** Jetty HTTP Servlet Server.
+ * This class is the main class for the Jetty HTTP Servlet server.
+ * It aggregates Connectors (HTTP request receivers) and request Handlers.
+ * The server is itself a handler and a ThreadPool. Connectors use the ThreadPool methods
+ * to run jobs that will eventually call the handle method.
+ */
+@ManagedObject(value="Jetty HTTP Servlet server")
+public class Server extends HandlerWrapper implements Attributes
+{
+ private static final Logger LOG = Log.getLogger(Server.class);
+
+ private final AttributesMap _attributes = new AttributesMap();
+ private final ThreadPool _threadPool;
+ private final List<Connector> _connectors = new CopyOnWriteArrayList<>();
+ private SessionIdManager _sessionIdManager;
+ private boolean _stopAtShutdown;
+ private boolean _dumpAfterStart=false;
+ private boolean _dumpBeforeStop=false;
+
+ private volatile DateField _dateField;
+
+ /* ------------------------------------------------------------ */
+ public Server()
+ {
+ this((ThreadPool)null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience constructor
+ * Creates server and a {@link ServerConnector} at the passed port.
+ * @param port The port of a network HTTP connector (or 0 for a randomly allocated port).
+ * @see NetworkConnector#getLocalPort()
+ */
+ public Server(@Name("port")int port)
+ {
+ this((ThreadPool)null);
+ ServerConnector connector=new ServerConnector(this);
+ connector.setPort(port);
+ setConnectors(new Connector[]{connector});
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience constructor
+ * Creates server and a {@link ServerConnector} at the passed address.
+ */
+ public Server(@Name("address")InetSocketAddress addr)
+ {
+ this((ThreadPool)null);
+ ServerConnector connector=new ServerConnector(this);
+ connector.setHost(addr.getHostName());
+ connector.setPort(addr.getPort());
+ setConnectors(new Connector[]{connector});
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ public Server(@Name("threadpool") ThreadPool pool)
+ {
+ _threadPool=pool!=null?pool:new QueuedThreadPool();
+ addBean(_threadPool);
+ setServer(this);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute("version of this server")
+ public static String getVersion()
+ {
+ return Jetty.VERSION;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getStopAtShutdown()
+ {
+ return _stopAtShutdown;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setStopAtShutdown(boolean stop)
+ {
+ //if we now want to stop
+ if (stop)
+ {
+ //and we weren't stopping before
+ if (!_stopAtShutdown)
+ {
+ //only register to stop if we're already started (otherwise we'll do it in doStart())
+ if (isStarted())
+ ShutdownThread.register(this);
+ }
+ }
+ else
+ ShutdownThread.deregister(this);
+
+ _stopAtShutdown=stop;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the connectors.
+ */
+ @ManagedAttribute(value="connectors for this server", readonly=true)
+ public Connector[] getConnectors()
+ {
+ List<Connector> connectors = new ArrayList<>(_connectors);
+ return connectors.toArray(new Connector[connectors.size()]);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addConnector(Connector connector)
+ {
+ if (connector.getServer() != this)
+ throw new IllegalArgumentException("Connector " + connector +
+ " cannot be shared among server " + connector.getServer() + " and server " + this);
+ if (_connectors.add(connector))
+ addBean(connector);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Convenience method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to
+ * remove a connector.
+ * @param connector The connector to remove.
+ */
+ public void removeConnector(Connector connector)
+ {
+ if (_connectors.remove(connector))
+ removeBean(connector);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the connectors for this server.
+ * Each connector has this server set as it's ThreadPool and its Handler.
+ * @param connectors The connectors to set.
+ */
+ public void setConnectors(Connector[] connectors)
+ {
+ if (connectors != null)
+ {
+ for (Connector connector : connectors)
+ {
+ if (connector.getServer() != this)
+ throw new IllegalArgumentException("Connector " + connector +
+ " cannot be shared among server " + connector.getServer() + " and server " + this);
+ }
+ }
+
+ Connector[] oldConnectors = getConnectors();
+ updateBeans(oldConnectors, connectors);
+ _connectors.removeAll(Arrays.asList(oldConnectors));
+ if (connectors != null)
+ _connectors.addAll(Arrays.asList(connectors));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the threadPool.
+ */
+ @ManagedAttribute("the server thread pool")
+ public ThreadPool getThreadPool()
+ {
+ return _threadPool;
+ }
+
+ /**
+ * @return true if {@link #dumpStdErr()} is called after starting
+ */
+ @ManagedAttribute("dump state to stderr after start")
+ public boolean isDumpAfterStart()
+ {
+ return _dumpAfterStart;
+ }
+
+ /**
+ * @param dumpAfterStart true if {@link #dumpStdErr()} is called after starting
+ */
+ public void setDumpAfterStart(boolean dumpAfterStart)
+ {
+ _dumpAfterStart = dumpAfterStart;
+ }
+
+ /**
+ * @return true if {@link #dumpStdErr()} is called before stopping
+ */
+ @ManagedAttribute("dump state to stderr before stop")
+ public boolean isDumpBeforeStop()
+ {
+ return _dumpBeforeStop;
+ }
+
+ /**
+ * @param dumpBeforeStop true if {@link #dumpStdErr()} is called before stopping
+ */
+ public void setDumpBeforeStop(boolean dumpBeforeStop)
+ {
+ _dumpBeforeStop = dumpBeforeStop;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HttpField getDateField()
+ {
+ long now=System.currentTimeMillis();
+ long seconds = now/1000;
+ DateField df = _dateField;
+
+ if (df==null || df._seconds!=seconds)
+ {
+ synchronized (this) // Trade some contention for less garbage
+ {
+ df = _dateField;
+ if (df==null || df._seconds!=seconds)
+ {
+ HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.DATE,DateGenerator.formatDate(now));
+ _dateField=new DateField(seconds,field);
+ return field;
+ }
+ }
+ }
+ return df._dateField;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (getStopAtShutdown())
+ {
+ ShutdownThread.register(this);
+ }
+
+ ShutdownMonitor.getInstance().start(); // initialize
+
+ LOG.info("jetty-" + getVersion());
+ HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
+ MultiException mex=new MultiException();
+
+ // check size of thread pool
+ SizedThreadPool pool = getBean(SizedThreadPool.class);
+ int max=pool==null?-1:pool.getMaxThreads();
+ int needed=1;
+ if (mex.size()==0)
+ {
+ for (Connector connector : _connectors)
+ {
+ if (connector instanceof AbstractConnector)
+ needed+=((AbstractConnector)connector).getAcceptors();
+ if (connector instanceof ServerConnector)
+ needed+=((ServerConnector)connector).getSelectorManager().getSelectorCount();
+ }
+ }
+
+ if (max>0 && needed>max)
+ throw new IllegalStateException("Insufficient max threads in ThreadPool: max="+max+" < needed="+needed);
+
+ try
+ {
+ super.doStart();
+ }
+ catch(Throwable e)
+ {
+ mex.add(e);
+ }
+
+ // start connectors last
+ for (Connector connector : _connectors)
+ {
+ try
+ {
+ connector.start();
+ }
+ catch(Throwable e)
+ {
+ mex.add(e);
+ }
+ }
+
+ if (isDumpAfterStart())
+ dumpStdErr();
+
+ mex.ifExceptionThrow();
+
+ LOG.info(String.format("Started @%dms",ManagementFactory.getRuntimeMXBean().getUptime()));
+ }
+
+ @Override
+ protected void start(LifeCycle l) throws Exception
+ {
+ // start connectors last
+ if (!(l instanceof Connector))
+ super.start(l);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ if (isDumpBeforeStop())
+ dumpStdErr();
+
+ MultiException mex=new MultiException();
+
+ // list if graceful futures
+ List<Future<Void>> futures = new ArrayList<>();
+
+ // First close the network connectors to stop accepting new connections
+ for (Connector connector : _connectors)
+ futures.add(connector.shutdown());
+
+ // Then tell the contexts that we are shutting down
+
+ Handler[] gracefuls = getChildHandlersByClass(Graceful.class);
+ for (Handler graceful : gracefuls)
+ futures.add(((Graceful)graceful).shutdown());
+
+ // Shall we gracefully wait for zero connections?
+ long stopTimeout = getStopTimeout();
+ if (stopTimeout>0)
+ {
+ long stop_by=System.currentTimeMillis()+stopTimeout;
+ LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
+
+ // Wait for shutdowns
+ for (Future<Void> future: futures)
+ {
+ try
+ {
+ if (!future.isDone())
+ future.get(Math.max(1L,stop_by-System.currentTimeMillis()),TimeUnit.MILLISECONDS);
+ }
+ catch (Exception e)
+ {
+ mex.add(e.getCause());
+ }
+ }
+ }
+
+ // Cancel any shutdowns not done
+ for (Future<Void> future: futures)
+ if (!future.isDone())
+ future.cancel(true);
+
+ // Now stop the connectors (this will close existing connections)
+ for (Connector connector : _connectors)
+ {
+ try
+ {
+ connector.stop();
+ }
+ catch (Throwable e)
+ {
+ mex.add(e);
+ }
+ }
+
+ // And finally stop everything else
+ try
+ {
+ super.doStop();
+ }
+ catch (Throwable e)
+ {
+ mex.add(e);
+ }
+
+ if (getStopAtShutdown())
+ ShutdownThread.deregister(this);
+
+ mex.ifExceptionThrow();
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Handle a request from a connection.
+ * Called to handle a request on the connection when either the header has been received,
+ * or after the entire request has been received (for short requests of known length), or
+ * on the dispatch of an async request.
+ */
+ public void handle(HttpChannel<?> connection) throws IOException, ServletException
+ {
+ final String target=connection.getRequest().getPathInfo();
+ final Request request=connection.getRequest();
+ final Response response=connection.getResponse();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
+
+ if ("*".equals(target))
+ {
+ handleOptions(request,response);
+ if (!request.isHandled())
+ handle(target, request, request, response);
+ }
+ else
+ handle(target, request, request, response);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus()+" handled="+request.isHandled());
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Handle Options request to server
+ */
+ protected void handleOptions(Request request,Response response) throws IOException
+ {
+ if (!HttpMethod.OPTIONS.is(request.getMethod()))
+ response.sendError(HttpStatus.BAD_REQUEST_400);
+ request.setHandled(true);
+ response.setStatus(200);
+ response.getHttpFields().put(HttpHeader.ALLOW,"GET,POST,HEAD,OPTIONS");
+ response.setContentLength(0);
+ response.closeOutput();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Handle a request from a connection.
+ * Called to handle a request on the connection when either the header has been received,
+ * or after the entire request has been received (for short requests of known length), or
+ * on the dispatch of an async request.
+ */
+ public void handleAsync(HttpChannel<?> connection) throws IOException, ServletException
+ {
+ final HttpChannelState state = connection.getRequest().getHttpChannelState();
+ final AsyncContextEvent event = state.getAsyncContextEvent();
+
+ final Request baseRequest=connection.getRequest();
+ final String path=event.getPath();
+
+ if (path!=null)
+ {
+ // this is a dispatch with a path
+ ServletContext context=event.getServletContext();
+ HttpURI uri = new HttpURI(context==null?path:URIUtil.addPaths(context.getContextPath(),path));
+ baseRequest.setUri(uri);
+ baseRequest.setRequestURI(null);
+ baseRequest.setPathInfo(baseRequest.getRequestURI());
+ if (uri.getQuery()!=null)
+ baseRequest.mergeQueryParameters(uri.getQuery(), true); //we have to assume dispatch path and query are UTF8
+ }
+
+ final String target=baseRequest.getPathInfo();
+ final HttpServletRequest request=(HttpServletRequest)event.getSuppliedRequest();
+ final HttpServletResponse response=(HttpServletResponse)event.getSuppliedResponse();
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
+ handle(target, baseRequest, request, response);
+ LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus());
+ }
+ else
+ handle(target, baseRequest, request, response);
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public void join() throws InterruptedException
+ {
+ getThreadPool().join();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionIdManager.
+ */
+ public SessionIdManager getSessionIdManager()
+ {
+ return _sessionIdManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionIdManager The sessionIdManager to set.
+ */
+ public void setSessionIdManager(SessionIdManager sessionIdManager)
+ {
+ updateBean(_sessionIdManager,sessionIdManager);
+ _sessionIdManager=sessionIdManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#clearAttributes()
+ */
+ @Override
+ public void clearAttributes()
+ {
+ Enumeration<String> names = _attributes.getAttributeNames();
+ while (names.hasMoreElements())
+ removeBean(_attributes.getAttribute(names.nextElement()));
+ _attributes.clearAttributes();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
+ */
+ @Override
+ public Object getAttribute(String name)
+ {
+ return _attributes.getAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#getAttributeNames()
+ */
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ return AttributesMap.getAttributeNamesCopy(_attributes);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
+ */
+ @Override
+ public void removeAttribute(String name)
+ {
+ Object bean=_attributes.getAttribute(name);
+ if (bean!=null)
+ removeBean(bean);
+ _attributes.removeAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public void setAttribute(String name, Object attribute)
+ {
+ addBean(attribute);
+ _attributes.setAttribute(name, attribute);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The URI of the first {@link NetworkConnector} and first {@link ContextHandler}, or null
+ */
+ public URI getURI()
+ {
+ NetworkConnector connector=null;
+ for (Connector c: _connectors)
+ {
+ if (c instanceof NetworkConnector)
+ {
+ connector=(NetworkConnector)c;
+ break;
+ }
+ }
+
+ if (connector==null)
+ return null;
+
+ ContextHandler context = getChildHandlerByClass(ContextHandler.class);
+
+ try
+ {
+ String scheme=connector.getDefaultConnectionFactory().getProtocol().startsWith("SSL-")?"https":"http";
+
+ String host=connector.getHost();
+ if (context!=null && context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+ host=context.getVirtualHosts()[0];
+ if (host==null)
+ host=InetAddress.getLocalHost().getHostAddress();
+
+ String path=context==null?null:context.getContextPath();
+ if (path==null)
+ path="/";
+ return new URI(scheme,null,host,connector.getLocalPort(),path,null,null);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ return null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
+ }
+
+ @Override
+ public void dump(Appendable out,String indent) throws IOException
+ {
+ dumpBeans(out,indent,Collections.singleton(new ClassLoaderDump(this.getClass().getClassLoader())));
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void main(String...args) throws Exception
+ {
+ System.err.println(getVersion());
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class DateField
+ {
+ final long _seconds;
+ final HttpField _dateField;
+ public DateField(long seconds, HttpField dateField)
+ {
+ super();
+ _seconds = seconds;
+ _dateField = dateField;
+ }
+
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.channels.Channel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SelectChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.SelectorManager.ManagedSelector;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * This {@link Connector} implementation is the primary connector for the
+ * Jetty server over TCP/IP. By the use of various {@link ConnectionFactory} instances it is able
+ * to accept connections for HTTP, SPDY and WebSocket, either directly or over SSL.
+ * <p>
+ * The connector is a fully asynchronous NIO based implementation that by default will
+ * use all the commons services (eg {@link Executor}, {@link Scheduler}) of the
+ * passed {@link Server} instance, but all services may also be constructor injected
+ * into the connector so that it may operate with dedicated or otherwise shared services.
+ * <p>
+ * <h2>Connection Factories</h2>
+ * Various convenience constructors are provided to assist with common configurations of
+ * ConnectionFactories, whose generic use is described in {@link AbstractConnector}.
+ * If no connection factories are passed, then the connector will
+ * default to use a {@link HttpConnectionFactory}. If an non null {@link SslContextFactory}
+ * instance is passed, then this used to instantiate a {@link SslConnectionFactory} which is
+ * prepended to the other passed or default factories.
+ * <p>
+ * <h2>Selectors</h2>
+ * The connector will use the {@link Executor} service to execute a number of Selector Tasks,
+ * which are implemented to each use a NIO {@link Selector} instance to asynchronously
+ * schedule a set of accepted connections. It is the selector thread that will call the
+ * {@link Callback} instances passed in the {@link EndPoint#fillInterested(Callback)} or
+ * {@link EndPoint#write(Callback, java.nio.ByteBuffer...)} methods. It is expected
+ * that these callbacks may do some non-blocking IO work, but will always dispatch to the
+ * {@link Executor} service any blocking, long running or application tasks.
+ * <p>
+ * The default number of selectors is equal to the number of processors available to the JVM,
+ * which should allow optimal performance even if all the connections used are performing
+ * significant non-blocking work in the callback tasks.
+ *
+ */
+@ManagedObject("HTTP connector using NIO ByteChannels and Selectors")
+public class ServerConnector extends AbstractNetworkConnector
+{
+ private final SelectorManager _manager;
+ private volatile ServerSocketChannel _acceptChannel;
+ private volatile boolean _inheritChannel = false;
+ private volatile int _localPort = -1;
+ private volatile int _acceptQueueSize = 0;
+ private volatile boolean _reuseAddress = true;
+ private volatile int _lingerTime = -1;
+
+
+ /* ------------------------------------------------------------ */
+ /** HTTP Server Connection.
+ * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+ * @param server The {@link Server} this connector will accept connection for.
+ */
+ public ServerConnector(
+ @Name("server") Server server)
+ {
+ this(server,null,null,null,-1,-1,new HttpConnectionFactory());
+ }
+
+ /* ------------------------------------------------------------ */
+ /** HTTP Server Connection.
+ * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the only factory.</p>
+ * @param server The {@link Server} this connector will accept connection for.
+ * @param acceptors
+ * the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections. If 0, then
+ * the selector threads are used to accept connections.
+ * @param selectors
+ * the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
+ */
+ public ServerConnector(
+ @Name("server") Server server,
+ @Name("acceptors") int acceptors,
+ @Name("selectors") int selectors)
+ {
+ this(server,null,null,null,acceptors,selectors,new HttpConnectionFactory());
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Generic Server Connection with default configuration.
+ * <p>Construct a Server Connector with the passed Connection factories.</p>
+ * @param server The {@link Server} this connector will accept connection for.
+ * @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+ */
+ public ServerConnector(
+ @Name("server") Server server,
+ @Name("factories") ConnectionFactory... factories)
+ {
+ this(server,null,null,null,-1,-1,factories);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** HTTP Server Connection.
+ * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
+ * @param server The {@link Server} this connector will accept connection for.
+ * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the
+ * list of HTTP Connection Factory.
+ */
+ public ServerConnector(
+ @Name("server") Server server,
+ @Name("sslContextFactory") SslContextFactory sslContextFactory)
+ {
+ this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+ }
+
+ /* ------------------------------------------------------------ */
+ /** HTTP Server Connection.
+ * <p>Construct a ServerConnector with a private instance of {@link HttpConnectionFactory} as the primary protocol</p>.
+ * @param server The {@link Server} this connector will accept connection for.
+ * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the
+ * list of HTTP Connection Factory.
+ * @param acceptors
+ * the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections. If 0, then
+ * the selector threads are used to accept connections.
+ * @param selectors
+ * the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
+ */
+ public ServerConnector(
+ @Name("server") Server server,
+ @Name("acceptors") int acceptors,
+ @Name("selectors") int selectors,
+ @Name("sslContextFactory") SslContextFactory sslContextFactory)
+ {
+ this(server,null,null,null,acceptors,selectors,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Generic SSL Server Connection.
+ * @param server The {@link Server} this connector will accept connection for.
+ * @param sslContextFactory If non null, then a {@link SslConnectionFactory} is instantiated and prepended to the
+ * list of ConnectionFactories, with the first factory being the default protocol for the SslConnectionFactory.
+ * @param factories Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+ */
+ public ServerConnector(
+ @Name("server") Server server,
+ @Name("sslContextFactory") SslContextFactory sslContextFactory,
+ @Name("factories") ConnectionFactory... factories)
+ {
+ this(server,null,null,null,-1,-1,AbstractConnectionFactory.getFactories(sslContextFactory,factories));
+ }
+
+ /** Generic Server Connection.
+ * @param server
+ * The server this connector will be accept connection for.
+ * @param executor
+ * An executor used to run tasks for handling requests, acceptors and selectors. I
+ * If null then use the servers executor
+ * @param scheduler
+ * A scheduler used to schedule timeouts. If null then use the servers scheduler
+ * @param bufferPool
+ * A ByteBuffer pool used to allocate buffers. If null then create a private pool with default configuration.
+ * @param acceptors
+ * the number of acceptor threads to use, or -1 for a default value. Acceptors accept new TCP/IP connections. If 0, then
+ * the selector threads are used to accept connections.
+ * @param selectors
+ * the number of selector threads, or -1 for a default value. Selectors notice and schedule established connection that can make IO progress.
+ * @param factories
+ * Zero or more {@link ConnectionFactory} instances used to create and configure connections.
+ */
+ public ServerConnector(
+ @Name("server") Server server,
+ @Name("executor") Executor executor,
+ @Name("scheduler") Scheduler scheduler,
+ @Name("bufferPool") ByteBufferPool bufferPool,
+ @Name("acceptors") int acceptors,
+ @Name("selectors") int selectors,
+ @Name("factories") ConnectionFactory... factories)
+ {
+ super(server,executor,scheduler,bufferPool,acceptors,factories);
+ _manager = new ServerConnectorManager(getExecutor(), getScheduler(), selectors > 0 ? selectors : Runtime.getRuntime().availableProcessors());
+ addBean(_manager, true);
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+
+ if (getAcceptors()==0)
+ {
+ _acceptChannel.configureBlocking(false);
+ _manager.acceptor(_acceptChannel);
+ }
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ ServerSocketChannel channel = _acceptChannel;
+ return channel!=null && channel.isOpen();
+ }
+
+ /**
+ * @return whether this connector uses a channel inherited from the JVM.
+ * @see System#inheritedChannel()
+ */
+ public boolean isInheritChannel()
+ {
+ return _inheritChannel;
+ }
+
+ /**
+ * <p>Sets whether this connector uses a channel inherited from the JVM.</p>
+ * <p>If true, the connector first tries to inherit from a channel provided by the system.
+ * If there is no inherited channel available, or if the inherited channel is not usable,
+ * then it will fall back using {@link ServerSocketChannel}.</p>
+ * <p>Use it with xinetd/inetd, to launch an instance of Jetty on demand. The port
+ * used to access pages on the Jetty instance is the same as the port used to
+ * launch Jetty.</p>
+ *
+ * @param inheritChannel whether this connector uses a channel inherited from the JVM.
+ */
+ public void setInheritChannel(boolean inheritChannel)
+ {
+ _inheritChannel = inheritChannel;
+ }
+
+ @Override
+ public void open() throws IOException
+ {
+ if (_acceptChannel == null)
+ {
+ ServerSocketChannel serverChannel = null;
+ if (isInheritChannel())
+ {
+ Channel channel = System.inheritedChannel();
+ if (channel instanceof ServerSocketChannel)
+ serverChannel = (ServerSocketChannel)channel;
+ else
+ LOG.warn("Unable to use System.inheritedChannel() [{}]. Trying a new ServerSocketChannel at {}:{}", channel, getHost(), getPort());
+ }
+
+ if (serverChannel == null)
+ {
+ serverChannel = ServerSocketChannel.open();
+
+ InetSocketAddress bindAddress = getHost() == null ? new InetSocketAddress(getPort()) : new InetSocketAddress(getHost(), getPort());
+ serverChannel.socket().bind(bindAddress, getAcceptQueueSize());
+ serverChannel.socket().setReuseAddress(getReuseAddress());
+
+ _localPort = serverChannel.socket().getLocalPort();
+ if (_localPort <= 0)
+ throw new IOException("Server channel not bound");
+
+ addBean(serverChannel);
+ }
+
+ serverChannel.configureBlocking(true);
+ addBean(serverChannel);
+
+ _acceptChannel = serverChannel;
+ }
+ }
+
+ @Override
+ public Future<Void> shutdown()
+ {
+ // TODO shutdown all the connections
+ return super.shutdown();
+ }
+
+ @Override
+ public void close()
+ {
+ ServerSocketChannel serverChannel = _acceptChannel;
+ _acceptChannel = null;
+
+ if (serverChannel != null)
+ {
+ removeBean(serverChannel);
+
+ // If the interrupt did not close it, we should close it
+ if (serverChannel.isOpen())
+ {
+ try
+ {
+ serverChannel.close();
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+ // super.close();
+ _localPort = -2;
+ }
+
+ @Override
+ public void accept(int acceptorID) throws IOException
+ {
+ ServerSocketChannel serverChannel = _acceptChannel;
+ if (serverChannel != null && serverChannel.isOpen())
+ {
+ SocketChannel channel = serverChannel.accept();
+ accepted(channel);
+ }
+ }
+
+ private void accepted(SocketChannel channel) throws IOException
+ {
+ channel.configureBlocking(false);
+ Socket socket = channel.socket();
+ configure(socket);
+ _manager.accept(channel);
+ }
+
+ protected void configure(Socket socket)
+ {
+ try
+ {
+ socket.setTcpNoDelay(true);
+ if (_lingerTime >= 0)
+ socket.setSoLinger(true, _lingerTime / 1000);
+ else
+ socket.setSoLinger(false, 0);
+ }
+ catch (SocketException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ public SelectorManager getSelectorManager()
+ {
+ return _manager;
+ }
+
+ @Override
+ public Object getTransport()
+ {
+ return _acceptChannel;
+ }
+
+ @Override
+ @ManagedAttribute("local port")
+ public int getLocalPort()
+ {
+ return _localPort;
+ }
+
+ protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ {
+ return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout());
+ }
+
+ /**
+ * @return the linger time
+ * @see Socket#getSoLinger()
+ */
+ @ManagedAttribute("TCP/IP solinger time or -1 to disable")
+ public int getSoLingerTime()
+ {
+ return _lingerTime;
+ }
+
+ /**
+ * @param lingerTime the linger time. Use -1 to disable.
+ * @see Socket#setSoLinger(boolean, int)
+ */
+ public void setSoLingerTime(int lingerTime)
+ {
+ _lingerTime = lingerTime;
+ }
+
+ /**
+ * @return the accept queue size
+ */
+ @ManagedAttribute("Accept Queue size")
+ public int getAcceptQueueSize()
+ {
+ return _acceptQueueSize;
+ }
+
+ /**
+ * @param acceptQueueSize the accept queue size (also known as accept backlog)
+ */
+ public void setAcceptQueueSize(int acceptQueueSize)
+ {
+ _acceptQueueSize = acceptQueueSize;
+ }
+
+ /**
+ * @return whether the server socket reuses addresses
+ * @see ServerSocket#getReuseAddress()
+ */
+ public boolean getReuseAddress()
+ {
+ return _reuseAddress;
+ }
+
+ /**
+ * @param reuseAddress whether the server socket reuses addresses
+ * @see ServerSocket#setReuseAddress(boolean)
+ */
+ public void setReuseAddress(boolean reuseAddress)
+ {
+ _reuseAddress = reuseAddress;
+ }
+
+ private final class ServerConnectorManager extends SelectorManager
+ {
+ private ServerConnectorManager(Executor executor, Scheduler scheduler, int selectors)
+ {
+ super(executor, scheduler, selectors);
+ }
+
+ @Override
+ protected void accepted(SocketChannel channel) throws IOException
+ {
+ ServerConnector.this.accepted(channel);
+ }
+
+ @Override
+ protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+ {
+ return ServerConnector.this.newEndPoint(channel, selectSet, selectionKey);
+ }
+
+ @Override
+ public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
+ {
+ return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
+ }
+
+ @Override
+ protected void endPointOpened(EndPoint endpoint)
+ {
+ super.endPointOpened(endpoint);
+ onEndPointOpened(endpoint);
+ }
+
+ @Override
+ protected void endPointClosed(EndPoint endpoint)
+ {
+ onEndPointClosed(endpoint);
+ super.endPointClosed(endpoint);
+ }
+
+
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * ServletRequestHttpWrapper
+ *
+ * Class to tunnel a ServletRequest via a HttpServletRequest
+ */
+public class ServletRequestHttpWrapper extends ServletRequestWrapper implements HttpServletRequest
+{
+ public ServletRequestHttpWrapper(ServletRequest request)
+ {
+ super(request);
+ }
+
+ public String getAuthType()
+ {
+ return null;
+ }
+
+ public Cookie[] getCookies()
+ {
+ return null;
+ }
+
+ public long getDateHeader(String name)
+ {
+ return 0;
+ }
+
+ public String getHeader(String name)
+ {
+ return null;
+ }
+
+ public Enumeration getHeaders(String name)
+ {
+ return null;
+ }
+
+ public Enumeration getHeaderNames()
+ {
+ return null;
+ }
+
+ public int getIntHeader(String name)
+ {
+ return 0;
+ }
+
+ public String getMethod()
+ {
+ return null;
+ }
+
+ public String getPathInfo()
+ {
+ return null;
+ }
+
+ public String getPathTranslated()
+ {
+ return null;
+ }
+
+ public String getContextPath()
+ {
+ return null;
+ }
+
+ public String getQueryString()
+ {
+ return null;
+ }
+
+ public String getRemoteUser()
+ {
+ return null;
+ }
+
+ public boolean isUserInRole(String role)
+ {
+ return false;
+ }
+
+ public Principal getUserPrincipal()
+ {
+ return null;
+ }
+
+ public String getRequestedSessionId()
+ {
+ return null;
+ }
+
+ public String getRequestURI()
+ {
+ return null;
+ }
+
+ public StringBuffer getRequestURL()
+ {
+ return null;
+ }
+
+ public String getServletPath()
+ {
+ return null;
+ }
+
+ public HttpSession getSession(boolean create)
+ {
+ return null;
+ }
+
+ public HttpSession getSession()
+ {
+ return null;
+ }
+
+ public boolean isRequestedSessionIdValid()
+ {
+ return false;
+ }
+
+ public boolean isRequestedSessionIdFromCookie()
+ {
+ return false;
+ }
+
+ public boolean isRequestedSessionIdFromURL()
+ {
+ return false;
+ }
+
+ public boolean isRequestedSessionIdFromUrl()
+ {
+ return false;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#authenticate(javax.servlet.http.HttpServletResponse)
+ */
+ public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+ {
+ return false;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#getPart(java.lang.String)
+ */
+ public Part getPart(String name) throws IOException, ServletException
+ {
+ return null;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#getParts()
+ */
+ public Collection<Part> getParts() throws IOException, ServletException
+ {
+ return null;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#login(java.lang.String, java.lang.String)
+ */
+ public void login(String username, String password) throws ServletException
+ {
+
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#logout()
+ */
+ public void logout() throws ServletException
+ {
+
+ }
+
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#changeSessionId()
+ */
+ @Override
+ public String changeSessionId()
+ {
+ // TODO 3.1 Auto-generated method stub
+ return null;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletRequest#upgrade(java.lang.Class)
+ */
+ @Override
+ public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException
+ {
+ // TODO 3.1 Auto-generated method stub
+ return null;
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * ServletResponseHttpWrapper
+ *
+ * Wrapper to tunnel a ServletResponse via a HttpServletResponse
+ */
+public class ServletResponseHttpWrapper extends ServletResponseWrapper implements HttpServletResponse
+{
+ public ServletResponseHttpWrapper(ServletResponse response)
+ {
+ super(response);
+ }
+
+ public void addCookie(Cookie cookie)
+ {
+ }
+
+ public boolean containsHeader(String name)
+ {
+ return false;
+ }
+
+ public String encodeURL(String url)
+ {
+ return null;
+ }
+
+ public String encodeRedirectURL(String url)
+ {
+ return null;
+ }
+
+ public String encodeUrl(String url)
+ {
+ return null;
+ }
+
+ public String encodeRedirectUrl(String url)
+ {
+ return null;
+ }
+
+ public void sendError(int sc, String msg) throws IOException
+ {
+ }
+
+ public void sendError(int sc) throws IOException
+ {
+ }
+
+ public void sendRedirect(String location) throws IOException
+ {
+ }
+
+ public void setDateHeader(String name, long date)
+ {
+ }
+
+ public void addDateHeader(String name, long date)
+ {
+ }
+
+ public void setHeader(String name, String value)
+ {
+ }
+
+ public void addHeader(String name, String value)
+ {
+ }
+
+ public void setIntHeader(String name, int value)
+ {
+ }
+
+ public void addIntHeader(String name, int value)
+ {
+ }
+
+ public void setStatus(int sc)
+ {
+ }
+
+ public void setStatus(int sc, String sm)
+ {
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletResponse#getHeader(java.lang.String)
+ */
+ public String getHeader(String name)
+ {
+ return null;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletResponse#getHeaderNames()
+ */
+ public Collection<String> getHeaderNames()
+ {
+ return null;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletResponse#getHeaders(java.lang.String)
+ */
+ public Collection<String> getHeaders(String name)
+ {
+ return null;
+ }
+
+ /**
+ * @see javax.servlet.http.HttpServletResponse#getStatus()
+ */
+ public int getStatus()
+ {
+ return 0;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/** Session ID Manager.
+ * Manages session IDs across multiple contexts.
+ */
+public interface SessionIdManager extends LifeCycle
+{
+ /**
+ * @param id The session ID without any cluster node extension
+ * @return True if the session ID is in use by at least one context.
+ */
+ public boolean idInUse(String id);
+
+ /**
+ * Add a session to the list of known sessions for a given ID.
+ * @param session The session
+ */
+ public void addSession(HttpSession session);
+
+ /**
+ * Remove session from the list of known sessions for a given ID.
+ * @param session
+ */
+ public void removeSession(HttpSession session);
+
+ /**
+ * Call {@link HttpSession#invalidate()} on all known sessions for the given id.
+ * @param id The session ID without any cluster node extension
+ */
+ public void invalidateAll(String id);
+
+ /**
+ * @param request
+ * @param created
+ * @return the new session id
+ */
+ public String newSessionId(HttpServletRequest request,long created);
+
+
+
+ public String getWorkerName();
+
+
+ /* ------------------------------------------------------------ */
+ /** Get a cluster ID from a node ID.
+ * Strip node identifier from a located session ID.
+ * @param nodeId
+ * @return the cluster id
+ */
+ public String getClusterId(String nodeId);
+
+ /* ------------------------------------------------------------ */
+ /** Get a node ID from a cluster ID and a request
+ * @param clusterId The ID of the session
+ * @param request The request that for the session (or null)
+ * @return The session ID qualified with the node ID.
+ */
+ public String getNodeId(String clusterId,HttpServletRequest request);
+
+
+ /* ------------------------------------------------------------ */
+ /** Change the existing session id.
+ *
+ * @param oldClusterId
+ * @param oldNodeId
+ * @param request
+ */
+ public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.util.EventListener;
+import java.util.Set;
+
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* --------------------------------------------------------------------- */
+/**
+ * Session Manager.
+ * The API required to manage sessions for a servlet context.
+ *
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public interface SessionManager extends LifeCycle
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Session cookie name.
+ * Defaults to <code>JSESSIONID</code>, but can be set with the
+ * <code>org.eclipse.jetty.servlet.SessionCookie</code> context init parameter.
+ */
+ public final static String __SessionCookieProperty = "org.eclipse.jetty.servlet.SessionCookie";
+ public final static String __DefaultSessionCookie = "JSESSIONID";
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Session id path parameter name.
+ * Defaults to <code>jsessionid</code>, but can be set with the
+ * <code>org.eclipse.jetty.servlet.SessionIdPathParameterName</code> context init parameter.
+ * If set to null or "none" no URL rewriting will be done.
+ */
+ public final static String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.servlet.SessionIdPathParameterName";
+ public final static String __DefaultSessionIdPathParameterName = "jsessionid";
+ public final static String __CheckRemoteSessionEncoding = "org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding";
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Session Domain.
+ * If this property is set as a ServletContext InitParam, then it is
+ * used as the domain for session cookies. If it is not set, then
+ * no domain is specified for the session cookie.
+ */
+ public final static String __SessionDomainProperty = "org.eclipse.jetty.servlet.SessionDomain";
+ public final static String __DefaultSessionDomain = null;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Session Path.
+ * If this property is set as a ServletContext InitParam, then it is
+ * used as the path for the session cookie. If it is not set, then
+ * the context path is used as the path for the cookie.
+ */
+ public final static String __SessionPathProperty = "org.eclipse.jetty.servlet.SessionPath";
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Session Max Age.
+ * If this property is set as a ServletContext InitParam, then it is
+ * used as the max age for the session cookie. If it is not set, then
+ * a max age of -1 is used.
+ */
+ public final static String __MaxAgeProperty = "org.eclipse.jetty.servlet.MaxAge";
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the <code>HttpSession</code> with the given session id
+ *
+ * @param id the session id
+ * @return the <code>HttpSession</code> with the corresponding id or null if no session with the given id exists
+ */
+ public HttpSession getHttpSession(String id);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Creates a new <code>HttpSession</code>.
+ *
+ * @param request the HttpServletRequest containing the requested session id
+ * @return the new <code>HttpSession</code>
+ */
+ public HttpSession newHttpSession(HttpServletRequest request);
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if session cookies should be HTTP-only (Microsoft extension)
+ * @see org.eclipse.jetty.http.HttpCookie#isHttpOnly()
+ */
+ public boolean getHttpOnly();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the max period of inactivity, after which the session is invalidated, in seconds.
+ * @see #setMaxInactiveInterval(int)
+ */
+ public int getMaxInactiveInterval();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the max period of inactivity, after which the session is invalidated, in seconds.
+ *
+ * @param seconds the max inactivity period, in seconds.
+ * @see #getMaxInactiveInterval()
+ */
+ public void setMaxInactiveInterval(int seconds);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the {@link SessionHandler}.
+ *
+ * @param handler the <code>SessionHandler</code> object
+ */
+ public void setSessionHandler(SessionHandler handler);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Adds an event listener for session-related events.
+ *
+ * @param listener the session event listener to add
+ * Individual SessionManagers implementations may accept arbitrary listener types,
+ * but they are expected to at least handle HttpSessionActivationListener,
+ * HttpSessionAttributeListener, HttpSessionBindingListener and HttpSessionListener.
+ * @see #removeEventListener(EventListener)
+ */
+ public void addEventListener(EventListener listener);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Removes an event listener for for session-related events.
+ *
+ * @param listener the session event listener to remove
+ * @see #addEventListener(EventListener)
+ */
+ public void removeEventListener(EventListener listener);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Removes all event listeners for session-related events.
+ *
+ * @see #removeEventListener(EventListener)
+ */
+ public void clearEventListeners();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Gets a Cookie for a session.
+ *
+ * @param session the session to which the cookie should refer.
+ * @param contextPath the context to which the cookie should be linked.
+ * The client will only send the cookie value when requesting resources under this path.
+ * @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS).
+ * @return if this <code>SessionManager</code> uses cookies, then this method will return a new
+ * {@link Cookie cookie object} that should be set on the client in order to link future HTTP requests
+ * with the <code>session</code>. If cookies are not in use, this method returns <code>null</code>.
+ */
+ public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the cross context session id manager.
+ * @see #setSessionIdManager(SessionIdManager)
+ */
+ public SessionIdManager getSessionIdManager();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the cross context session id manager.
+ * @deprecated use {@link #getSessionIdManager()}
+ */
+ @Deprecated
+ public SessionIdManager getMetaManager();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the cross context session id manager
+ *
+ * @param idManager the cross context session id manager.
+ * @see #getSessionIdManager()
+ */
+ public void setSessionIdManager(SessionIdManager idManager);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param session the session to test for validity
+ * @return whether the given session is valid, that is, it has not been invalidated.
+ */
+ public boolean isValid(HttpSession session);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param session the session object
+ * @return the unique id of the session within the cluster, extended with an optional node id.
+ * @see #getClusterId(HttpSession)
+ */
+ public String getNodeId(HttpSession session);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param session the session object
+ * @return the unique id of the session within the cluster (without a node id extension)
+ * @see #getNodeId(HttpSession)
+ */
+ public String getClusterId(HttpSession session);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Called by the {@link SessionHandler} when a session is first accessed by a request.
+ *
+ * @param session the session object
+ * @param secure whether the request is secure or not
+ * @return the session cookie. If not null, this cookie should be set on the response to either migrate
+ * the session or to refresh a session cookie that may expire.
+ * @see #complete(HttpSession)
+ */
+ public HttpCookie access(HttpSession session, boolean secure);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Called by the {@link SessionHandler} when a session is last accessed by a request.
+ *
+ * @param session the session object
+ * @see #access(HttpSession, boolean)
+ */
+ public void complete(HttpSession session);
+
+ /**
+ * Sets the session id URL path parameter name.
+ *
+ * @param parameterName the URL path parameter name for session id URL rewriting (null or "none" for no rewriting).
+ * @see #getSessionIdPathParameterName()
+ * @see #getSessionIdPathParameterNamePrefix()
+ */
+ public void setSessionIdPathParameterName(String parameterName);
+
+ /**
+ * @return the URL path parameter name for session id URL rewriting, by default "jsessionid".
+ * @see #setSessionIdPathParameterName(String)
+ */
+ public String getSessionIdPathParameterName();
+
+ /**
+ * @return a formatted version of {@link #getSessionIdPathParameterName()}, by default
+ * ";" + sessionIdParameterName + "=", for easier lookup in URL strings.
+ * @see #getSessionIdPathParameterName()
+ */
+ public String getSessionIdPathParameterNamePrefix();
+
+ /**
+ * @return whether the session management is handled via cookies.
+ */
+ public boolean isUsingCookies();
+
+ /**
+ * @return whether the session management is handled via URLs.
+ */
+ public boolean isUsingURLs();
+
+ public Set<SessionTrackingMode> getDefaultSessionTrackingModes();
+
+ public Set<SessionTrackingMode> getEffectiveSessionTrackingModes();
+
+ public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes);
+
+ public SessionCookieConfig getSessionCookieConfig();
+
+ /**
+ * @return True if absolute URLs are check for remoteness before being session encoded.
+ */
+ public boolean isCheckingRemoteSessionIdEncoding();
+
+ /**
+ * @param remote True if absolute URLs are check for remoteness before being session encoded.
+ */
+ public void setCheckingRemoteSessionIdEncoding(boolean remote);
+
+ /* ------------------------------------------------------------ */
+ /** Change the existing session id.
+ *
+ * @param oldClusterId
+ * @param oldNodeId
+ * @param newClusterId
+ * @param newNodeId
+ */
+ public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId);
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.thread.ShutdownThread;
+
+/**
+ * Shutdown/Stop Monitor thread.
+ * <p>
+ * This thread listens on the port specified by the STOP.PORT system parameter (defaults to -1 for not listening) for request authenticated with the key given
+ * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
+ * <p>
+ * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
+ * <p>
+ * Commands "stop" and "status" are currently supported.
+ */
+public class ShutdownMonitor
+{
+ // Implementation of safe lazy init, using Initialization on Demand Holder technique.
+ static class Holder
+ {
+ static ShutdownMonitor instance = new ShutdownMonitor();
+ }
+
+ public static ShutdownMonitor getInstance()
+ {
+ return Holder.instance;
+ }
+
+ /**
+ * ShutdownMonitorThread
+ *
+ * Thread for listening to STOP.PORT for command to stop Jetty.
+ * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
+ * called after the stop.
+ *
+ */
+ public class ShutdownMonitorThread extends Thread
+ {
+
+ public ShutdownMonitorThread ()
+ {
+ setDaemon(true);
+ setName("ShutdownMonitor");
+ }
+
+ @Override
+ public void run()
+ {
+ if (serverSocket == null)
+ {
+ return;
+ }
+
+ while (serverSocket != null)
+ {
+ Socket socket = null;
+ try
+ {
+ socket = serverSocket.accept();
+
+ LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+ String receivedKey = lin.readLine();
+ if (!key.equals(receivedKey))
+ {
+ System.err.println("Ignoring command with incorrect key");
+ continue;
+ }
+
+ OutputStream out = socket.getOutputStream();
+
+ String cmd = lin.readLine();
+ debug("command=%s",cmd);
+ if ("stop".equals(cmd))
+ {
+ // Graceful Shutdown
+ debug("Issuing graceful shutdown..");
+ ShutdownThread.getInstance().run();
+
+ //Stop accepting any more
+ close(serverSocket);
+ serverSocket = null;
+
+ //Shutdown input from client
+ shutdownInput(socket);
+
+ // Reply to client
+ debug("Informing client that we are stopped.");
+ out.write("Stopped\r\n".getBytes(StandardCharsets.UTF_8));
+ out.flush();
+
+ // Shutdown Monitor
+ socket.shutdownOutput();
+ close(socket);
+ socket = null;
+ debug("Shutting down monitor");
+
+ if (exitVm)
+ {
+ // Kill JVM
+ debug("Killing JVM");
+ System.exit(0);
+ }
+ }
+ else if ("status".equals(cmd))
+ {
+ // Reply to client
+ out.write("OK\r\n".getBytes(StandardCharsets.UTF_8));
+ out.flush();
+ }
+ }
+ catch (Exception e)
+ {
+ debug(e);
+ System.err.println(e.toString());
+ }
+ finally
+ {
+ close(socket);
+ socket = null;
+ }
+ }
+ }
+
+ public void start()
+ {
+ if (isAlive())
+ {
+ // TODO why are we reentrant here?
+ if (DEBUG)
+ System.err.printf("ShutdownMonitorThread already started");
+ return; // cannot start it again
+ }
+
+ startListenSocket();
+
+ if (serverSocket == null)
+ {
+ return;
+ }
+ if (DEBUG)
+ System.err.println("Starting ShutdownMonitorThread");
+ super.start();
+ }
+
+ private void startListenSocket()
+ {
+ if (port < 0)
+ {
+ if (DEBUG)
+ System.err.println("ShutdownMonitor not in use (port < 0): " + port);
+ return;
+ }
+
+ try
+ {
+ serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
+ if (port == 0)
+ {
+ // server assigned port in use
+ port = serverSocket.getLocalPort();
+ System.out.printf("STOP.PORT=%d%n",port);
+ }
+
+ if (key == null)
+ {
+ // create random key
+ key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
+ System.out.printf("STOP.KEY=%s%n",key);
+ }
+ }
+ catch (Exception e)
+ {
+ debug(e);
+ System.err.println("Error binding monitor port " + port + ": " + e.toString());
+ serverSocket = null;
+ }
+ finally
+ {
+ // establish the port and key that are in use
+ debug("STOP.PORT=%d",port);
+ debug("STOP.KEY=%s",key);
+ debug("%s",serverSocket);
+ }
+ }
+
+ }
+
+ private boolean DEBUG;
+ private int port;
+ private String key;
+ private boolean exitVm;
+ private ServerSocket serverSocket;
+ private ShutdownMonitorThread thread;
+
+
+
+ /**
+ * Create a ShutdownMonitor using configuration from the System properties.
+ * <p>
+ * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
+ * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
+ * <p>
+ * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call.
+ */
+ private ShutdownMonitor()
+ {
+ Properties props = System.getProperties();
+
+ this.DEBUG = props.containsKey("DEBUG");
+
+ // Use values passed thru via /jetty-start/
+ this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
+ this.key = props.getProperty("STOP.KEY",null);
+ this.exitVm = true;
+ }
+
+ private void close(ServerSocket server)
+ {
+ if (server == null)
+ {
+ return;
+ }
+
+ try
+ {
+ server.close();
+ }
+ catch (IOException ignore)
+ {
+ debug(ignore);
+ }
+ }
+
+ private void close(Socket socket)
+ {
+ if (socket == null)
+ {
+ return;
+ }
+
+ try
+ {
+ socket.close();
+ }
+ catch (IOException ignore)
+ {
+ debug(ignore);
+ }
+ }
+
+
+ private void shutdownInput(Socket socket)
+ {
+ if (socket == null)
+ return;
+
+ try
+ {
+ socket.shutdownInput();
+ }
+ catch (IOException ignore)
+ {
+ debug(ignore);
+ }
+ }
+
+
+ private void debug(String format, Object... args)
+ {
+ if (DEBUG)
+ {
+ System.err.printf("[ShutdownMonitor] " + format + "%n",args);
+ }
+ }
+
+ private void debug(Throwable t)
+ {
+ if (DEBUG)
+ {
+ t.printStackTrace(System.err);
+ }
+ }
+
+ public String getKey()
+ {
+ return key;
+ }
+
+ public int getPort()
+ {
+ return port;
+ }
+
+ public ServerSocket getServerSocket()
+ {
+ return serverSocket;
+ }
+
+ public boolean isExitVm()
+ {
+ return exitVm;
+ }
+
+
+ public void setDebug(boolean flag)
+ {
+ this.DEBUG = flag;
+ }
+
+ public void setExitVm(boolean exitVm)
+ {
+ synchronized (this)
+ {
+ if (thread != null && thread.isAlive())
+ {
+ throw new IllegalStateException("ShutdownMonitorThread already started");
+ }
+ this.exitVm = exitVm;
+ }
+ }
+
+ public void setKey(String key)
+ {
+ synchronized (this)
+ {
+ if (thread != null && thread.isAlive())
+ {
+ throw new IllegalStateException("ShutdownMonitorThread already started");
+ }
+ this.key = key;
+ }
+ }
+
+ public void setPort(int port)
+ {
+ synchronized (this)
+ {
+ if (thread != null && thread.isAlive())
+ {
+ throw new IllegalStateException("ShutdownMonitorThread already started");
+ }
+ this.port = port;
+ }
+ }
+
+ protected void start() throws Exception
+ {
+ ShutdownMonitorThread t = null;
+ synchronized (this)
+ {
+ if (thread != null && thread.isAlive())
+ {
+ // TODO why are we reentrant here?
+ if (DEBUG)
+ System.err.printf("ShutdownMonitorThread already started");
+ return; // cannot start it again
+ }
+
+ thread = new ShutdownMonitorThread();
+ t = thread;
+ }
+
+ if (t != null)
+ t.start();
+ }
+
+
+ protected boolean isAlive ()
+ {
+ boolean result = false;
+ synchronized (this)
+ {
+ result = (thread != null && thread.isAlive());
+ }
+ return result;
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[port=%d]",this.getClass().getName(),port);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.SslConnection;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class SslConnectionFactory extends AbstractConnectionFactory
+{
+ private final SslContextFactory _sslContextFactory;
+ private final String _nextProtocol;
+
+ public SslConnectionFactory()
+ {
+ this(HttpVersion.HTTP_1_1.asString());
+ }
+
+ public SslConnectionFactory(@Name("next") String nextProtocol)
+ {
+ this(null,nextProtocol);
+ }
+
+ public SslConnectionFactory(@Name("sslContextFactory") SslContextFactory factory, @Name("next") String nextProtocol)
+ {
+ super("SSL-"+nextProtocol);
+ _sslContextFactory=factory==null?new SslContextFactory():factory;
+ _nextProtocol=nextProtocol;
+ addBean(_sslContextFactory);
+ }
+
+ public SslContextFactory getSslContextFactory()
+ {
+ return _sslContextFactory;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+
+ SSLEngine engine = _sslContextFactory.newSSLEngine();
+ engine.setUseClientMode(false);
+ SSLSession session=engine.getSession();
+
+ if (session.getPacketBufferSize()>getInputBufferSize())
+ setInputBufferSize(session.getPacketBufferSize());
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ SSLEngine engine = _sslContextFactory.newSSLEngine(endPoint.getRemoteAddress());
+ engine.setUseClientMode(false);
+
+ SslConnection sslConnection = newSslConnection(connector, endPoint, engine);
+ sslConnection.setRenegotiationAllowed(_sslContextFactory.isRenegotiationAllowed());
+ configure(sslConnection, connector, endPoint);
+
+ ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
+ EndPoint decryptedEndPoint = sslConnection.getDecryptedEndPoint();
+ Connection connection = next.newConnection(connector, decryptedEndPoint);
+ decryptedEndPoint.setConnection(connection);
+
+ return sslConnection;
+ }
+
+ protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
+ {
+ return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.security.Principal;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+
+/* ------------------------------------------------------------ */
+/** User object that encapsulates user identity and operations such as run-as-role actions,
+ * checking isUserInRole and getUserPrincipal.
+ *
+ * Implementations of UserIdentity should be immutable so that they may be
+ * cached by Authenticators and LoginServices.
+ *
+ */
+public interface UserIdentity
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The user subject
+ */
+ Subject getSubject();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The user principal
+ */
+ Principal getUserPrincipal();
+
+ /* ------------------------------------------------------------ */
+ /** Check if the user is in a role.
+ * This call is used to satisfy authorization calls from
+ * container code which will be using translated role names.
+ * @param role A role name.
+ * @param scope
+ * @return True if the user can act in that role.
+ */
+ boolean isUserInRole(String role, Scope scope);
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * A UserIdentity Scope.
+ * A scope is the environment in which a User Identity is to
+ * be interpreted. Typically it is set by the target servlet of
+ * a request.
+ */
+ interface Scope
+ {
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The context path that the identity is being considered within
+ */
+ String getContextPath();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The name of the identity context. Typically this is the servlet name.
+ */
+ String getName();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return A map of role reference names that converts from names used by application code
+ * to names used by the context deployment.
+ */
+ Map<String,String> getRoleRefMap();
+ }
+
+ /* ------------------------------------------------------------ */
+ public interface UnauthenticatedUserIdentity extends UserIdentity
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public static final UserIdentity UNAUTHENTICATED_IDENTITY = new UnauthenticatedUserIdentity()
+ {
+ public Subject getSubject()
+ {
+ return null;
+ }
+
+ public Principal getUserPrincipal()
+ {
+ return null;
+ }
+
+ public boolean isUserInRole(String role, Scope scope)
+ {
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "UNAUTHENTICATED";
+ }
+ };
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server;
+
+import java.io.IOException;
+
+/** OutputWriter.
+ * A writer that can wrap a {@link HttpOutput} stream and provide
+ * character encodings.
+ *
+ * The UTF-8 encoding is done by this class and no additional
+ * buffers or Writers are used.
+ * The UTF-8 code was inspired by http://javolution.org
+ */
+public class Utf8HttpWriter extends HttpWriter
+{
+ int _surrogate=0;
+
+ /* ------------------------------------------------------------ */
+ public Utf8HttpWriter(HttpOutput out)
+ {
+ super(out);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (char[] s,int offset, int length) throws IOException
+ {
+ HttpOutput out = _out;
+ if (length==0 && out.isAllContentWritten())
+ {
+ close();
+ return;
+ }
+
+ while (length > 0)
+ {
+ _bytes.reset();
+ int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+
+ byte[] buffer=_bytes.getBuf();
+ int bytes=_bytes.getCount();
+
+ if (bytes+chars>buffer.length)
+ chars=buffer.length-bytes;
+
+ for (int i = 0; i < chars; i++)
+ {
+ int code = s[offset+i];
+
+ // Do we already have a surrogate?
+ if(_surrogate==0)
+ {
+ // No - is this char code a surrogate?
+ if(Character.isHighSurrogate((char)code))
+ {
+ _surrogate=code; // UCS-?
+ continue;
+ }
+ }
+ // else handle a low surrogate
+ else if(Character.isLowSurrogate((char)code))
+ {
+ code = Character.toCodePoint((char)_surrogate, (char)code); // UCS-4
+ }
+ // else UCS-2
+ else
+ {
+ code=_surrogate; // UCS-2
+ _surrogate=0; // USED
+ i--;
+ }
+
+ if ((code & 0xffffff80) == 0)
+ {
+ // 1b
+ if (bytes>=buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(code);
+ }
+ else
+ {
+ if((code&0xfffff800)==0)
+ {
+ // 2b
+ if (bytes+2>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xc0|(code>>6));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+ }
+ else if((code&0xffff0000)==0)
+ {
+ // 3b
+ if (bytes+3>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xe0|(code>>12));
+ buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+ }
+ else if((code&0xff200000)==0)
+ {
+ // 4b
+ if (bytes+4>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xf0|(code>>18));
+ buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+ }
+ else if((code&0xf4000000)==0)
+ {
+ // 5b
+ if (bytes+5>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xf8|(code>>24));
+ buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+ }
+ else if((code&0x80000000)==0)
+ {
+ // 6b
+ if (bytes+6>buffer.length)
+ {
+ chars=i;
+ break;
+ }
+ buffer[bytes++]=(byte)(0xfc|(code>>30));
+ buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+ buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+ buffer[bytes++]=(byte)(0x80|(code&0x3f));
+ }
+ else
+ {
+ buffer[bytes++]=(byte)('?');
+ }
+
+ _surrogate=0; // USED
+
+ if (bytes==buffer.length)
+ {
+ chars=i+1;
+ break;
+ }
+ }
+ }
+ _bytes.setCount(bytes);
+
+ _bytes.writeTo(out);
+ length-=chars;
+ offset+=chars;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+
+import java.io.IOException;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** AbstractHandler.
+ */
+@ManagedObject("Jetty Handler")
+public abstract class AbstractHandler extends ContainerLifeCycle implements Handler
+{
+ private static final Logger LOG = Log.getLogger(AbstractHandler.class);
+
+ private Server _server;
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public AbstractHandler()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.LifeCycle#start()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ LOG.debug("starting {}",this);
+ if (_server==null)
+ LOG.warn("No Server set for {}",this);
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.LifeCycle#stop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ LOG.debug("stopping {}",this);
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setServer(Server server)
+ {
+ if (_server==server)
+ return;
+ if (isStarted())
+ throw new IllegalStateException(STARTED);
+ _server=server;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Server getServer()
+ {
+ return _server;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void destroy()
+ {
+ if (!isStopped())
+ throw new IllegalStateException("!STOPPED");
+ super.destroy();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void dumpThis(Appendable out) throws IOException
+ {
+ out.append(toString()).append(" - ").append(getState()).append('\n');
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Handler Container.
+ * This is the base class for handlers that may contain other handlers.
+ *
+ */
+public abstract class AbstractHandlerContainer extends AbstractHandler implements HandlerContainer
+{
+ /* ------------------------------------------------------------ */
+ public AbstractHandlerContainer()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Handler[] getChildHandlers()
+ {
+ List<Handler> list=new ArrayList<>();
+ expandChildren(list,null);
+ return list.toArray(new Handler[list.size()]);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Handler[] getChildHandlersByClass(Class<?> byclass)
+ {
+ List<Handler> list=new ArrayList<>();
+ expandChildren(list,byclass);
+ return list.toArray(new Handler[list.size()]);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public <T extends Handler> T getChildHandlerByClass(Class<T> byclass)
+ {
+ List<Handler> list=new ArrayList<>();
+ expandChildren(list,byclass);
+ if (list.isEmpty())
+ return null;
+ return (T)list.get(0);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void expandChildren(List<Handler> list, Class<?> byClass)
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void expandHandler(Handler handler, List<Handler> list, Class<?> byClass)
+ {
+ if (handler==null)
+ return;
+
+ if (byClass==null || byClass.isAssignableFrom(handler.getClass()))
+ list.add(handler);
+
+ if (handler instanceof AbstractHandlerContainer)
+ ((AbstractHandlerContainer)handler).expandChildren(list, byClass);
+ else if (handler instanceof HandlerContainer)
+ {
+ HandlerContainer container = (HandlerContainer)handler;
+ Handler[] handlers=byClass==null?container.getChildHandlers():container.getChildHandlersByClass(byClass);
+ list.addAll(Arrays.asList(handlers));
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static <T extends HandlerContainer> T findContainerOf(HandlerContainer root,Class<T>type, Handler handler)
+ {
+ if (root==null || handler==null)
+ return null;
+
+ Handler[] branches=root.getChildHandlersByClass(type);
+ if (branches!=null)
+ {
+ for (Handler h:branches)
+ {
+ T container = (T)h;
+ Handler[] candidates = container.getChildHandlersByClass(handler.getClass());
+ if (candidates!=null)
+ {
+ for (Handler c:candidates)
+ if (c==handler)
+ return container;
+ }
+ }
+ }
+ return null;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.File;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jetty.server.handler.ContextHandler.AliasCheck;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Symbolic Link AliasChecker.
+ * <p>An instance of this class can be registered with {@link ContextHandler#addAliasCheck(AliasCheck)}
+ * to check resources that are aliased to other locations. The checker uses the
+ * Java {@link Files#readSymbolicLink(Path)} and {@link Path#toRealPath(java.nio.file.LinkOption...)}
+ * APIs to check if a file is aliased with symbolic links.</p>
+ */
+public class AllowSymLinkAliasChecker implements AliasCheck
+{
+ private static final Logger LOG = Log.getLogger(AllowSymLinkAliasChecker.class);
+
+ @Override
+ public boolean check(String path, Resource resource)
+ {
+ try
+ {
+ File file =resource.getFile();
+ if (file==null)
+ return false;
+
+ // If the file exists
+ if (file.exists())
+ {
+ // we can use the real path method to check the symlinks resolve to the alias
+ URI real = file.toPath().toRealPath().toUri();
+ if (real.equals(resource.getAlias()))
+ {
+ LOG.debug("Allow symlink {} --> {}",resource,real);
+ return true;
+ }
+ }
+ else
+ {
+ // file does not exists, so we have to walk the path and links ourselves.
+ Path p = file.toPath().toAbsolutePath();
+ File d = p.getRoot().toFile();
+ for (Path e:p)
+ {
+ d=new File(d,e.toString());
+
+ while (d.exists() && Files.isSymbolicLink(d.toPath()))
+ {
+ Path link=Files.readSymbolicLink(d.toPath());
+ if (!link.isAbsolute())
+ link=link.resolve(d.toPath());
+ d=link.toFile().getAbsoluteFile().getCanonicalFile();
+ }
+ }
+ if (resource.getAlias().equals(d.toURI()))
+ {
+ LOG.debug("Allow symlink {} --> {}",resource,d);
+ return true;
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ LOG.ignore(e);
+ }
+ return false;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Future;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.FilterRegistration.Dynamic;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.ClassLoaderDump;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/**
+ * ContextHandler.
+ *
+ * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
+ *
+ * <p>
+ * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as
+ * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
+ * <p>
+ * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys
+ * and org.eclipse.jetty.server.Request.maxFormContentSize. These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
+ * <p>
+ * This servers executore is made available via a context attributed "org.eclipse.jetty.server.Executor".
+ *
+ * @org.apache.xbean.XBean description="Creates a basic HTTP context"
+ */
+@ManagedObject("URI Context")
+public class ContextHandler extends ScopedHandler implements Attributes, Graceful
+{
+ public final static int SERVLET_MAJOR_VERSION=3;
+ public final static int SERVLET_MINOR_VERSION=0;
+ public static final Class[] SERVLET_LISTENER_TYPES = new Class[] {ServletContextListener.class,
+ ServletContextAttributeListener.class,
+ ServletRequestListener.class,
+ ServletRequestAttributeListener.class};
+
+ public static final int DEFAULT_LISTENER_TYPE_INDEX = 1;
+ public static final int EXTENDED_LISTENER_TYPE_INDEX = 0;
+
+
+ final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
+
+ private static final Logger LOG = Log.getLogger(ContextHandler.class);
+
+ private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
+
+ /**
+ * If a context attribute with this name is set, it is interpreted as a comma separated list of attribute name. Any other context attributes that are set
+ * with a name from this list will result in a call to {@link #setManagedAttribute(String, Object)}, which typically initiates the creation of a JMX MBean
+ * for the attribute value.
+ */
+ public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the current ServletContext implementation.
+ *
+ * @return ServletContext implementation
+ */
+ public static Context getCurrentContext()
+ {
+ return __context.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static ContextHandler getContextHandler(ServletContext context)
+ {
+ if(context instanceof ContextHandler.Context)
+ return ((ContextHandler.Context)context).getContextHandler();
+ Context c= getCurrentContext();
+ if (c!=null)
+ return c.getContextHandler();
+ return null;
+ }
+
+
+ protected Context _scontext;
+ private final AttributesMap _attributes;
+ private final Map<String, String> _initParams;
+ private ClassLoader _classLoader;
+ private String _contextPath = "/";
+
+ private String _displayName;
+
+ private Resource _baseResource;
+ private MimeTypes _mimeTypes;
+ private Map<String, String> _localeEncodingMap;
+ private String[] _welcomeFiles;
+ private ErrorHandler _errorHandler;
+ private String[] _vhosts;
+
+ private Logger _logger;
+ private boolean _allowNullPathInfo;
+ private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue();
+ private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue();
+ private boolean _compactPath = false;
+
+ private final List<EventListener> _eventListeners=new CopyOnWriteArrayList<>();
+ private final List<EventListener> _programmaticListeners=new CopyOnWriteArrayList<>();
+ private final List<ServletContextListener> _contextListeners=new CopyOnWriteArrayList<>();
+ private final List<ServletContextAttributeListener> _contextAttributeListeners=new CopyOnWriteArrayList<>();
+ private final List<ServletRequestListener> _requestListeners=new CopyOnWriteArrayList<>();
+ private final List<ServletRequestAttributeListener> _requestAttributeListeners=new CopyOnWriteArrayList<>();
+ private final List<EventListener> _durableListeners = new CopyOnWriteArrayList<>();
+ private Map<String, Object> _managedAttributes;
+ private String[] _protectedTargets;
+ private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
+
+ public enum Availability { UNAVAILABLE,STARTING,AVAILABLE,SHUTDOWN,};
+ private volatile Availability _availability;
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ContextHandler()
+ {
+ super();
+ _scontext = new Context();
+ _attributes = new AttributesMap();
+ _initParams = new HashMap<String, String>();
+ addAliasCheck(new ApproveNonExistentDirectoryAliases());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ protected ContextHandler(Context context)
+ {
+ super();
+ _scontext = context;
+ _attributes = new AttributesMap();
+ _initParams = new HashMap<String, String>();
+ addAliasCheck(new ApproveNonExistentDirectoryAliases());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ContextHandler(String contextPath)
+ {
+ this();
+ setContextPath(contextPath);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public ContextHandler(HandlerContainer parent, String contextPath)
+ {
+ this();
+ setContextPath(contextPath);
+ if (parent instanceof HandlerWrapper)
+ ((HandlerWrapper)parent).setHandler(this);
+ else if (parent instanceof HandlerCollection)
+ ((HandlerCollection)parent).addHandler(this);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ dumpBeans(out,indent,
+ Collections.singletonList(new ClassLoaderDump(getClassLoader())),
+ _initParams.entrySet(),
+ _attributes.getAttributeEntrySet(),
+ _scontext.getAttributeEntrySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ public Context getServletContext()
+ {
+ return _scontext;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the allowNullPathInfo true if /context is not redirected to /context/
+ */
+ @ManagedAttribute("Checks if the /context is not redirected to /context/")
+ public boolean getAllowNullPathInfo()
+ {
+ return _allowNullPathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param allowNullPathInfo
+ * true if /context is not redirected to /context/
+ */
+ public void setAllowNullPathInfo(boolean allowNullPathInfo)
+ {
+ _allowNullPathInfo = allowNullPathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setServer(Server server)
+ {
+ super.setServer(server);
+ if (_errorHandler != null)
+ _errorHandler.setServer(server);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
+ * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
+ * matching virtual host name.
+ *
+ * @param vhosts
+ * Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+ * String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. Hosts may start with '@', in which case they
+ * will match the {@link Connector#getName()} for the request.
+ */
+ public void setVirtualHosts(String[] vhosts)
+ {
+ if (vhosts == null)
+ {
+ _vhosts = vhosts;
+ }
+ else
+ {
+ _vhosts = new String[vhosts.length];
+ for (int i = 0; i < vhosts.length; i++)
+ _vhosts[i] = normalizeHostname(vhosts[i]);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Either set virtual hosts or add to an existing set of virtual hosts.
+ *
+ * @param virtualHosts
+ * Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+ * String representation of IP addresses. Host names may start with '*.' to wildcard one level of names. Host names may start with '@', in which case they
+ * will match the {@link Connector#getName()} for the request.
+ */
+ public void addVirtualHosts(String[] virtualHosts)
+ {
+ if (virtualHosts == null) // since this is add, we don't null the old ones
+ {
+ return;
+ }
+ else
+ {
+ List<String> currentVirtualHosts = null;
+ if (_vhosts != null)
+ {
+ currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
+ }
+ else
+ {
+ currentVirtualHosts = new ArrayList<String>();
+ }
+
+ for (int i = 0; i < virtualHosts.length; i++)
+ {
+ String normVhost = normalizeHostname(virtualHosts[i]);
+ if (!currentVirtualHosts.contains(normVhost))
+ {
+ currentVirtualHosts.add(normVhost);
+ }
+ }
+ _vhosts = currentVirtualHosts.toArray(new String[0]);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
+ *
+ * @param virtualHosts
+ * Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+ * String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+ */
+ public void removeVirtualHosts(String[] virtualHosts)
+ {
+ if (virtualHosts == null)
+ {
+ return; // do nothing
+ }
+ else if ( _vhosts == null || _vhosts.length == 0)
+ {
+ return; // do nothing
+ }
+ else
+ {
+ List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
+
+ for (int i = 0; i < virtualHosts.length; i++)
+ {
+ String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
+ if (existingVirtualHosts.contains(toRemoveVirtualHost))
+ {
+ existingVirtualHosts.remove(toRemoveVirtualHost);
+ }
+ }
+
+ if (existingVirtualHosts.isEmpty())
+ {
+ _vhosts = null; // if we ended up removing them all, just null out _vhosts
+ }
+ else
+ {
+ _vhosts = existingVirtualHosts.toArray(new String[0]);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
+ * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
+ * matching virtual host name.
+ *
+ * @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String
+ * representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+ */
+ @ManagedAttribute(value="Virtual hosts accepted by the context", readonly=true)
+ public String[] getVirtualHosts()
+ {
+ return _vhosts;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+ */
+ @Override
+ public Object getAttribute(String name)
+ {
+ return _attributes.getAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getAttributeNames()
+ */
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ return AttributesMap.getAttributeNamesCopy(_attributes);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the attributes.
+ */
+ public Attributes getAttributes()
+ {
+ return _attributes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the classLoader.
+ */
+ public ClassLoader getClassLoader()
+ {
+ return _classLoader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Make best effort to extract a file classpath from the context classloader
+ *
+ * @return Returns the classLoader.
+ */
+ @ManagedAttribute("The file classpath")
+ public String getClassPath()
+ {
+ if (_classLoader == null || !(_classLoader instanceof URLClassLoader))
+ return null;
+ URLClassLoader loader = (URLClassLoader)_classLoader;
+ URL[] urls = loader.getURLs();
+ StringBuilder classpath = new StringBuilder();
+ for (int i = 0; i < urls.length; i++)
+ {
+ try
+ {
+ Resource resource = newResource(urls[i]);
+ File file = resource.getFile();
+ if (file != null && file.exists())
+ {
+ if (classpath.length() > 0)
+ classpath.append(File.pathSeparatorChar);
+ classpath.append(file.getAbsolutePath());
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.debug(e);
+ }
+ }
+ if (classpath.length() == 0)
+ return null;
+ return classpath.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the _contextPath.
+ */
+ @ManagedAttribute("True if URLs are compacted to replace the multiple '/'s with a single '/'")
+ public String getContextPath()
+ {
+ return _contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+ */
+ public String getInitParameter(String name)
+ {
+ return _initParams.get(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public String setInitParameter(String name, String value)
+ {
+ return _initParams.put(name,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getInitParameterNames()
+ */
+ @SuppressWarnings("rawtypes")
+ public Enumeration getInitParameterNames()
+ {
+ return Collections.enumeration(_initParams.keySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the initParams.
+ */
+ @ManagedAttribute("Initial Parameter map for the context")
+ public Map<String, String> getInitParams()
+ {
+ return _initParams;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getServletContextName()
+ */
+ @ManagedAttribute(value="Display name of the Context", readonly=true)
+ public String getDisplayName()
+ {
+ return _displayName;
+ }
+
+ /* ------------------------------------------------------------ */
+ public EventListener[] getEventListeners()
+ {
+ return _eventListeners.toArray(new EventListener[_eventListeners.size()]);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the context event listeners.
+ *
+ * @param eventListeners
+ * the event listeners
+ * @see ServletContextListener
+ * @see ServletContextAttributeListener
+ * @see ServletRequestListener
+ * @see ServletRequestAttributeListener
+ */
+ public void setEventListeners(EventListener[] eventListeners)
+ {
+ _contextListeners.clear();
+ _contextAttributeListeners.clear();
+ _requestListeners.clear();
+ _requestAttributeListeners.clear();
+ _eventListeners.clear();
+
+ if (eventListeners!=null)
+ for (EventListener listener : eventListeners)
+ addEventListener(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add a context event listeners.
+ *
+ * @see ServletContextListener
+ * @see ServletContextAttributeListener
+ * @see ServletRequestListener
+ * @see ServletRequestAttributeListener
+ */
+ public void addEventListener(EventListener listener)
+ {
+ _eventListeners.add(listener);
+
+ if (!(isStarted() || isStarting()))
+ _durableListeners.add(listener);
+
+ if (listener instanceof ServletContextListener)
+ _contextListeners.add((ServletContextListener)listener);
+
+ if (listener instanceof ServletContextAttributeListener)
+ _contextAttributeListeners.add((ServletContextAttributeListener)listener);
+
+ if (listener instanceof ServletRequestListener)
+ _requestListeners.add((ServletRequestListener)listener);
+
+ if (listener instanceof ServletRequestAttributeListener)
+ _requestAttributeListeners.add((ServletRequestAttributeListener)listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Remove a context event listeners.
+ *
+ * @see ServletContextListener
+ * @see ServletContextAttributeListener
+ * @see ServletRequestListener
+ * @see ServletRequestAttributeListener
+ */
+ public void removeEventListener(EventListener listener)
+ {
+ _eventListeners.remove(listener);
+
+ if (listener instanceof ServletContextListener)
+ _contextListeners.remove(listener);
+
+ if (listener instanceof ServletContextAttributeListener)
+ _contextAttributeListeners.remove(listener);
+
+ if (listener instanceof ServletRequestListener)
+ _requestListeners.remove(listener);
+
+ if (listener instanceof ServletRequestAttributeListener)
+ _requestAttributeListeners.remove(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Apply any necessary restrictions on a programmatic added listener.
+ *
+ * @param listener
+ */
+ protected void addProgrammaticListener (EventListener listener)
+ {
+ _programmaticListeners.add(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected boolean isProgrammaticListener(EventListener listener)
+ {
+ return _programmaticListeners.contains(listener);
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if this context is accepting new requests
+ */
+ @ManagedAttribute("true for graceful shutdown, which allows existing requests to complete")
+ public boolean isShutdown()
+ {
+ switch(_availability)
+ {
+ case SHUTDOWN:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
+ * requests can complete, but no new requests are accepted.
+ *
+ */
+ @Override
+ public Future<Void> shutdown()
+ {
+ _availability = isRunning() ? Availability.SHUTDOWN : Availability.UNAVAILABLE;
+ return new FutureCallback(true);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return false if this context is unavailable (sends 503)
+ */
+ public boolean isAvailable()
+ {
+ return _availability==Availability.AVAILABLE;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set Available status.
+ */
+ public void setAvailable(boolean available)
+ {
+ synchronized (this)
+ {
+ if (available && isRunning())
+ _availability = Availability.AVAILABLE;
+ else if (!available || !isRunning())
+ _availability = Availability.UNAVAILABLE;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Logger getLogger()
+ {
+ return _logger;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setLogger(Logger logger)
+ {
+ _logger = logger;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ _availability = Availability.STARTING;
+
+ if (_contextPath == null)
+ throw new IllegalStateException("Null contextPath");
+
+ _logger = Log.getLogger(getDisplayName() == null?getContextPath():getDisplayName());
+ ClassLoader old_classloader = null;
+ Thread current_thread = null;
+ Context old_context = null;
+
+ _attributes.setAttribute("org.eclipse.jetty.server.Executor",getServer().getThreadPool());
+
+ try
+ {
+ // Set the classloader
+ if (_classLoader != null)
+ {
+ current_thread = Thread.currentThread();
+ old_classloader = current_thread.getContextClassLoader();
+ current_thread.setContextClassLoader(_classLoader);
+ }
+
+ if (_mimeTypes == null)
+ _mimeTypes = new MimeTypes();
+
+ old_context = __context.get();
+ __context.set(_scontext);
+
+ // defers the calling of super.doStart()
+ startContext();
+
+ _availability = Availability.AVAILABLE;
+ LOG.info("Started {}", this);
+ }
+ finally
+ {
+ __context.set(old_context);
+
+ // reset the classloader
+ if (_classLoader != null && current_thread!=null)
+ current_thread.setContextClassLoader(old_classloader);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to
+ * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
+ *
+ * @see org.eclipse.jetty.server.handler.ContextHandler.Context
+ */
+ protected void startContext() throws Exception
+ {
+ String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
+ if (managedAttributes != null)
+ {
+ _managedAttributes = new HashMap<String, Object>();
+ String[] attributes = managedAttributes.split(",");
+ for (String attribute : attributes)
+ _managedAttributes.put(attribute,null);
+
+ Enumeration<String> e = _scontext.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ String name = e.nextElement();
+ Object value = _scontext.getAttribute(name);
+ checkManagedAttribute(name,value);
+ }
+ }
+
+ super.doStart();
+
+ // Call context listeners
+ if (!_contextListeners.isEmpty())
+ {
+ ServletContextEvent event = new ServletContextEvent(_scontext);
+ for (ServletContextListener listener:_contextListeners)
+ callContextInitialized(listener, event);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void callContextInitialized (ServletContextListener l, ServletContextEvent e)
+ {
+ LOG.debug("contextInitialized: {}->{}",e,l);
+ l.contextInitialized(e);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void callContextDestroyed (ServletContextListener l, ServletContextEvent e)
+ {
+ LOG.debug("contextDestroyed: {}->{}",e,l);
+ l.contextDestroyed(e);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ _availability = Availability.UNAVAILABLE;
+
+ ClassLoader old_classloader = null;
+ Thread current_thread = null;
+
+ Context old_context = __context.get();
+ __context.set(_scontext);
+ try
+ {
+ // Set the classloader
+ if (_classLoader != null)
+ {
+ current_thread = Thread.currentThread();
+ old_classloader = current_thread.getContextClassLoader();
+ current_thread.setContextClassLoader(_classLoader);
+ }
+
+ super.doStop();
+
+ // Context listeners
+ if (!_contextListeners.isEmpty())
+ {
+ ServletContextEvent event = new ServletContextEvent(_scontext);
+ for (int i = _contextListeners.size(); i-->0;)
+ callContextDestroyed(_contextListeners.get(i),event);
+ }
+
+ //retain only durable listeners
+ setEventListeners(_durableListeners.toArray(new EventListener[_durableListeners.size()]));
+ _durableListeners.clear();
+
+ if (_errorHandler != null)
+ _errorHandler.stop();
+
+ Enumeration<String> e = _scontext.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ String name = e.nextElement();
+ checkManagedAttribute(name,null);
+ }
+
+ for (EventListener l : _programmaticListeners)
+ removeEventListener(l);
+ _programmaticListeners.clear();
+ }
+ finally
+ {
+ LOG.info("Stopped {}", this);
+ __context.set(old_context);
+ // reset the classloader
+ if (_classLoader != null && current_thread!=null)
+ current_thread.setContextClassLoader(old_classloader);
+ }
+
+ _scontext.clearAttributes();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException
+ {
+ DispatcherType dispatch = baseRequest.getDispatcherType();
+
+ switch (_availability)
+ {
+ case SHUTDOWN:
+ case UNAVAILABLE:
+ baseRequest.setHandled(true);
+ response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ return false;
+ default:
+ if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
+ return false;
+ }
+
+ // Check the vhosts
+ if (_vhosts != null && _vhosts.length > 0)
+ {
+ String vhost = normalizeHostname(baseRequest.getServerName());
+
+ boolean match = false;
+ boolean connectorName = false;
+ boolean connectorMatch = false;
+
+ for (String contextVhost:_vhosts)
+ {
+ if (contextVhost == null || contextVhost.length()==0)
+ continue;
+ char c=contextVhost.charAt(0);
+ switch (c)
+ {
+ case '*':
+ if (contextVhost.startsWith("*."))
+ // wildcard only at the beginning, and only for one additional subdomain level
+ match = match || contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
+ break;
+ case '@':
+ connectorName=true;
+ String name=baseRequest.getHttpChannel().getConnector().getName();
+ boolean m=name!=null && contextVhost.length()==name.length()+1 && contextVhost.endsWith(name);
+ match = match || m;
+ connectorMatch = connectorMatch || m;
+ break;
+ default:
+ match = match || contextVhost.equalsIgnoreCase(vhost);
+ }
+
+ }
+ if (!match || connectorName && !connectorMatch)
+ return false;
+ }
+
+ // Are we not the root context?
+ if (_contextPath.length() > 1)
+ {
+ // reject requests that are not for us
+ if (!target.startsWith(_contextPath))
+ return false;
+ if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/')
+ return false;
+
+ // redirect null path infos
+ if (!_allowNullPathInfo && _contextPath.length() == target.length())
+ {
+ // context request must end with /
+ baseRequest.setHandled(true);
+ if (baseRequest.getQueryString() != null)
+ response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
+ else
+ response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.handler.ScopedHandler#doScope(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this);
+
+ Context old_context = null;
+ String old_context_path = null;
+ String old_servlet_path = null;
+ String old_path_info = null;
+ ClassLoader old_classloader = null;
+ Thread current_thread = null;
+ String pathInfo = target;
+
+ DispatcherType dispatch = baseRequest.getDispatcherType();
+
+ old_context = baseRequest.getContext();
+
+ // Are we already in this context?
+ if (old_context != _scontext)
+ {
+ // check the target.
+ if (DispatcherType.REQUEST.equals(dispatch) ||
+ DispatcherType.ASYNC.equals(dispatch) ||
+ DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isAsync())
+ {
+ if (_compactPath)
+ target = URIUtil.compactPath(target);
+ if (!checkContext(target,baseRequest,response))
+ return;
+
+ if (target.length() > _contextPath.length())
+ {
+ if (_contextPath.length() > 1)
+ target = target.substring(_contextPath.length());
+ pathInfo = target;
+ }
+ else if (_contextPath.length() == 1)
+ {
+ target = URIUtil.SLASH;
+ pathInfo = URIUtil.SLASH;
+ }
+ else
+ {
+ target = URIUtil.SLASH;
+ pathInfo = null;
+ }
+ }
+
+ // Set the classloader
+ if (_classLoader != null)
+ {
+ current_thread = Thread.currentThread();
+ old_classloader = current_thread.getContextClassLoader();
+ current_thread.setContextClassLoader(_classLoader);
+ }
+ }
+
+ try
+ {
+ old_context_path = baseRequest.getContextPath();
+ old_servlet_path = baseRequest.getServletPath();
+ old_path_info = baseRequest.getPathInfo();
+
+ // Update the paths
+ baseRequest.setContext(_scontext);
+ __context.set(_scontext);
+ if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
+ {
+ if (_contextPath.length() == 1)
+ baseRequest.setContextPath("");
+ else
+ baseRequest.setContextPath(_contextPath);
+ baseRequest.setServletPath(null);
+ baseRequest.setPathInfo(pathInfo);
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
+
+ // start manual inline of nextScope(target,baseRequest,request,response);
+ if (never())
+ nextScope(target,baseRequest,request,response);
+ else if (_nextScope != null)
+ _nextScope.doScope(target,baseRequest,request,response);
+ else if (_outerScope != null)
+ _outerScope.doHandle(target,baseRequest,request,response);
+ else
+ doHandle(target,baseRequest,request,response);
+ // end manual inline (pathentic attempt to reduce stack depth)
+ }
+ finally
+ {
+ if (old_context != _scontext)
+ {
+ // reset the classloader
+ if (_classLoader != null && current_thread!=null)
+ {
+ current_thread.setContextClassLoader(old_classloader);
+ }
+
+ // reset the context and servlet path.
+ baseRequest.setContext(old_context);
+ __context.set(old_context);
+ baseRequest.setContextPath(old_context_path);
+ baseRequest.setServletPath(old_servlet_path);
+ baseRequest.setPathInfo(old_path_info);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ final DispatcherType dispatch = baseRequest.getDispatcherType();
+ final boolean new_context = baseRequest.takeNewContext();
+ try
+ {
+ if (new_context)
+ {
+ // Handle the REALLY SILLY request events!
+ if (!_requestAttributeListeners.isEmpty())
+ for (ServletRequestAttributeListener l :_requestAttributeListeners)
+ baseRequest.addEventListener(l);
+
+ if (!_requestListeners.isEmpty())
+ {
+ final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
+ for (ServletRequestListener l : _requestListeners)
+ l.requestInitialized(sre);
+ }
+ }
+
+ if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
+ {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ baseRequest.setHandled(true);
+ return;
+ }
+
+ // start manual inline of nextHandle(target,baseRequest,request,response);
+ // noinspection ConstantIfStatement
+ if (never())
+ nextHandle(target,baseRequest,request,response);
+ else if (_nextScope != null && _nextScope == _handler)
+ _nextScope.doHandle(target,baseRequest,request,response);
+ else if (_handler != null)
+ _handler.handle(target,baseRequest,request,response);
+ // end manual inline
+ }
+ finally
+ {
+ // Handle more REALLY SILLY request events!
+ if (new_context)
+ {
+ if (!_requestListeners.isEmpty())
+ {
+ final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
+ for (int i=_requestListeners.size();i-->0;)
+ _requestListeners.get(i).requestDestroyed(sre);
+ }
+
+ if (!_requestAttributeListeners.isEmpty())
+ {
+ ListIterator<ServletRequestAttributeListener> iter = _requestAttributeListeners.listIterator(_requestAttributeListeners.size());
+ for (int i=_requestAttributeListeners.size();i-->0;)
+ baseRequest.removeEventListener(_requestAttributeListeners.get(i));
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Handle a runnable in this context
+ */
+ public void handle(Runnable runnable)
+ {
+ ClassLoader old_classloader = null;
+ Thread current_thread = null;
+ Context old_context = null;
+ try
+ {
+ old_context = __context.get();
+ __context.set(_scontext);
+
+ // Set the classloader
+ if (_classLoader != null)
+ {
+ current_thread = Thread.currentThread();
+ old_classloader = current_thread.getContextClassLoader();
+ current_thread.setContextClassLoader(_classLoader);
+ }
+
+ runnable.run();
+ }
+ finally
+ {
+ __context.set(old_context);
+ if (old_classloader != null && current_thread!=null)
+ {
+ current_thread.setContextClassLoader(old_classloader);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
+ * the target is protected, 404 is returned.
+ */
+ /* ------------------------------------------------------------ */
+ public boolean isProtectedTarget(String target)
+ {
+ if (target == null || _protectedTargets == null)
+ return false;
+
+ while (target.startsWith("//"))
+ target=URIUtil.compactPath(target);
+
+ for (int i=0; i<_protectedTargets.length; i++)
+ {
+ String t=_protectedTargets[i];
+ if (StringUtil.startsWithIgnoreCase(target,t))
+ {
+ if (target.length()==t.length())
+ return true;
+
+ // Check that the target prefix really is a path segment, thus
+ // it can end with /, a query, a target or a parameter
+ char c=target.charAt(t.length());
+ if (c=='/'||c=='?'||c=='#'||c==';')
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param targets Array of URL prefix. Each prefix is in the form /path and will match
+ * either /path exactly or /path/anything
+ */
+ public void setProtectedTargets (String[] targets)
+ {
+ if (targets == null)
+ {
+ _protectedTargets = null;
+ return;
+ }
+
+ _protectedTargets = Arrays.copyOf(targets, targets.length);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String[] getProtectedTargets()
+ {
+ if (_protectedTargets == null)
+ return null;
+
+ return Arrays.copyOf(_protectedTargets, _protectedTargets.length);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+ */
+ @Override
+ public void removeAttribute(String name)
+ {
+ checkManagedAttribute(name,null);
+ _attributes.removeAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of
+ * a context. No attribute listener events are triggered by this API.
+ *
+ * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public void setAttribute( String name, Object value)
+ {
+ checkManagedAttribute(name,value);
+ _attributes.setAttribute(name,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param attributes
+ * The attributes to set.
+ */
+ public void setAttributes(Attributes attributes)
+ {
+ _attributes.clearAttributes();
+ _attributes.addAll(attributes);
+ Enumeration<String> e = _attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ String name = e.nextElement();
+ checkManagedAttribute(name,attributes.getAttribute(name));
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void clearAttributes()
+ {
+ Enumeration<String> e = _attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ String name = e.nextElement();
+ checkManagedAttribute(name,null);
+ }
+ _attributes.clearAttributes();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void checkManagedAttribute(String name, Object value)
+ {
+ if (_managedAttributes != null && _managedAttributes.containsKey(name))
+ {
+ setManagedAttribute(name,value);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setManagedAttribute(String name, Object value)
+ {
+ Object old = _managedAttributes.put(name,value);
+ updateBean(old,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param classLoader
+ * The classLoader to set.
+ */
+ public void setClassLoader(ClassLoader classLoader)
+ {
+ _classLoader = classLoader;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param contextPath
+ * The _contextPath to set.
+ */
+ public void setContextPath(String contextPath)
+ {
+ if (contextPath == null)
+ throw new IllegalArgumentException("null contextPath");
+
+ if (contextPath.endsWith("/*"))
+ {
+ LOG.warn(this+" contextPath ends with /*");
+ contextPath=contextPath.substring(0,contextPath.length()-2);
+ }
+ else if (contextPath.length()>1 && contextPath.endsWith("/"))
+ {
+ LOG.warn(this+" contextPath ends with /");
+ contextPath=contextPath.substring(0,contextPath.length()-1);
+ }
+
+ if (contextPath.length()==0)
+ {
+ LOG.warn("Empty contextPath");
+ contextPath="/";
+ }
+
+ _contextPath = contextPath;
+
+ if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
+ {
+ Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class);
+ for (int h = 0; contextCollections != null && h < contextCollections.length; h++)
+ ((ContextHandlerCollection)contextCollections[h]).mapContexts();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletContextName
+ * The servletContextName to set.
+ */
+ public void setDisplayName(String servletContextName)
+ {
+ _displayName = servletContextName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the resourceBase.
+ */
+ public Resource getBaseResource()
+ {
+ if (_baseResource == null)
+ return null;
+ return _baseResource;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the base resource as a string.
+ */
+ @ManagedAttribute("document root for context")
+ public String getResourceBase()
+ {
+ if (_baseResource == null)
+ return null;
+ return _baseResource.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the base resource for this context.
+ * @param base The resource used as the base for all static content of this context.
+ * @see #setResourceBase(String)
+ */
+ public void setBaseResource(Resource base)
+ {
+ _baseResource = base;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the base resource for this context.
+ * @param resourceBase A string representing the base resource for the context. Any string accepted
+ * by {@link Resource#newResource(String)} may be passed and the call is equivalent to
+ * <code>setBaseResource(newResource(resourceBase));</code>
+ */
+ public void setResourceBase(String resourceBase)
+ {
+ try
+ {
+ setBaseResource(newResource(resourceBase));
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ throw new IllegalArgumentException(resourceBase);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the mimeTypes.
+ */
+ public MimeTypes getMimeTypes()
+ {
+ if (_mimeTypes == null)
+ _mimeTypes = new MimeTypes();
+ return _mimeTypes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param mimeTypes
+ * The mimeTypes to set.
+ */
+ public void setMimeTypes(MimeTypes mimeTypes)
+ {
+ _mimeTypes = mimeTypes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public void setWelcomeFiles(String[] files)
+ {
+ _welcomeFiles = files;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The names of the files which the server should consider to be welcome files in this context.
+ * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
+ * @see #setWelcomeFiles
+ */
+ @ManagedAttribute(value="Partial URIs of directory welcome files", readonly=true)
+ public String[] getWelcomeFiles()
+ {
+ return _welcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the errorHandler.
+ */
+ @ManagedAttribute("The error handler to use for the context")
+ public ErrorHandler getErrorHandler()
+ {
+ return _errorHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param errorHandler
+ * The errorHandler to set.
+ */
+ public void setErrorHandler(ErrorHandler errorHandler)
+ {
+ if (errorHandler != null)
+ errorHandler.setServer(getServer());
+ updateBean(_errorHandler,errorHandler);
+ _errorHandler = errorHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute("The maximum content size")
+ public int getMaxFormContentSize()
+ {
+ return _maxFormContentSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the maximum size of a form post, to protect against DOS attacks from large forms.
+ * @param maxSize
+ */
+ public void setMaxFormContentSize(int maxSize)
+ {
+ _maxFormContentSize = maxSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getMaxFormKeys()
+ {
+ return _maxFormKeys;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
+ * @param max
+ */
+ public void setMaxFormKeys(int max)
+ {
+ _maxFormKeys = max;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if URLs are compacted to replace multiple '/'s with a single '/'
+ */
+ public boolean isCompactPath()
+ {
+ return _compactPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param compactPath
+ * True if URLs are compacted to replace multiple '/'s with a single '/'
+ */
+ public void setCompactPath(boolean compactPath)
+ {
+ _compactPath = compactPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ String[] vhosts = getVirtualHosts();
+
+ StringBuilder b = new StringBuilder();
+
+ Package pkg = getClass().getPackage();
+ if (pkg != null)
+ {
+ String p = pkg.getName();
+ if (p != null && p.length() > 0)
+ {
+ String[] ss = p.split("\\.");
+ for (String s : ss)
+ b.append(s.charAt(0)).append('.');
+ }
+ }
+ b.append(getClass().getSimpleName()).append('@').append(Integer.toString(hashCode(),16));
+ b.append('{').append(getContextPath()).append(',').append(getBaseResource()).append(',').append(_availability);
+
+ if (vhosts != null && vhosts.length > 0)
+ b.append(',').append(vhosts[0]);
+ b.append('}');
+
+ return b.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized Class<?> loadClass(String className) throws ClassNotFoundException
+ {
+ if (className == null)
+ return null;
+
+ if (_classLoader == null)
+ return Loader.loadClass(this.getClass(),className);
+
+ return _classLoader.loadClass(className);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addLocaleEncoding(String locale, String encoding)
+ {
+ if (_localeEncodingMap == null)
+ _localeEncodingMap = new HashMap<String, String>();
+ _localeEncodingMap.put(locale,encoding);
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getLocaleEncoding(String locale)
+ {
+ if (_localeEncodingMap == null)
+ return null;
+ String encoding = _localeEncodingMap.get(locale);
+ return encoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale
+ * language is looked up.
+ *
+ * @param locale
+ * a <code>Locale</code> value
+ * @return a <code>String</code> representing the character encoding for the locale or null if none found.
+ */
+ public String getLocaleEncoding(Locale locale)
+ {
+ if (_localeEncodingMap == null)
+ return null;
+ String encoding = _localeEncodingMap.get(locale.toString());
+ if (encoding == null)
+ encoding = _localeEncodingMap.get(locale.getLanguage());
+ return encoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get all of the locale encodings
+ *
+ * @return a map of all the locale encodings: key is name of the locale and value is the char encoding
+ */
+ public Map<String,String> getLocaleEncodings()
+ {
+ if (_localeEncodingMap == null)
+ return null;
+ return Collections.unmodifiableMap(_localeEncodingMap);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Resource getResource(String path) throws MalformedURLException
+ {
+ if (path == null || !path.startsWith(URIUtil.SLASH))
+ throw new MalformedURLException(path);
+
+ if (_baseResource == null)
+ return null;
+
+ try
+ {
+ path = URIUtil.canonicalPath(path);
+ Resource resource = _baseResource.addPath(path);
+
+ if (checkAlias(path,resource))
+ return resource;
+ return null;
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean checkAlias(String path, Resource resource)
+ {
+ // Is the resource aliased?
+ if (resource.getAlias() != null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
+
+ // alias checks
+ for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
+ {
+ AliasCheck check = i.next();
+ if (check.check(path,resource))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Aliased resource: " + resource + " approved by " + check);
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
+ */
+ public Resource newResource(URL url) throws IOException
+ {
+ return Resource.newResource(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
+ */
+ public Resource newResource(URI uri) throws IOException
+ {
+ return Resource.newResource(uri);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}.
+ *
+ * @param urlOrPath
+ * The URL or path to convert
+ * @return The Resource for the URL/path
+ * @throws IOException
+ * The Resource could not be created.
+ */
+ public Resource newResource(String urlOrPath) throws IOException
+ {
+ return Resource.newResource(urlOrPath);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Set<String> getResourcePaths(String path)
+ {
+ try
+ {
+ path = URIUtil.canonicalPath(path);
+ Resource resource = getResource(path);
+
+ if (resource != null && resource.exists())
+ {
+ if (!path.endsWith(URIUtil.SLASH))
+ path = path + URIUtil.SLASH;
+
+ String[] l = resource.list();
+ if (l != null)
+ {
+ HashSet<String> set = new HashSet<String>();
+ for (int i = 0; i < l.length; i++)
+ set.add(path + l[i]);
+ return set;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ }
+ return Collections.emptySet();
+ }
+
+ /* ------------------------------------------------------------ */
+ private String normalizeHostname(String host)
+ {
+ if (host == null)
+ return null;
+
+ if (host.endsWith("."))
+ return host.substring(0,host.length() - 1);
+
+ return host;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add an AliasCheck instance to possibly permit aliased resources
+ * @param check The alias checker
+ */
+ public void addAliasCheck(AliasCheck check)
+ {
+ _aliasChecks.add(check);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Mutable list of Alias checks
+ */
+ public List<AliasCheck> getAliasChecks()
+ {
+ return _aliasChecks;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param checks list of AliasCheck instances
+ */
+ public void setAliasChecks(List<AliasCheck> checks)
+ {
+ _aliasChecks.clear();
+ _aliasChecks.addAll(checks);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Context.
+ * <p>
+ * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
+ * </p>
+ *
+ *
+ */
+ public class Context extends NoContext
+ {
+ protected boolean _enabled = true; //whether or not the dynamic API is enabled for callers
+ protected boolean _extendedListenerTypes = false;
+
+
+ /* ------------------------------------------------------------ */
+ protected Context()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public ContextHandler getContextHandler()
+ {
+ return ContextHandler.this;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getContext(java.lang.String)
+ */
+ @Override
+ public ServletContext getContext(String uripath)
+ {
+ List<ContextHandler> contexts = new ArrayList<ContextHandler>();
+ Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
+ String matched_path = null;
+
+ for (Handler handler : handlers)
+ {
+ if (handler == null)
+ continue;
+ ContextHandler ch = (ContextHandler)handler;
+ String context_path = ch.getContextPath();
+
+ if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
+ || "/".equals(context_path))
+ {
+ // look first for vhost matching context only
+ if (getVirtualHosts() != null && getVirtualHosts().length > 0)
+ {
+ if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0)
+ {
+ for (String h1 : getVirtualHosts())
+ for (String h2 : ch.getVirtualHosts())
+ if (h1.equals(h2))
+ {
+ if (matched_path == null || context_path.length() > matched_path.length())
+ {
+ contexts.clear();
+ matched_path = context_path;
+ }
+
+ if (matched_path.equals(context_path))
+ contexts.add(ch);
+ }
+ }
+ }
+ else
+ {
+ if (matched_path == null || context_path.length() > matched_path.length())
+ {
+ contexts.clear();
+ matched_path = context_path;
+ }
+
+ if (matched_path.equals(context_path))
+ contexts.add(ch);
+ }
+ }
+ }
+
+ if (contexts.size() > 0)
+ return contexts.get(0)._scontext;
+
+ // try again ignoring virtual hosts
+ matched_path = null;
+ for (Handler handler : handlers)
+ {
+ if (handler == null)
+ continue;
+ ContextHandler ch = (ContextHandler)handler;
+ String context_path = ch.getContextPath();
+
+ if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
+ || "/".equals(context_path))
+ {
+ if (matched_path == null || context_path.length() > matched_path.length())
+ {
+ contexts.clear();
+ matched_path = context_path;
+ }
+
+ if (matched_path.equals(context_path))
+ contexts.add(ch);
+ }
+ }
+
+ if (contexts.size() > 0)
+ return contexts.get(0)._scontext;
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
+ */
+ @Override
+ public String getMimeType(String file)
+ {
+ if (_mimeTypes == null)
+ return null;
+ return _mimeTypes.getMimeByExtension(file);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
+ */
+ @Override
+ public RequestDispatcher getRequestDispatcher(String uriInContext)
+ {
+ if (uriInContext == null)
+ return null;
+
+ if (!uriInContext.startsWith("/"))
+ return null;
+
+ try
+ {
+ String query = null;
+ int q = 0;
+ if ((q = uriInContext.indexOf('?')) > 0)
+ {
+ query = uriInContext.substring(q + 1);
+ uriInContext = uriInContext.substring(0,q);
+ }
+
+ String pathInContext = URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
+ if (pathInContext!=null)
+ {
+ String uri = URIUtil.addPaths(getContextPath(),uriInContext);
+ ContextHandler context = ContextHandler.this;
+ return new Dispatcher(context,uri,pathInContext,query);
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
+ */
+ @Override
+ public String getRealPath(String path)
+ {
+ if (path == null)
+ return null;
+ if (path.length() == 0)
+ path = URIUtil.SLASH;
+ else if (path.charAt(0) != '/')
+ path = URIUtil.SLASH + path;
+
+ try
+ {
+ Resource resource = ContextHandler.this.getResource(path);
+ if (resource != null)
+ {
+ File file = resource.getFile();
+ if (file != null)
+ return file.getCanonicalPath();
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public URL getResource(String path) throws MalformedURLException
+ {
+ Resource resource = ContextHandler.this.getResource(path);
+ if (resource != null && resource.exists())
+ return resource.getURL();
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
+ */
+ @Override
+ public InputStream getResourceAsStream(String path)
+ {
+ try
+ {
+ URL url = getResource(path);
+ if (url == null)
+ return null;
+ Resource r = Resource.newResource(url);
+ return r.getInputStream();
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ return null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
+ */
+ @Override
+ public Set<String> getResourcePaths(String path)
+ {
+ return ContextHandler.this.getResourcePaths(path);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
+ */
+ @Override
+ public void log(Exception exception, String msg)
+ {
+ _logger.warn(msg,exception);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#log(java.lang.String)
+ */
+ @Override
+ public void log(String msg)
+ {
+ _logger.info(msg);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
+ */
+ @Override
+ public void log(String message, Throwable throwable)
+ {
+ _logger.warn(message,throwable);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+ */
+ @Override
+ public String getInitParameter(String name)
+ {
+ return ContextHandler.this.getInitParameter(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getInitParameterNames()
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public Enumeration<String> getInitParameterNames()
+ {
+ return ContextHandler.this.getInitParameterNames();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+ */
+ @Override
+ public synchronized Object getAttribute(String name)
+ {
+ Object o = ContextHandler.this.getAttribute(name);
+ if (o == null)
+ o = super.getAttribute(name);
+ return o;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getAttributeNames()
+ */
+ @Override
+ public synchronized Enumeration<String> getAttributeNames()
+ {
+ HashSet<String> set = new HashSet<String>();
+ Enumeration<String> e = super.getAttributeNames();
+ while (e.hasMoreElements())
+ set.add(e.nextElement());
+ e = _attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ set.add(e.nextElement());
+
+ return Collections.enumeration(set);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public synchronized void setAttribute(String name, Object value)
+ {
+ checkManagedAttribute(name,value);
+ Object old_value = super.getAttribute(name);
+
+ if (value == null)
+ super.removeAttribute(name);
+ else
+ super.setAttribute(name,value);
+
+ if (!_contextAttributeListeners.isEmpty())
+ {
+ ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value);
+
+ for (ServletContextAttributeListener l : _contextAttributeListeners)
+ {
+ if (old_value == null)
+ l.attributeAdded(event);
+ else if (value == null)
+ l.attributeRemoved(event);
+ else
+ l.attributeReplaced(event);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+ */
+ @Override
+ public synchronized void removeAttribute(String name)
+ {
+ checkManagedAttribute(name,null);
+
+ Object old_value = super.getAttribute(name);
+ super.removeAttribute(name);
+ if (old_value != null &&!_contextAttributeListeners.isEmpty())
+ {
+ ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);
+
+ for (ServletContextAttributeListener l : _contextAttributeListeners)
+ l.attributeRemoved(event);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getServletContextName()
+ */
+ @Override
+ public String getServletContextName()
+ {
+ String name = ContextHandler.this.getDisplayName();
+ if (name == null)
+ name = ContextHandler.this.getContextPath();
+ return name;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getContextPath()
+ {
+ if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
+ return "";
+
+ return _contextPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return "ServletContext@" + ContextHandler.this.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean setInitParameter(String name, String value)
+ {
+ if (ContextHandler.this.getInitParameter(name) != null)
+ return false;
+ ContextHandler.this.getInitParams().put(name,value);
+ return true;
+ }
+
+ @Override
+ public void addListener(String className)
+ {
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ try
+ {
+ Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className);
+ addListener(clazz);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ @Override
+ public <T extends EventListener> void addListener(T t)
+ {
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ checkListener(t.getClass());
+
+ ContextHandler.this.addEventListener(t);
+ ContextHandler.this.addProgrammaticListener(t);
+ }
+
+ @Override
+ public void addListener(Class<? extends EventListener> listenerClass)
+ {
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ try
+ {
+ EventListener e = createListener(listenerClass);
+ addListener(e);
+ }
+ catch (ServletException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ @Override
+ public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+ {
+ try
+ {
+ return createInstance(clazz);
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+
+ public void checkListener (Class<? extends EventListener> listener) throws IllegalStateException
+ {
+ boolean ok = false;
+ int startIndex = (isExtendedListenerTypes()?EXTENDED_LISTENER_TYPE_INDEX:DEFAULT_LISTENER_TYPE_INDEX);
+ for (int i=startIndex;i<SERVLET_LISTENER_TYPES.length;i++)
+ {
+ if (SERVLET_LISTENER_TYPES[i].isAssignableFrom(listener))
+ {
+ ok = true;
+ break;
+ }
+ }
+ if (!ok)
+ throw new IllegalArgumentException("Inappropriate listener class "+listener.getName());
+ }
+
+ public void setExtendedListenerTypes (boolean extended)
+ {
+ _extendedListenerTypes = extended;
+ }
+
+ public boolean isExtendedListenerTypes()
+ {
+ return _extendedListenerTypes;
+ }
+
+
+ @Override
+ public ClassLoader getClassLoader()
+ {
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ //no security manager just return the classloader
+ if (System.getSecurityManager() == null)
+ return _classLoader;
+ else
+ {
+ //check to see if the classloader of the caller is the same as the context
+ //classloader, or a parent of it
+ try
+ {
+ Class reflect = Loader.loadClass(getClass(), "sun.reflect.Reflection");
+ Method getCallerClass = reflect.getMethod("getCallerClass", Integer.TYPE);
+ Class caller = (Class)getCallerClass.invoke(null, 2);
+
+ boolean ok = false;
+ ClassLoader callerLoader = caller.getClassLoader();
+ while (!ok && callerLoader != null)
+ {
+ if (callerLoader == _classLoader)
+ ok = true;
+ else
+ callerLoader = callerLoader.getParent();
+ }
+
+ if (ok)
+ return _classLoader;
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Unable to check classloader of caller",e);
+ }
+
+ AccessController.checkPermission(new RuntimePermission("getClassLoader"));
+ return _classLoader;
+ }
+ }
+
+ @Override
+ public JspConfigDescriptor getJspConfigDescriptor()
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ public void setJspConfigDescriptor(JspConfigDescriptor d)
+ {
+
+ }
+
+ @Override
+ public void declareRoles(String... roleNames)
+ {
+ if (!isStarting())
+ throw new IllegalStateException ();
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ _enabled = enabled;
+ }
+
+ public boolean isEnabled()
+ {
+ return _enabled;
+ }
+
+
+
+ public <T> T createInstance (Class<T> clazz) throws Exception
+ {
+ T o = clazz.newInstance();
+ return o;
+ }
+ }
+
+
+ public static class NoContext extends AttributesMap implements ServletContext
+ {
+ private int _effectiveMajorVersion = SERVLET_MAJOR_VERSION;
+ private int _effectiveMinorVersion = SERVLET_MINOR_VERSION;
+
+ /* ------------------------------------------------------------ */
+ public NoContext()
+ {
+ }
+
+ @Override
+ public ServletContext getContext(String uripath)
+ {
+ return null;
+ }
+
+ @Override
+ public int getMajorVersion()
+ {
+ return SERVLET_MAJOR_VERSION;
+ }
+
+ @Override
+ public String getMimeType(String file)
+ {
+ return null;
+ }
+
+ @Override
+ public int getMinorVersion()
+ {
+ return SERVLET_MINOR_VERSION;
+ }
+
+ @Override
+ public RequestDispatcher getNamedDispatcher(String name)
+ {
+ return null;
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String uriInContext)
+ {
+ return null;
+ }
+
+ @Override
+ public String getRealPath(String path)
+ {
+ return null;
+ }
+
+ @Override
+ public URL getResource(String path) throws MalformedURLException
+ {
+ return null;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String path)
+ {
+ return null;
+ }
+
+ @Override
+ public Set<String> getResourcePaths(String path)
+ {
+ return null;
+ }
+
+ @Override
+ public String getServerInfo()
+ {
+ return "jetty/" + Server.getVersion();
+ }
+
+ @Override
+ @Deprecated
+ public Servlet getServlet(String name) throws ServletException
+ {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @Deprecated
+ public Enumeration<String> getServletNames()
+ {
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ @Deprecated
+ public Enumeration<Servlet> getServlets()
+ {
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ @Override
+ public void log(Exception exception, String msg)
+ {
+ LOG.warn(msg,exception);
+ }
+
+ @Override
+ public void log(String msg)
+ {
+ LOG.info(msg);
+ }
+
+ @Override
+ public void log(String message, Throwable throwable)
+ {
+ LOG.warn(message,throwable);
+ }
+
+ @Override
+ public String getInitParameter(String name)
+ {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Enumeration<String> getInitParameterNames()
+ {
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+
+ @Override
+ public String getServletContextName()
+ {
+ return "No Context";
+ }
+
+ @Override
+ public String getContextPath()
+ {
+ return null;
+ }
+
+
+ @Override
+ public boolean setInitParameter(String name, String value)
+ {
+ return false;
+ }
+
+ @Override
+ public Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public Dynamic addFilter(String filterName, Filter filter)
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public Dynamic addFilter(String filterName, String className)
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className)
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public FilterRegistration getFilterRegistration(String filterName)
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public ServletRegistration getServletRegistration(String servletName)
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public Map<String, ? extends ServletRegistration> getServletRegistrations()
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public SessionCookieConfig getSessionCookieConfig()
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+ {
+ LOG.warn(__unimplmented);
+ }
+
+ @Override
+ public void addListener(String className)
+ {
+ LOG.warn(__unimplmented);
+ }
+
+ @Override
+ public <T extends EventListener> void addListener(T t)
+ {
+ LOG.warn(__unimplmented);
+ }
+
+ @Override
+ public void addListener(Class<? extends EventListener> listenerClass)
+ {
+ LOG.warn(__unimplmented);
+ }
+
+ @Override
+ public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+ {
+ try
+ {
+ return clazz.newInstance();
+ }
+ catch (InstantiationException e)
+ {
+ throw new ServletException(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+ @Override
+ public ClassLoader getClassLoader()
+ {
+ AccessController.checkPermission(new RuntimePermission("getClassLoader"));
+ return ContextHandler.class.getClassLoader();
+ }
+
+ @Override
+ public int getEffectiveMajorVersion()
+ {
+ return _effectiveMajorVersion;
+ }
+
+ @Override
+ public int getEffectiveMinorVersion()
+ {
+ return _effectiveMinorVersion;
+ }
+
+ public void setEffectiveMajorVersion (int v)
+ {
+ _effectiveMajorVersion = v;
+ }
+
+ public void setEffectiveMinorVersion (int v)
+ {
+ _effectiveMinorVersion = v;
+ }
+
+ @Override
+ public JspConfigDescriptor getJspConfigDescriptor()
+ {
+ LOG.warn(__unimplmented);
+ return null;
+ }
+
+ @Override
+ public void declareRoles(String... roleNames)
+ {
+ LOG.warn(__unimplmented);
+ }
+
+ /**
+ * @see javax.servlet.ServletContext#getVirtualServerName()
+ */
+ @Override
+ public String getVirtualServerName()
+ {
+ // TODO 3.1 Auto-generated method stub
+ return null;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Interface to check aliases
+ */
+ public interface AliasCheck
+ {
+ /* ------------------------------------------------------------ */
+ /** Check an alias
+ * @param path The path the aliased resource was created for
+ * @param resource The aliased resourced
+ * @return True if the resource is OK to be served.
+ */
+ boolean check(String path, Resource resource);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Approve all aliases.
+ */
+ public static class ApproveAliases implements AliasCheck
+ {
+ @Override
+ public boolean check(String path, Resource resource)
+ {
+ return true;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Approve Aliases with same suffix.
+ * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
+ * approved because both the resource and alias end with ".html".
+ */
+ @Deprecated
+ public static class ApproveSameSuffixAliases implements AliasCheck
+ {
+ {
+ LOG.warn("ApproveSameSuffixAlias is not safe for production");
+ }
+
+ @Override
+ public boolean check(String path, Resource resource)
+ {
+ int dot = path.lastIndexOf('.');
+ if (dot<0)
+ return false;
+ String suffix=path.substring(dot);
+ return resource.toString().endsWith(suffix);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Approve Aliases with a path prefix.
+ * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
+ * approved because both the resource and alias end with "/foobar.html".
+ */
+ @Deprecated
+ public static class ApprovePathPrefixAliases implements AliasCheck
+ {
+ {
+ LOG.warn("ApprovePathPrefixAliases is not safe for production");
+ }
+
+ @Override
+ public boolean check(String path, Resource resource)
+ {
+ int slash = path.lastIndexOf('/');
+ if (slash<0 || slash==path.length()-1)
+ return false;
+ String suffix=path.substring(slash);
+ return resource.toString().endsWith(suffix);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Approve Aliases of a non existent directory.
+ * If a directory "/foobar/" does not exist, then the resource is
+ * aliased to "/foobar". Accept such aliases.
+ */
+ public static class ApproveNonExistentDirectoryAliases implements AliasCheck
+ {
+ @Override
+ public boolean check(String path, Resource resource)
+ {
+ if (resource.exists())
+ return false;
+
+ String a=resource.getAlias().toString();
+ String r=resource.getURL().toString();
+
+ if (a.length()>r.length())
+ return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/");
+ if (a.length()<r.length())
+ return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/");
+
+ return a.equals(r);
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** ContextHandlerCollection.
+ *
+ * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a
+ * {@link org.eclipse.jetty.http.PathMap} to it's contained handlers based
+ * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
+ * The contexts do not need to be directly contained, only children of the contained handlers.
+ * Multiple contexts may have the same context path and they are called in order until one
+ * handles the request.
+ *
+ */
+@ManagedObject("Context Handler Collection")
+public class ContextHandlerCollection extends HandlerCollection
+{
+ private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
+
+ private volatile Trie<ContextHandler[]> _contexts;
+ private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
+
+ /* ------------------------------------------------------------ */
+ public ContextHandlerCollection()
+ {
+ super(true);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Remap the context paths.
+ */
+ @ManagedOperation("update the mapping of context path to context")
+ public void mapContexts()
+ {
+ int capacity=512;
+
+ // Loop until we have a big enough trie to hold all the context paths
+ Trie<ContextHandler[]> trie;
+ loop: while(true)
+ {
+ trie=new ArrayTernaryTrie<>(false,capacity);
+
+ Handler[] branches = getHandlers();
+
+ // loop over each group of contexts
+ for (int b=0;branches!=null && b<branches.length;b++)
+ {
+ Handler[] handlers=null;
+
+ if (branches[b] instanceof ContextHandler)
+ {
+ handlers = new Handler[]{ branches[b] };
+ }
+ else if (branches[b] instanceof HandlerContainer)
+ {
+ handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
+ }
+ else
+ continue;
+
+ // for each context handler in a group
+ for (int i=0;handlers!=null && i<handlers.length;i++)
+ {
+ ContextHandler handler=(ContextHandler)handlers[i];
+ String contextPath=handler.getContextPath().substring(1);
+ ContextHandler[] contexts=trie.get(contextPath);
+
+ if (!trie.put(contextPath,ArrayUtil.addToArray(contexts,handler,ContextHandler.class)))
+ {
+ capacity+=512;
+ continue loop;
+ }
+ }
+ }
+
+ break;
+ }
+
+ // Sort the contexts so those with virtual hosts are considered before those without
+ for (String ctx : trie.keySet())
+ {
+ ContextHandler[] contexts=trie.get(ctx);
+ ContextHandler[] sorted=new ContextHandler[contexts.length];
+ int i=0;
+ for (ContextHandler handler:contexts)
+ if (handler.getVirtualHosts()!=null && handler.getVirtualHosts().length>0)
+ sorted[i++]=handler;
+ for (ContextHandler handler:contexts)
+ if (handler.getVirtualHosts()==null || handler.getVirtualHosts().length==0)
+ sorted[i++]=handler;
+ trie.put(ctx,sorted);
+ }
+
+ //for (String ctx : trie.keySet())
+ // System.err.printf("'%s'->%s%n",ctx,Arrays.asList(trie.get(ctx)));
+ _contexts=trie;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
+ */
+ @Override
+ public void setHandlers(Handler[] handlers)
+ {
+ super.setHandlers(handlers);
+ if (isStarted())
+ mapContexts();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ mapContexts();
+ super.doStart();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ Handler[] handlers = getHandlers();
+ if (handlers==null || handlers.length==0)
+ return;
+
+ HttpChannelState async = baseRequest.getHttpChannelState();
+ if (async.isAsync())
+ {
+ ContextHandler context=async.getContextHandler();
+ if (context!=null)
+ {
+ context.handle(target,baseRequest,request, response);
+ return;
+ }
+ }
+
+ // data structure which maps a request to a context; first-best match wins
+ // { context path => [ context ] }
+ // }
+ if (target.startsWith("/"))
+ {
+ int limit = target.length()-1;
+
+ while (limit>=0)
+ {
+ // Get best match
+ ContextHandler[] contexts = _contexts.getBest(target,1,limit);
+ if (contexts==null)
+ break;
+
+ int l=contexts[0].getContextPath().length();
+ if (l==1 || target.length()==l || target.charAt(l)=='/')
+ {
+ for (ContextHandler handler : contexts)
+ {
+ handler.handle(target,baseRequest, request, response);
+ if (baseRequest.isHandled())
+ return;
+ }
+ }
+
+ limit=l-2;
+ }
+ }
+ else
+ {
+ // This may not work in all circumstances... but then I think it should never be called
+ for (int i=0;i<handlers.length;i++)
+ {
+ handlers[i].handle(target,baseRequest, request, response);
+ if ( baseRequest.isHandled())
+ return;
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Add a context handler.
+ * @param contextPath The context path to add
+ * @return the ContextHandler just added
+ */
+ public ContextHandler addContext(String contextPath,String resourceBase)
+ {
+ try
+ {
+ ContextHandler context = _contextClass.newInstance();
+ context.setContextPath(contextPath);
+ context.setResourceBase(resourceBase);
+ addHandler(context);
+ return context;
+ }
+ catch (Exception e)
+ {
+ LOG.debug(e);
+ throw new Error(e);
+ }
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The class to use to add new Contexts
+ */
+ public Class<?> getContextClass()
+ {
+ return _contextClass;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param contextClass The class to use to add new Contexts
+ */
+ public void setContextClass(Class<? extends ContextHandler> contextClass)
+ {
+ if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
+ throw new IllegalArgumentException();
+ _contextClass = contextClass;
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+
+
+/**
+ * Debug Handler.
+ * A lightweight debug handler that can be used in production code.
+ * Details of the request and response are written to an output stream
+ * and the current thread name is updated with information that will link
+ * to the details in that output.
+ */
+public class DebugHandler extends HandlerWrapper implements Connection.Listener
+{
+ private DateCache _date=new DateCache("HH:mm:ss", Locale.US);
+ private OutputStream _out;
+ private PrintStream _print;
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ final Response base_response = baseRequest.getResponse();
+ final Thread thread=Thread.currentThread();
+ final String old_name=thread.getName();
+
+ boolean suspend=false;
+ boolean retry=false;
+ String name=(String)request.getAttribute("org.eclipse.jetty.thread.name");
+ if (name==null)
+ name=old_name+":"+baseRequest.getScheme()+"://"+baseRequest.getLocalAddr()+":"+baseRequest.getLocalPort()+baseRequest.getUri();
+ else
+ retry=true;
+
+ String ex=null;
+ try
+ {
+ if (retry)
+ print(name,"RESUME");
+ else
+ print(name,"REQUEST "+baseRequest.getRemoteAddr()+" "+request.getMethod()+" "+baseRequest.getHeader("Cookie")+"; "+baseRequest.getHeader("User-Agent"));
+ thread.setName(name);
+
+ getHandler().handle(target,baseRequest,request,response);
+ }
+ catch(IOException ioe)
+ {
+ ex=ioe.toString();
+ throw ioe;
+ }
+ catch(ServletException se)
+ {
+ ex=se.toString()+":"+se.getCause();
+ throw se;
+ }
+ catch(RuntimeException rte)
+ {
+ ex=rte.toString();
+ throw rte;
+ }
+ catch(Error e)
+ {
+ ex=e.toString();
+ throw e;
+ }
+ finally
+ {
+ thread.setName(old_name);
+ suspend=baseRequest.getHttpChannelState().isSuspended();
+ if (suspend)
+ {
+ request.setAttribute("org.eclipse.jetty.thread.name",name);
+ print(name,"SUSPEND");
+ }
+ else
+ print(name,"RESPONSE "+base_response.getStatus()+(ex==null?"":("/"+ex))+" "+base_response.getContentType());
+ }
+ }
+
+ private void print(String name,String message)
+ {
+ long now=System.currentTimeMillis();
+ final String d=_date.formatNow(now);
+ final int ms=(int)(now%1000);
+
+ _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+message);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (_out==null)
+ _out=new RolloverFileOutputStream("./logs/yyyy_mm_dd.debug.log",true);
+ _print=new PrintStream(_out);
+
+ for (Connector connector : getServer().getConnectors())
+ if (connector instanceof AbstractConnector)
+ ((AbstractConnector)connector).addBean(this,false);
+
+ super.doStart();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ _print.close();
+ for (Connector connector : getServer().getConnectors())
+ if (connector instanceof AbstractConnector)
+ ((AbstractConnector)connector).removeBean(this);
+ }
+
+ /**
+ * @return the out
+ */
+ public OutputStream getOutputStream()
+ {
+ return _out;
+ }
+
+ /**
+ * @param out the out to set
+ */
+ public void setOutputStream(OutputStream out)
+ {
+ _out = out;
+ }
+
+ @Override
+ public void onOpened(Connection connection)
+ {
+ print(Thread.currentThread().getName(),"OPENED "+connection.toString());
+ }
+
+ @Override
+ public void onClosed(Connection connection)
+ {
+ print(Thread.currentThread().getName(),"CLOSED "+connection.toString());
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Default Handler.
+ *
+ * This handle will deal with unhandled requests in the server.
+ * For requests for favicon.ico, the Jetty icon is served.
+ * For reqests to '/' a 404 with a list of known contexts is served.
+ * For all other requests a normal 404 is served.
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class DefaultHandler extends AbstractHandler
+{
+ private static final Logger LOG = Log.getLogger(DefaultHandler.class);
+
+ final long _faviconModified=(System.currentTimeMillis()/1000)*1000L;
+ byte[] _favicon;
+ boolean _serveIcon=true;
+ boolean _showContexts=true;
+
+ public DefaultHandler()
+ {
+ try
+ {
+ URL fav = this.getClass().getClassLoader().getResource("org/eclipse/jetty/favicon.ico");
+ if (fav!=null)
+ {
+ Resource r = Resource.newResource(fav);
+ _favicon=IO.readBytes(r.getInputStream());
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (response.isCommitted() || baseRequest.isHandled())
+ return;
+
+ baseRequest.setHandled(true);
+
+ String method=request.getMethod();
+
+ // little cheat for common request
+ if (_serveIcon && _favicon!=null && HttpMethod.GET.is(method) && request.getRequestURI().equals("/favicon.ico"))
+ {
+ if (request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString())==_faviconModified)
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ else
+ {
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType("image/x-icon");
+ response.setContentLength(_favicon.length);
+ response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(), _faviconModified);
+ response.setHeader(HttpHeader.CACHE_CONTROL.toString(),"max-age=360000,public");
+ response.getOutputStream().write(_favicon);
+ }
+ return;
+ }
+
+
+ if (!_showContexts || !HttpMethod.GET.is(method) || !request.getRequestURI().equals("/"))
+ {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ response.setContentType(MimeTypes.Type.TEXT_HTML.toString());
+
+ ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
+
+ writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
+ writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
+ writer.write("No context on this server matched or handled this request.<BR>");
+ writer.write("Contexts known to this server are: <ul>");
+
+ Server server = getServer();
+ Handler[] handlers = server==null?null:server.getChildHandlersByClass(ContextHandler.class);
+
+ for (int i=0;handlers!=null && i<handlers.length;i++)
+ {
+ ContextHandler context = (ContextHandler)handlers[i];
+ if (context.isRunning())
+ {
+ writer.write("<li><a href=\"");
+ if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+ writer.write("http://"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+ writer.write(context.getContextPath());
+ if (context.getContextPath().length()>1 && context.getContextPath().endsWith("/"))
+ writer.write("/");
+ writer.write("\">");
+ writer.write(context.getContextPath());
+ if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+ writer.write(" @ "+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+ writer.write(" ---> ");
+ writer.write(context.toString());
+ writer.write("</a></li>\n");
+ }
+ else
+ {
+ writer.write("<li>");
+ writer.write(context.getContextPath());
+ if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+ writer.write(" @ "+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+ writer.write(" ---> ");
+ writer.write(context.toString());
+ if (context.isFailed())
+ writer.write(" [failed]");
+ if (context.isStopped())
+ writer.write(" [stopped]");
+ writer.write("</li>\n");
+ }
+ }
+
+ writer.write("</ul><hr>");
+ writer.write("<a href=\"http://eclipse.org/jetty\"><img border=0 src=\"/favicon.ico\"/></a> ");
+ writer.write("<a href=\"http://eclipse.org/jetty\">Powered by Jetty:// Java Web Server</a><hr/>\n");
+
+ writer.write("\n</BODY>\n</HTML>\n");
+ writer.flush();
+ response.setContentLength(writer.size());
+ try (OutputStream out=response.getOutputStream())
+ {
+ writer.writeTo(out);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns true if the handle can server the jetty favicon.ico
+ */
+ public boolean getServeIcon()
+ {
+ return _serveIcon;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param serveIcon true if the handle can server the jetty favicon.ico
+ */
+ public void setServeIcon(boolean serveIcon)
+ {
+ _serveIcon = serveIcon;
+ }
+
+ public boolean getShowContexts()
+ {
+ return _showContexts;
+ }
+
+ public void setShowContexts(boolean show)
+ {
+ _showContexts = show;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Handler for Error pages
+ * An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or
+ * {@link org.eclipse.jetty.server.Server#addBean(Object)}.
+ * It is called by the HttpResponse.sendError method to write a error page via {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a dispatch cannot be done.
+ *
+ */
+public class ErrorHandler extends AbstractHandler
+{
+ private static final Logger LOG = Log.getLogger(ErrorHandler.class);
+ public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
+
+ boolean _showStacks=true;
+ boolean _showMessageInTitle=true;
+ String _cacheControl="must-revalidate,no-cache,no-store";
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ String method = request.getMethod();
+ if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
+ {
+ baseRequest.setHandled(true);
+ return;
+ }
+
+ if (this instanceof ErrorPageMapper)
+ {
+ String error_page=((ErrorPageMapper)this).getErrorPage(request);
+ if (error_page!=null && request.getServletContext()!=null)
+ {
+ String old_error_page=(String)request.getAttribute(ERROR_PAGE);
+ if (old_error_page==null || !old_error_page.equals(error_page))
+ {
+ request.setAttribute(ERROR_PAGE, error_page);
+
+ Dispatcher dispatcher = (Dispatcher) request.getServletContext().getRequestDispatcher(error_page);
+ try
+ {
+ if(dispatcher!=null)
+ {
+ dispatcher.error(request, response);
+ return;
+ }
+ LOG.warn("No error page "+error_page);
+ }
+ catch (ServletException e)
+ {
+ LOG.warn(Log.EXCEPTION, e);
+ return;
+ }
+ }
+ }
+ }
+
+ baseRequest.setHandled(true);
+ response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString());
+ if (_cacheControl!=null)
+ response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl);
+ ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096);
+ String reason=(response instanceof Response)?((Response)response).getReason():null;
+ handleErrorPage(request, writer, response.getStatus(), reason);
+ writer.flush();
+ response.setContentLength(writer.size());
+ writer.writeTo(response.getOutputStream());
+ writer.destroy();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
+ throws IOException
+ {
+ writeErrorPage(request, writer, code, message, _showStacks);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+ throws IOException
+ {
+ if (message == null)
+ message=HttpStatus.getMessage(code);
+
+ writer.write("<html>\n<head>\n");
+ writeErrorPageHead(request,writer,code,message);
+ writer.write("</head>\n<body>");
+ writeErrorPageBody(request,writer,code,message,showStacks);
+ writer.write("\n</body>\n</html>\n");
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message)
+ throws IOException
+ {
+ writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n");
+ writer.write("<title>Error ");
+ writer.write(Integer.toString(code));
+
+ if (_showMessageInTitle)
+ {
+ writer.write(' ');
+ write(writer,message);
+ }
+ writer.write("</title>\n");
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+ throws IOException
+ {
+ String uri= request.getRequestURI();
+
+ writeErrorPageMessage(request,writer,code,message,uri);
+ if (showStacks)
+ writeErrorPageStacks(request,writer);
+ writer.write("<hr><i><small>Powered by Jetty://</small></i><hr/>\n");
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri)
+ throws IOException
+ {
+ writer.write("<h2>HTTP ERROR ");
+ writer.write(Integer.toString(code));
+ writer.write("</h2>\n<p>Problem accessing ");
+ write(writer,uri);
+ writer.write(". Reason:\n<pre> ");
+ write(writer,message);
+ writer.write("</pre></p>");
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
+ throws IOException
+ {
+ Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception");
+ while(th!=null)
+ {
+ writer.write("<h3>Caused by:</h3><pre>");
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ th.printStackTrace(pw);
+ pw.flush();
+ write(writer,sw.getBuffer().toString());
+ writer.write("</pre>\n");
+
+ th =th.getCause();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Bad Message Error body
+ * <p>Generate a error response body to be sent for a bad message.
+ * In this case there is something wrong with the request, so either
+ * a request cannot be built, or it is not safe to build a request.
+ * This method allows for a simple error page body to be returned
+ * and some response headers to be set.
+ * @param status The error code that will be sent
+ * @param reason The reason for the error code (may be null)
+ * @param fields The header fields that will be sent with the response.
+ * @return The content as a ByteBuffer, or null for no body.
+ */
+ public ByteBuffer badMessageError(int status, String reason, HttpFields fields)
+ {
+ if (reason==null)
+ reason=HttpStatus.getMessage(status);
+ fields.put(HttpHeader.CONTENT_TYPE,MimeTypes.Type.TEXT_HTML_8859_1.asString());
+ return BufferUtil.toBuffer("<h1>Bad Message " + status + "</h1><pre>reason: " + reason + "</pre>");
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the cacheControl.
+ * @return the cacheControl header to set on error responses.
+ */
+ public String getCacheControl()
+ {
+ return _cacheControl;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the cacheControl.
+ * @param cacheControl the cacheControl header to set on error responses.
+ */
+ public void setCacheControl(String cacheControl)
+ {
+ _cacheControl = cacheControl;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if stack traces are shown in the error pages
+ */
+ public boolean isShowStacks()
+ {
+ return _showStacks;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param showStacks True if stack traces are shown in the error pages
+ */
+ public void setShowStacks(boolean showStacks)
+ {
+ _showStacks = showStacks;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param showMessageInTitle if true, the error message appears in page title
+ */
+ public void setShowMessageInTitle(boolean showMessageInTitle)
+ {
+ _showMessageInTitle = showMessageInTitle;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public boolean getShowMessageInTitle()
+ {
+ return _showMessageInTitle;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void write(Writer writer,String string)
+ throws IOException
+ {
+ if (string==null)
+ return;
+
+ for (int i=0;i<string.length();i++)
+ {
+ char c=string.charAt(i);
+
+ switch(c)
+ {
+ case '&' :
+ writer.write("&");
+ break;
+ case '<' :
+ writer.write("<");
+ break;
+ case '>' :
+ writer.write(">");
+ break;
+
+ default:
+ if (Character.isISOControl(c) && !Character.isWhitespace(c))
+ writer.write('?');
+ else
+ writer.write(c);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public interface ErrorPageMapper
+ {
+ String getErrorPage(HttpServletRequest request);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static ErrorHandler getErrorHandler(Server server, ContextHandler context)
+ {
+ ErrorHandler error_handler=null;
+ if (context!=null)
+ error_handler=context.getErrorHandler();
+ if (error_handler==null && server!=null)
+ error_handler = server.getBean(ErrorHandler.class);
+ return error_handler;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/* ------------------------------------------------------------ */
+/** A collection of handlers.
+ * <p>
+ * The default implementations calls all handlers in list order,
+ * regardless of the response status or exceptions. Derived implementation
+ * may alter the order or the conditions of calling the contained
+ * handlers.
+ * <p>
+ *
+ */
+@ManagedObject("Handler of multiple handlers")
+public class HandlerCollection extends AbstractHandlerContainer
+{
+ private final boolean _mutableWhenRunning;
+ private volatile Handler[] _handlers;
+
+ /* ------------------------------------------------------------ */
+ public HandlerCollection()
+ {
+ _mutableWhenRunning=false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public HandlerCollection(boolean mutableWhenRunning)
+ {
+ _mutableWhenRunning=mutableWhenRunning;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the handlers.
+ */
+ @Override
+ @ManagedAttribute(value="Wrapped handlers", readonly=true)
+ public Handler[] getHandlers()
+ {
+ return _handlers;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param handlers The handlers to set.
+ */
+ public void setHandlers(Handler[] handlers)
+ {
+ if (!_mutableWhenRunning && isStarted())
+ throw new IllegalStateException(STARTED);
+
+ if (handlers!=null)
+ for (Handler handler:handlers)
+ if (handler.getServer()!=getServer())
+ handler.setServer(getServer());
+ Handler[] old=_handlers;;
+ _handlers = handlers;
+ updateBeans(old, handlers);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ if (_handlers!=null && isStarted())
+ {
+ MultiException mex=null;
+
+ for (int i=0;i<_handlers.length;i++)
+ {
+ try
+ {
+ _handlers[i].handle(target,baseRequest, request, response);
+ }
+ catch(IOException e)
+ {
+ throw e;
+ }
+ catch(RuntimeException e)
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ if (mex==null)
+ mex=new MultiException();
+ mex.add(e);
+ }
+ }
+ if (mex!=null)
+ {
+ if (mex.size()==1)
+ throw new ServletException(mex.getThrowable(0));
+ else
+ throw new ServletException(mex);
+ }
+
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setServer(Server server)
+ {
+ super.setServer(server);
+ Handler[] handlers=getHandlers();
+ if (handlers!=null)
+ for (Handler h : handlers)
+ h.setServer(server);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Add a handler.
+ * This implementation adds the passed handler to the end of the existing collection of handlers.
+ * @see org.eclipse.jetty.server.server.HandlerContainer#addHandler(org.eclipse.jetty.server.server.Handler)
+ */
+ public void addHandler(Handler handler)
+ {
+ setHandlers(ArrayUtil.addToArray(getHandlers(), handler, Handler.class));
+ }
+
+ /* ------------------------------------------------------------ */
+ public void removeHandler(Handler handler)
+ {
+ Handler[] handlers = getHandlers();
+
+ if (handlers!=null && handlers.length>0 )
+ setHandlers(ArrayUtil.removeFromArray(handlers, handler));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void expandChildren(List<Handler> list, Class<?> byClass)
+ {
+ if (getHandlers()!=null)
+ for (Handler h:getHandlers())
+ expandHandler(h, list, byClass);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void destroy()
+ {
+ if (!isStopped())
+ throw new IllegalStateException("!STOPPED");
+ Handler[] children=getChildHandlers();
+ setHandlers(null);
+ for (Handler child: children)
+ child.destroy();
+ super.destroy();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+
+/* ------------------------------------------------------------ */
+/** HandlerList.
+ * This extension of {@link HandlerCollection} will call
+ * each contained handler in turn until either an exception is thrown, the response
+ * is committed or a positive response status is set.
+ */
+public class HandlerList extends HandlerCollection
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ Handler[] handlers = getHandlers();
+
+ if (handlers!=null && isStarted())
+ {
+ for (int i=0;i<handlers.length;i++)
+ {
+ handlers[i].handle(target,baseRequest, request, response);
+ if ( baseRequest.isHandled())
+ return;
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A <code>HandlerWrapper</code> acts as a {@link Handler} but delegates the {@link Handler#handle handle} method and
+ * {@link LifeCycle life cycle} events to a delegate. This is primarily used to implement the <i>Decorator</i> pattern.
+ *
+ */
+@ManagedObject("Handler wrapping another Handler")
+public class HandlerWrapper extends AbstractHandlerContainer
+{
+ protected Handler _handler;
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public HandlerWrapper()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the handlers.
+ */
+ @ManagedAttribute(value="Wrapped Handler", readonly=true)
+ public Handler getHandler()
+ {
+ return _handler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the handlers.
+ */
+ @Override
+ public Handler[] getHandlers()
+ {
+ if (_handler==null)
+ return new Handler[0];
+ return new Handler[] {_handler};
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param handler Set the {@link Handler} which should be wrapped.
+ */
+ public void setHandler(Handler handler)
+ {
+ if (isStarted())
+ throw new IllegalStateException(STARTED);
+
+ if (handler!=null)
+ handler.setServer(getServer());
+
+ Handler old=_handler;
+ _handler=handler;
+ updateBean(old,_handler);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (_handler!=null && isStarted())
+ {
+ _handler.handle(target,baseRequest, request, response);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setServer(Server server)
+ {
+ if (server==getServer())
+ return;
+
+ if (isStarted())
+ throw new IllegalStateException(STARTED);
+
+ super.setServer(server);
+ Handler h=getHandler();
+ if (h!=null)
+ h.setServer(server);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void expandChildren(List<Handler> list, Class<?> byClass)
+ {
+ expandHandler(_handler,list,byClass);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void destroy()
+ {
+ if (!isStopped())
+ throw new IllegalStateException("!STOPPED");
+ Handler child=getHandler();
+ if (child!=null)
+ {
+ setHandler(null);
+ child.destroy();
+ }
+ super.destroy();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+
+/* ------------------------------------------------------------ */
+/**
+ * A <code>HandlerContainer</code> that allows a hot swap of a wrapped handler.
+ *
+ */
+public class HotSwapHandler extends AbstractHandlerContainer
+{
+ private volatile Handler _handler;
+
+ /* ------------------------------------------------------------ */
+ /**
+ *
+ */
+ public HotSwapHandler()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the handlers.
+ */
+ public Handler getHandler()
+ {
+ return _handler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the handlers.
+ */
+ @Override
+ public Handler[] getHandlers()
+ {
+ return new Handler[]
+ { _handler };
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param handler
+ * Set the {@link Handler} which should be wrapped.
+ */
+ public void setHandler(Handler handler)
+ {
+ if (handler == null)
+ throw new IllegalArgumentException("Parameter handler is null.");
+ try
+ {
+ updateBean(_handler,handler);
+ _handler=handler;
+ Server server = getServer();
+ handler.setServer(server);
+
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (_handler != null && isStarted())
+ {
+ _handler.handle(target,baseRequest,request,response);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setServer(Server server)
+ {
+ if (isRunning())
+ throw new IllegalStateException(RUNNING);
+
+ super.setServer(server);
+
+ Handler h = getHandler();
+ if (h != null)
+ h.setServer(server);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void expandChildren(List<Handler> list, Class<?> byClass)
+ {
+ expandHandler(_handler,list,byClass);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void destroy()
+ {
+ if (!isStopped())
+ throw new IllegalStateException("!STOPPED");
+ Handler child = getHandler();
+ if (child != null)
+ {
+ setHandler(null);
+ child.destroy();
+ }
+ super.destroy();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.IPAddressMap;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * IP Access Handler
+ * <p>
+ * Controls access to the wrapped handler by the real remote IP. Control is provided
+ * by white/black lists that include both internet addresses and URIs. This handler
+ * uses the real internet address of the connection, not one reported in the forwarded
+ * for headers, as this cannot be as easily forged.
+ * <p>
+ * Typically, the black/white lists will be used in one of three modes:
+ * <ul>
+ * <li>Blocking a few specific IPs/URLs by specifying several black list entries.
+ * <li>Allowing only some specific IPs/URLs by specifying several white lists entries.
+ * <li>Allowing a general range of IPs/URLs by specifying several general white list
+ * entries, that are then further refined by several specific black list exceptions
+ * </ul>
+ * <p>
+ * By default an empty white list is treated as match all. If there is at least one entry in
+ * the white list, then a request must match a white list entry. Black list entries
+ * are always applied, so that even if an entry matches the white list, a black list
+ * entry will override it.
+ * <p>
+ * <p>
+ * You can change white list policy setting whiteListByPath to true. In this mode a request will be white listed
+ * IF it has a matching URL in the white list, otherwise the black list applies, e.g. in default mode when
+ * whiteListByPath = false and wl = "127.0.0.1|/foo", /bar request from 127.0.0.1 will be blacklisted,
+ * if whiteListByPath=true then not.
+ * </p>
+ * Internet addresses may be specified as absolute address or as a combination of
+ * four octet wildcard specifications (a.b.c.d) that are defined as follows.
+ * </p>
+ * <pre>
+ * nnn - an absolute value (0-255)
+ * mmm-nnn - an inclusive range of absolute values,
+ * with following shorthand notations:
+ * nnn- => nnn-255
+ * -nnn => 0-nnn
+ * - => 0-255
+ * a,b,... - a list of wildcard specifications
+ * </pre>
+ * <p>
+ * Internet address specification is separated from the URI pattern using the "|" (pipe)
+ * character. URI patterns follow the servlet specification for simple * prefix and
+ * suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).
+ * <p>
+ * Earlier versions of the handler used internet address prefix wildcard specification
+ * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
+ * They also used the first "/" character of the URI pattern to separate it from the
+ * internet address. Both of these features have been deprecated in the current version.
+ * <p>
+ * Examples of the entry specifications are:
+ * <ul>
+ * <li>10.10.1.2 - all requests from IP 10.10.1.2
+ * <li>10.10.1.2|/foo/bar - all requests from IP 10.10.1.2 to URI /foo/bar
+ * <li>10.10.1.2|/foo/* - all requests from IP 10.10.1.2 to URIs starting with /foo/
+ * <li>10.10.1.2|*.html - all requests from IP 10.10.1.2 to URIs ending with .html
+ * <li>10.10.0-255.0-255 - all requests from IPs within 10.10.0.0/16 subnet
+ * <li>10.10.0-.-255|/foo/bar - all requests from IPs within 10.10.0.0/16 subnet to URI /foo/bar
+ * <li>10.10.0-3,1,3,7,15|/foo/* - all requests from IPs addresses with last octet equal
+ * to 1,3,7,15 in subnet 10.10.0.0/22 to URIs starting with /foo/
+ * </ul>
+ * <p>
+ * Earlier versions of the handler used internet address prefix wildcard specification
+ * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
+ * They also used the first "/" character of the URI pattern to separate it from the
+ * internet address. Both of these features have been deprecated in the current version.
+ */
+public class IPAccessHandler extends HandlerWrapper
+{
+ private static final Logger LOG = Log.getLogger(IPAccessHandler.class);
+ // true means nodefault match
+ PathMap<IPAddressMap<Boolean>> _white = new PathMap<IPAddressMap<Boolean>>(true);
+ PathMap<IPAddressMap<Boolean>> _black = new PathMap<IPAddressMap<Boolean>>(true);
+ boolean _whiteListByPath = false;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Creates new handler object
+ */
+ public IPAccessHandler()
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Creates new handler object and initializes white- and black-list
+ *
+ * @param white array of whitelist entries
+ * @param black array of blacklist entries
+ */
+ public IPAccessHandler(String[] white, String []black)
+ {
+ super();
+
+ if (white != null && white.length > 0)
+ setWhite(white);
+ if (black != null && black.length > 0)
+ setBlack(black);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add a whitelist entry to an existing handler configuration
+ *
+ * @param entry new whitelist entry
+ */
+ public void addWhite(String entry)
+ {
+ add(entry, _white);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add a blacklist entry to an existing handler configuration
+ *
+ * @param entry new blacklist entry
+ */
+ public void addBlack(String entry)
+ {
+ add(entry, _black);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Re-initialize the whitelist of existing handler object
+ *
+ * @param entries array of whitelist entries
+ */
+ public void setWhite(String[] entries)
+ {
+ set(entries, _white);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Re-initialize the blacklist of existing handler object
+ *
+ * @param entries array of blacklist entries
+ */
+ public void setBlack(String[] entries)
+ {
+ set(entries, _black);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Re-initialize the mode of path matching
+ *
+ * @param whiteListByPath matching mode
+ */
+ public void setWhiteListByPath(boolean whiteListByPath)
+ {
+ this._whiteListByPath = whiteListByPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Checks the incoming request against the whitelist and blacklist
+ *
+ * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ // Get the real remote IP (not the one set by the forwarded headers (which may be forged))
+ HttpChannel<?> channel = baseRequest.getHttpChannel();
+ if (channel!=null)
+ {
+ EndPoint endp=channel.getEndPoint();
+ if (endp!=null)
+ {
+ InetSocketAddress address = endp.getRemoteAddress();
+ if (address!=null && !isAddrUriAllowed(address.getHostString(),baseRequest.getPathInfo()))
+ {
+ response.sendError(HttpStatus.FORBIDDEN_403);
+ baseRequest.setHandled(true);
+ return;
+ }
+ }
+ }
+
+ getHandler().handle(target,baseRequest, request, response);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Helper method to parse the new entry and add it to
+ * the specified address pattern map.
+ *
+ * @param entry new entry
+ * @param patternMap target address pattern map
+ */
+ protected void add(String entry, PathMap<IPAddressMap<Boolean>> patternMap)
+ {
+ if (entry != null && entry.length() > 0)
+ {
+ boolean deprecated = false;
+ int idx;
+ if (entry.indexOf('|') > 0 )
+ {
+ idx = entry.indexOf('|');
+ }
+ else
+ {
+ idx = entry.indexOf('/');
+ deprecated = (idx >= 0);
+ }
+
+ String addr = idx > 0 ? entry.substring(0,idx) : entry;
+ String path = idx > 0 ? entry.substring(idx) : "/*";
+
+ if (addr.endsWith("."))
+ deprecated = true;
+ if (path!=null && (path.startsWith("|") || path.startsWith("/*.")))
+ path=path.substring(1);
+
+ IPAddressMap<Boolean> addrMap = patternMap.get(path);
+ if (addrMap == null)
+ {
+ addrMap = new IPAddressMap<Boolean>();
+ patternMap.put(path,addrMap);
+ }
+ if (addr != null && !"".equals(addr))
+ // MUST NOT BE null
+ addrMap.put(addr, true);
+
+ if (deprecated)
+ LOG.debug(toString() +" - deprecated specification syntax: "+entry);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Helper method to process a list of new entries and replace
+ * the content of the specified address pattern map
+ *
+ * @param entries new entries
+ * @param patternMap target address pattern map
+ */
+ protected void set(String[] entries, PathMap<IPAddressMap<Boolean>> patternMap)
+ {
+ patternMap.clear();
+
+ if (entries != null && entries.length > 0)
+ {
+ for (String addrPath:entries)
+ {
+ add(addrPath, patternMap);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Check if specified request is allowed by current IPAccess rules.
+ *
+ * @param addr internet address
+ * @param path context path
+ * @return true if request is allowed
+ *
+ */
+ protected boolean isAddrUriAllowed(String addr, String path)
+ {
+ if (_white.size()>0)
+ {
+ boolean match = false;
+ boolean matchedByPath = false;
+
+ for (Map.Entry<String,IPAddressMap<Boolean>> entry : _white.getMatches(path))
+ {
+ matchedByPath=true;
+ IPAddressMap<Boolean> addrMap = entry.getValue();
+ if ((addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null)))
+ {
+ match=true;
+ break;
+ }
+ }
+
+ if (_whiteListByPath)
+ {
+ if (matchedByPath && !match)
+ return false;
+ }
+ else
+ {
+ if (!match)
+ return false;
+ }
+ }
+
+ if (_black.size() > 0)
+ {
+ for (Map.Entry<String,IPAddressMap<Boolean>> entry : _black.getMatches(path))
+ {
+ IPAddressMap<Boolean> addrMap = entry.getValue();
+ if (addrMap!=null && (addrMap.size()==0 || addrMap.match(addr)!=null))
+ return false;
+ }
+
+ }
+
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Dump the handler configuration
+ */
+ @Override
+ public String dump()
+ {
+ StringBuilder buf = new StringBuilder();
+
+ buf.append(toString());
+ buf.append(" WHITELIST:\n");
+ dump(buf, _white);
+ buf.append(toString());
+ buf.append(" BLACKLIST:\n");
+ dump(buf, _black);
+
+ return buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Dump a pattern map into a StringBuilder buffer
+ *
+ * @param buf buffer
+ * @param patternMap pattern map to dump
+ */
+ protected void dump(StringBuilder buf, PathMap<IPAddressMap<Boolean>> patternMap)
+ {
+ for (String path: patternMap.keySet())
+ {
+ for (String addr: patternMap.get(path).keySet())
+ {
+ buf.append("# ");
+ buf.append(addr);
+ buf.append("|");
+ buf.append(path);
+ buf.append("\n");
+ }
+ }
+ }
+ }
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.HttpConnection;
+import org.eclipse.jetty.server.Request;
+
+/**
+ * Handler to adjust the idle timeout of requests while dispatched.
+ * Can be applied in jetty.xml with
+ * <pre>
+ * <Get id='handler' name='Handler'/>
+ * <Set name='Handler'>
+ * <New id='idleTimeoutHandler' class='org.eclipse.jetty.server.handler.IdleTimeoutHandler'>
+ * <Set name='Handler'><Ref id='handler'/></Set>
+ * <Set name='IdleTimeoutMs'>5000</Set>
+ * </New>
+ * </Set>
+ * </pre>
+ */
+public class IdleTimeoutHandler extends HandlerWrapper
+{
+ private long _idleTimeoutMs = 1000;
+ private boolean _applyToAsync = false;
+
+ public boolean isApplyToAsync()
+ {
+ return _applyToAsync;
+ }
+
+ /**
+ * Should the adjusted idle time be maintained for asynchronous requests
+ * @param applyToAsync true if alternate idle timeout is applied to asynchronous requests
+ */
+ public void setApplyToAsync(boolean applyToAsync)
+ {
+ _applyToAsync = applyToAsync;
+ }
+
+ public long getIdleTimeoutMs()
+ {
+ return _idleTimeoutMs;
+ }
+
+ /**
+ * @param idleTimeoutMs The idle timeout in MS to apply while dispatched or async
+ */
+ public void setIdleTimeoutMs(long idleTimeoutMs)
+ {
+ this._idleTimeoutMs = idleTimeoutMs;
+ }
+
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ HttpConnection connection = HttpConnection.getCurrentConnection();
+ final EndPoint endp = connection==null?null:connection.getEndPoint();
+
+ final long idle_timeout;
+ if (endp==null)
+ idle_timeout=-1;
+ else
+ {
+ idle_timeout=endp.getIdleTimeout();
+ endp.setIdleTimeout(_idleTimeoutMs);
+ }
+
+ try
+ {
+ super.handle(target,baseRequest,request,response);
+ }
+ finally
+ {
+ if (endp!=null)
+ {
+ if (_applyToAsync && request.isAsyncStarted())
+ {
+ request.getAsyncContext().addListener(new AsyncListener()
+ {
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ endp.setIdleTimeout(idle_timeout);
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ endp.setIdleTimeout(idle_timeout);
+ }
+ });
+ }
+ else
+ endp.setIdleTimeout(idle_timeout);
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.URIUtil;
+
+/* ------------------------------------------------------------ */
+/** Moved ContextHandler.
+ * This context can be used to replace a context that has changed
+ * location. Requests are redirected (either to a fixed URL or to a
+ * new context base).
+ */
+public class MovedContextHandler extends ContextHandler
+{
+ final Redirector _redirector;
+ String _newContextURL;
+ boolean _discardPathInfo;
+ boolean _discardQuery;
+ boolean _permanent;
+ String _expires;
+
+ public MovedContextHandler()
+ {
+ _redirector=new Redirector();
+ setHandler(_redirector);
+ setAllowNullPathInfo(true);
+ }
+
+ public MovedContextHandler(HandlerContainer parent, String contextPath, String newContextURL)
+ {
+ super(parent,contextPath);
+ _newContextURL=newContextURL;
+ _redirector=new Redirector();
+ setHandler(_redirector);
+ }
+
+ public boolean isDiscardPathInfo()
+ {
+ return _discardPathInfo;
+ }
+
+ public void setDiscardPathInfo(boolean discardPathInfo)
+ {
+ _discardPathInfo = discardPathInfo;
+ }
+
+ public String getNewContextURL()
+ {
+ return _newContextURL;
+ }
+
+ public void setNewContextURL(String newContextURL)
+ {
+ _newContextURL = newContextURL;
+ }
+
+ public boolean isPermanent()
+ {
+ return _permanent;
+ }
+
+ public void setPermanent(boolean permanent)
+ {
+ _permanent = permanent;
+ }
+
+ public boolean isDiscardQuery()
+ {
+ return _discardQuery;
+ }
+
+ public void setDiscardQuery(boolean discardQuery)
+ {
+ _discardQuery = discardQuery;
+ }
+
+ private class Redirector extends AbstractHandler
+ {
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (_newContextURL==null)
+ return;
+
+ String path=_newContextURL;
+ if (!_discardPathInfo && request.getPathInfo()!=null)
+ path=URIUtil.addPaths(path, request.getPathInfo());
+
+ StringBuilder location = URIUtil.hasScheme(path)?new StringBuilder():baseRequest.getRootURL();
+
+ location.append(path);
+ if (!_discardQuery && request.getQueryString()!=null)
+ {
+ location.append('?');
+ String q=request.getQueryString();
+ q=q.replaceAll("\r\n?&=","!");
+ location.append(q);
+ }
+
+ response.setHeader(HttpHeader.LOCATION.asString(),location.toString());
+
+ if (_expires!=null)
+ response.setHeader(HttpHeader.EXPIRES.asString(),_expires);
+
+ response.setStatus(_permanent?HttpServletResponse.SC_MOVED_PERMANENTLY:HttpServletResponse.SC_FOUND);
+ response.setContentLength(0);
+ baseRequest.setHandled(true);
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the expires header value or null if no expires header
+ */
+ public String getExpires()
+ {
+ return _expires;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param expires the expires header value or null if no expires header
+ */
+ public void setExpires(String expires)
+ {
+ _expires = expires;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AsyncContextState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/**
+ * RequestLogHandler.
+ * This handler can be used to wrap an individual context for context logging.
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class RequestLogHandler extends HandlerWrapper
+{
+ private static final Logger LOG = Log.getLogger(RequestLogHandler.class);
+ private RequestLog _requestLog;
+ private final AsyncListener _listener = new AsyncListener()
+ {
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ event.getAsyncContext().addListener(this);
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ AsyncContextState context = (AsyncContextState)event.getAsyncContext();
+ Request request=context.getHttpChannelState().getBaseRequest();
+ Response response=request.getResponse();
+ _requestLog.log(request,response);
+ }
+ };
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ try
+ {
+ super.handle(target, baseRequest, request, response);
+ }
+ finally
+ {
+ if (_requestLog != null && baseRequest.getDispatcherType().equals(DispatcherType.REQUEST))
+ {
+ if (baseRequest.getHttpChannelState().isAsync())
+ {
+ if (baseRequest.getHttpChannelState().isInitial())
+ baseRequest.getAsyncContext().addListener(_listener);
+ }
+ else
+ _requestLog.log(baseRequest, (Response)response);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRequestLog(RequestLog requestLog)
+ {
+ updateBean(_requestLog,requestLog);
+ _requestLog=requestLog;
+ }
+
+ /* ------------------------------------------------------------ */
+ public RequestLog getRequestLog()
+ {
+ return _requestLog;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (_requestLog==null)
+ {
+ LOG.warn("!RequestLog");
+ _requestLog=new NullRequestLog();
+ }
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_requestLog instanceof NullRequestLog)
+ _requestLog=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class NullRequestLog extends AbstractLifeCycle implements RequestLog
+ {
+ @Override
+ public void log(Request request, Response response)
+ {
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.FileResource;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Resource Handler.
+ *
+ * This handle will serve static content and handle If-Modified-Since headers.
+ * No caching is done.
+ * Requests for resources that do not exist are let pass (Eg no 404's).
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class ResourceHandler extends HandlerWrapper
+{
+ private static final Logger LOG = Log.getLogger(ResourceHandler.class);
+
+ ContextHandler _context;
+ Resource _baseResource;
+ Resource _defaultStylesheet;
+ Resource _stylesheet;
+ String[] _welcomeFiles={"index.html"};
+ MimeTypes _mimeTypes = new MimeTypes();
+ String _cacheControl;
+ boolean _directory;
+ boolean _etags;
+ int _minMemoryMappedContentLength=-1;
+ int _minAsyncContentLength=0;
+
+ /* ------------------------------------------------------------ */
+ public ResourceHandler()
+ {
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public MimeTypes getMimeTypes()
+ {
+ return _mimeTypes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setMimeTypes(MimeTypes mimeTypes)
+ {
+ _mimeTypes = mimeTypes;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the directory option.
+ * @return true if directories are listed.
+ */
+ public boolean isDirectoriesListed()
+ {
+ return _directory;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the directory.
+ * @param directory true if directories are listed.
+ */
+ public void setDirectoriesListed(boolean directory)
+ {
+ _directory = directory;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get minimum memory mapped file content length.
+ * @return the minimum size in bytes of a file resource that will
+ * be served using a memory mapped buffer, or -1 (default) for no memory mapped
+ * buffers.
+ */
+ public int getMinMemoryMappedContentLength()
+ {
+ return _minMemoryMappedContentLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set minimum memory mapped file content length.
+ * @param minMemoryMappedFileSize the minimum size in bytes of a file resource that will
+ * be served using a memory mapped buffer, or -1 for no memory mapped
+ * buffers.
+ */
+ public void setMinMemoryMappedContentLength(int minMemoryMappedFileSize)
+ {
+ _minMemoryMappedContentLength = minMemoryMappedFileSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the minimum content length for async handling.
+ * @return The minimum size in bytes of the content before asynchronous
+ * handling is used, or -1 for no async handling or 0 (default) for using
+ * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+ */
+ public int getMinAsyncContentLength()
+ {
+ return _minAsyncContentLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the minimum content length for async handling.
+ * @param minAsyncContentLength The minimum size in bytes of the content before asynchronous
+ * handling is used, or -1 for no async handling or 0 for using
+ * {@link HttpServletResponse#getBufferSize()} as the minimum length.
+ */
+ public void setMinAsyncContentLength(int minAsyncContentLength)
+ {
+ _minAsyncContentLength = minAsyncContentLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if ETag processing is done
+ */
+ public boolean isEtags()
+ {
+ return _etags;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param etags True if ETag processing is done
+ */
+ public void setEtags(boolean etags)
+ {
+ _etags = etags;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doStart()
+ throws Exception
+ {
+ Context scontext = ContextHandler.getCurrentContext();
+ _context = (scontext==null?null:scontext.getContextHandler());
+
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the resourceBase.
+ */
+ public Resource getBaseResource()
+ {
+ if (_baseResource==null)
+ return null;
+ return _baseResource;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the base resource as a string.
+ */
+ public String getResourceBase()
+ {
+ if (_baseResource==null)
+ return null;
+ return _baseResource.toString();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param base The resourceBase to set.
+ */
+ public void setBaseResource(Resource base)
+ {
+ _baseResource=base;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param resourceBase The base resource as a string.
+ */
+ public void setResourceBase(String resourceBase)
+ {
+ try
+ {
+ setBaseResource(Resource.newResource(resourceBase));
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ throw new IllegalArgumentException(resourceBase);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the stylesheet as a Resource.
+ */
+ public Resource getStylesheet()
+ {
+ if(_stylesheet != null)
+ {
+ return _stylesheet;
+ }
+ else
+ {
+ if(_defaultStylesheet == null)
+ {
+ _defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+ }
+ return _defaultStylesheet;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param stylesheet The location of the stylesheet to be used as a String.
+ */
+ public void setStylesheet(String stylesheet)
+ {
+ try
+ {
+ _stylesheet = Resource.newResource(stylesheet);
+ if(!_stylesheet.exists())
+ {
+ LOG.warn("unable to find custom stylesheet: " + stylesheet);
+ _stylesheet = null;
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ throw new IllegalArgumentException(stylesheet);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the cacheControl header to set on all static content.
+ */
+ public String getCacheControl()
+ {
+ return _cacheControl;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param cacheControl the cacheControl header to set on all static content.
+ */
+ public void setCacheControl(String cacheControl)
+ {
+ _cacheControl=cacheControl;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ public Resource getResource(String path) throws MalformedURLException
+ {
+ if (path==null || !path.startsWith("/"))
+ throw new MalformedURLException(path);
+
+ Resource base = _baseResource;
+ if (base==null)
+ {
+ if (_context==null)
+ return null;
+ base=_context.getBaseResource();
+ if (base==null)
+ return null;
+ }
+
+ try
+ {
+ path=URIUtil.canonicalPath(path);
+ return base.addPath(path);
+ }
+ catch(Exception e)
+ {
+ LOG.ignore(e);
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Resource getResource(HttpServletRequest request) throws MalformedURLException
+ {
+ String servletPath;
+ String pathInfo;
+ Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
+ if (included != null && included.booleanValue())
+ {
+ servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+
+ if (servletPath == null && pathInfo == null)
+ {
+ servletPath = request.getServletPath();
+ pathInfo = request.getPathInfo();
+ }
+ }
+ else
+ {
+ servletPath = request.getServletPath();
+ pathInfo = request.getPathInfo();
+ }
+
+ String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+ return getResource(pathInContext);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String[] getWelcomeFiles()
+ {
+ return _welcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setWelcomeFiles(String[] welcomeFiles)
+ {
+ _welcomeFiles=welcomeFiles;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
+ {
+ for (int i=0;i<_welcomeFiles.length;i++)
+ {
+ Resource welcome=directory.addPath(_welcomeFiles[i]);
+ if (welcome.exists() && !welcome.isDirectory())
+ return welcome;
+ }
+
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (baseRequest.isHandled())
+ return;
+
+ boolean skipContentBody = false;
+
+ if(!HttpMethod.GET.is(request.getMethod()))
+ {
+ if(!HttpMethod.HEAD.is(request.getMethod()))
+ {
+ //try another handler
+ super.handle(target, baseRequest, request, response);
+ return;
+ }
+ skipContentBody = true;
+ }
+
+ Resource resource = getResource(request);
+ // If resource is not found
+ if (resource==null || !resource.exists())
+ {
+ // inject the jetty-dir.css file if it matches
+ if (target.endsWith("/jetty-dir.css"))
+ {
+ resource = getStylesheet();
+ if (resource==null)
+ return;
+ response.setContentType("text/css");
+ }
+ else
+ {
+ //no resource - try other handlers
+ super.handle(target, baseRequest, request, response);
+ return;
+ }
+ }
+
+ // We are going to serve something
+ baseRequest.setHandled(true);
+
+ // handle directories
+ if (resource.isDirectory())
+ {
+ if (!request.getPathInfo().endsWith(URIUtil.SLASH))
+ {
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
+ return;
+ }
+
+ Resource welcome=getWelcome(resource);
+ if (welcome!=null && welcome.exists())
+ resource=welcome;
+ else
+ {
+ doDirectory(request,response,resource);
+ baseRequest.setHandled(true);
+ return;
+ }
+ }
+
+ // Handle ETAGS
+ long last_modified=resource.lastModified();
+ String etag=null;
+ if (_etags)
+ {
+ // simple handling of only a single etag
+ String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+ etag=resource.getWeakETag();
+ if (ifnm!=null && resource!=null && ifnm.equals(etag))
+ {
+ response.setStatus(HttpStatus.NOT_MODIFIED_304);
+ baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+ return;
+ }
+ }
+
+ // Handle if modified since
+ if (last_modified>0)
+ {
+ long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+ if (if_modified>0 && last_modified/1000<=if_modified/1000)
+ {
+ response.setStatus(HttpStatus.NOT_MODIFIED_304);
+ return;
+ }
+ }
+
+ // set the headers
+ String mime=_mimeTypes.getMimeByExtension(resource.toString());
+ if (mime==null)
+ mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
+ doResponseHeaders(response,resource,mime);
+ if (_etags)
+ baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+
+ if(skipContentBody)
+ return;
+
+
+ // Send the content
+ OutputStream out =null;
+ try {out = response.getOutputStream();}
+ catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
+
+ // Has the output been wrapped
+ if (!(out instanceof HttpOutput))
+ // Write content via wrapped output
+ resource.writeTo(out,0,resource.length());
+ else
+ {
+ // select async by size
+ int min_async_size=_minAsyncContentLength==0?response.getBufferSize():_minAsyncContentLength;
+
+ if (request.isAsyncSupported() &&
+ min_async_size>0 &&
+ resource.length()>=min_async_size)
+ {
+ final AsyncContext async = request.startAsync();
+ Callback callback = new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ async.complete();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ LOG.warn(x.toString());
+ LOG.debug(x);
+ async.complete();
+ }
+ };
+
+ // Can we use a memory mapped file?
+ if (_minMemoryMappedContentLength>0 &&
+ resource.length()>_minMemoryMappedContentLength &&
+ resource instanceof FileResource)
+ {
+ ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+ ((HttpOutput)out).sendContent(buffer,callback);
+ }
+ else // Do a blocking write of a channel (if available) or input stream
+ {
+ // Close of the channel/inputstream is done by the async sendContent
+ ReadableByteChannel channel= resource.getReadableByteChannel();
+ if (channel!=null)
+ ((HttpOutput)out).sendContent(channel,callback);
+ else
+ ((HttpOutput)out).sendContent(resource.getInputStream(),callback);
+ }
+ }
+ else
+ {
+ // Can we use a memory mapped file?
+ if (_minMemoryMappedContentLength>0 &&
+ resource.length()>_minMemoryMappedContentLength &&
+ resource instanceof FileResource)
+ {
+ ByteBuffer buffer = BufferUtil.toMappedBuffer(resource.getFile());
+ ((HttpOutput)out).sendContent(buffer);
+ }
+ else // Do a blocking write of a channel (if available) or input stream
+ {
+ ReadableByteChannel channel= resource.getReadableByteChannel();
+ if (channel!=null)
+ ((HttpOutput)out).sendContent(channel);
+ else
+ ((HttpOutput)out).sendContent(resource.getInputStream());
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
+ throws IOException
+ {
+ if (_directory)
+ {
+ String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
+ response.setContentType("text/html; charset=UTF-8");
+ response.getWriter().println(listing);
+ }
+ else
+ response.sendError(HttpStatus.FORBIDDEN_403);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the response headers.
+ * This method is called to set the response headers such as content type and content length.
+ * May be extended to add additional headers.
+ * @param response
+ * @param resource
+ * @param mimeType
+ */
+ protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
+ {
+ if (mimeType!=null)
+ response.setContentType(mimeType);
+
+ long length=resource.length();
+
+ if (response instanceof Response)
+ {
+ HttpFields fields = ((Response)response).getHttpFields();
+
+ if (length>0)
+ ((Response)response).setLongContentLength(length);
+
+ if (_cacheControl!=null)
+ fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
+ }
+ else
+ {
+ if (length>Integer.MAX_VALUE)
+ response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
+ else if (length>0)
+ response.setContentLength((int)length);
+
+ if (_cacheControl!=null)
+ response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+
+
+/* ------------------------------------------------------------ */
+/** ScopedHandler.
+ *
+ * A ScopedHandler is a HandlerWrapper where the wrapped handlers
+ * each define a scope.
+ *
+ * <p>When {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * is called on the first ScopedHandler in a chain of HandlerWrappers,
+ * the {@link #doScope(String, Request, HttpServletRequest, HttpServletResponse)} method is
+ * called on all contained ScopedHandlers, before the
+ * {@link #doHandle(String, Request, HttpServletRequest, HttpServletResponse)} method
+ * is called on all contained handlers.</p>
+ *
+ * <p>For example if Scoped handlers A, B & C were chained together, then
+ * the calling order would be:</p>
+ * <pre>
+ * A.handle(...)
+ * A.doScope(...)
+ * B.doScope(...)
+ * C.doScope(...)
+ * A.doHandle(...)
+ * B.doHandle(...)
+ * C.doHandle(...)
+ * </pre>
+ *
+ * <p>If non scoped handler X was in the chained A, B, X & C, then
+ * the calling order would be:</p>
+ * <pre>
+ * A.handle(...)
+ * A.doScope(...)
+ * B.doScope(...)
+ * C.doScope(...)
+ * A.doHandle(...)
+ * B.doHandle(...)
+ * X.handle(...)
+ * C.handle(...)
+ * C.doHandle(...)
+ * </pre>
+ *
+ * <p>A typical usage pattern is:</p>
+ * <pre>
+ * private static class MyHandler extends ScopedHandler
+ * {
+ * public void doScope(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ * {
+ * try
+ * {
+ * setUpMyScope();
+ * super.doScope(target,request,response);
+ * }
+ * finally
+ * {
+ * tearDownMyScope();
+ * }
+ * }
+ *
+ * public void doHandle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ * {
+ * try
+ * {
+ * doMyHandling();
+ * super.doHandle(target,request,response);
+ * }
+ * finally
+ * {
+ * cleanupMyHandling();
+ * }
+ * }
+ * }
+ * </pre>
+ */
+public abstract class ScopedHandler extends HandlerWrapper
+{
+ private static final ThreadLocal<ScopedHandler> __outerScope= new ThreadLocal<ScopedHandler>();
+ protected ScopedHandler _outerScope;
+ protected ScopedHandler _nextScope;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ try
+ {
+ _outerScope=__outerScope.get();
+ if (_outerScope==null)
+ __outerScope.set(this);
+
+ super.doStart();
+
+ _nextScope= getChildHandlerByClass(ScopedHandler.class);
+
+ }
+ finally
+ {
+ if (_outerScope==null)
+ __outerScope.set(null);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ */
+ @Override
+ public final void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (isStarted())
+ {
+ if (_outerScope==null)
+ doScope(target,baseRequest,request, response);
+ else
+ doHandle(target,baseRequest,request, response);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Scope the handler
+ */
+ public abstract void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException;
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Scope the handler
+ */
+ public final void nextScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ // this method has been manually inlined in several locations, but
+ // is called protected by an if(never()), so your IDE can find those
+ // locations if this code is changed.
+ if (_nextScope!=null)
+ _nextScope.doScope(target,baseRequest,request, response);
+ else if (_outerScope!=null)
+ _outerScope.doHandle(target,baseRequest,request, response);
+ else
+ doHandle(target,baseRequest,request, response);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Do the handler work within the scope.
+ */
+ public abstract void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException;
+
+ /* ------------------------------------------------------------ */
+ /*
+ * Do the handler work within the scope.
+ */
+ public final void nextHandle(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ // this method has been manually inlined in several locations, but
+ // is called protected by an if(never()), so your IDE can find those
+ // locations if this code is changed.
+ if (_nextScope!=null && _nextScope==_handler)
+ _nextScope.doHandle(target,baseRequest,request, response);
+ else if (_handler!=null)
+ _handler.handle(target,baseRequest, request, response);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected boolean never()
+ {
+ return false;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java. If _exitJvm ist set to true a hard System.exit() call is being
+ * made.
+ *
+ * This handler is a contribution from Johannes Brodwall: https://bugs.eclipse.org/bugs/show_bug.cgi?id=357687
+ *
+ * Usage:
+ *
+ * <pre>
+ Server server = new Server(8080);
+ HandlerList handlers = new HandlerList();
+ handlers.setHandlers(new Handler[]
+ { someOtherHandler, new ShutdownHandler("secret password") });
+ server.setHandler(handlers);
+ server.start();
+ </pre>
+ *
+ <pre>
+ public static void attemptShutdown(int port, String shutdownCookie) {
+ try {
+ URL url = new URL("http://localhost:" + port + "/shutdown?token=" + shutdownCookie);
+ HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+ connection.setRequestMethod("POST");
+ connection.getResponseCode();
+ logger.info("Shutting down " + url + ": " + connection.getResponseMessage());
+ } catch (SocketException e) {
+ logger.debug("Not running");
+ // Okay - the server is not running
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ </pre>
+ */
+public class ShutdownHandler extends HandlerWrapper
+{
+ private static final Logger LOG = Log.getLogger(ShutdownHandler.class);
+
+ private final String _shutdownToken;
+ private boolean _sendShutdownAtStart;
+ private boolean _exitJvm = false;
+
+
+ /**
+ * Creates a listener that lets the server be shut down remotely (but only from localhost).
+ *
+ * @param server
+ * the Jetty instance that should be shut down
+ * @param shutdownToken
+ * a secret password to avoid unauthorized shutdown attempts
+ */
+ @Deprecated
+ public ShutdownHandler(Server server, String shutdownToken)
+ {
+ this(shutdownToken);
+ }
+
+ public ShutdownHandler(String shutdownToken)
+ {
+ this(shutdownToken,false,false);
+ }
+
+ /**
+ * @param shutdownToken
+ * @param sendShutdownAtStart If true, a shutdown is sent as a HTTP post
+ * during startup, which will shutdown any previously running instances of
+ * this server with an identically configured ShutdownHandler
+ */
+ public ShutdownHandler(String shutdownToken, boolean exitJVM, boolean sendShutdownAtStart)
+ {
+ this._shutdownToken = shutdownToken;
+ setExitJvm(exitJVM);
+ setSendShutdownAtStart(sendShutdownAtStart);
+ }
+
+
+ public void sendShutdown() throws IOException
+ {
+ URL url = new URL(getServerUrl() + "/shutdown?token=" + _shutdownToken);
+ try
+ {
+ HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+ connection.setRequestMethod("POST");
+ connection.getResponseCode();
+ LOG.info("Shutting down " + url + ": " + connection.getResponseMessage());
+ }
+ catch (SocketException e)
+ {
+ LOG.debug("Not running");
+ // Okay - the server is not running
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String getServerUrl()
+ {
+ NetworkConnector connector=null;
+ for (Connector c: getServer().getConnectors())
+ {
+ if (c instanceof NetworkConnector)
+ {
+ connector=(NetworkConnector)c;
+ break;
+ }
+ }
+
+ if (connector==null)
+ return "http://localhost";
+
+ return "http://localhost:" + connector.getPort();
+ }
+
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ if (_sendShutdownAtStart)
+ sendShutdown();
+ }
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (!target.equals("/shutdown"))
+ {
+ super.handle(target,baseRequest,request,response);
+ return;
+ }
+
+ if (!request.getMethod().equals("POST"))
+ {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+ if (!hasCorrectSecurityToken(request))
+ {
+ LOG.warn("Unauthorized tokenless shutdown attempt from " + request.getRemoteAddr());
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
+ }
+ if (!requestFromLocalhost(baseRequest))
+ {
+ LOG.warn("Unauthorized non-loopback shutdown attempt from " + request.getRemoteAddr());
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ return;
+ }
+
+ LOG.info("Shutting down by request from " + request.getRemoteAddr());
+
+ final Server server=getServer();
+ new Thread()
+ {
+ @Override
+ public void run ()
+ {
+ try
+ {
+ shutdownServer(server);
+ }
+ catch (InterruptedException e)
+ {
+ LOG.ignore(e);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Shutting down server",e);
+ }
+ }
+ }.start();
+ }
+
+ private boolean requestFromLocalhost(Request request)
+ {
+ InetSocketAddress addr = request.getRemoteInetSocketAddress();
+ if (addr == null)
+ {
+ return false;
+ }
+ return addr.getAddress().isLoopbackAddress();
+ }
+
+ private boolean hasCorrectSecurityToken(HttpServletRequest request)
+ {
+ String tok = request.getParameter("token");
+ LOG.debug("Token: {}", tok);
+ return _shutdownToken.equals(tok);
+ }
+
+ private void shutdownServer(Server server) throws Exception
+ {
+ server.stop();
+
+ if (_exitJvm)
+ {
+ System.exit(0);
+ }
+ }
+
+ public void setExitJvm(boolean exitJvm)
+ {
+ this._exitJvm = exitJvm;
+ }
+
+ public boolean isSendShutdownAtStart()
+ {
+ return _sendShutdownAtStart;
+ }
+
+ public void setSendShutdownAtStart(boolean sendShutdownAtStart)
+ {
+ _sendShutdownAtStart = sendShutdownAtStart;
+ }
+
+ public String getShutdownToken()
+ {
+ return _shutdownToken;
+ }
+
+ public boolean isExitJvm()
+ {
+ return _exitJvm;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.handler;
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AsyncContextEvent;
+import org.eclipse.jetty.server.HttpChannelState;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.Graceful;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+@ManagedObject("Request Statistics Gathering")
+public class StatisticsHandler extends HandlerWrapper implements Graceful
+{
+ private final AtomicLong _statsStartedAt = new AtomicLong();
+
+ private final CounterStatistic _requestStats = new CounterStatistic();
+ private final SampleStatistic _requestTimeStats = new SampleStatistic();
+ private final CounterStatistic _dispatchedStats = new CounterStatistic();
+ private final SampleStatistic _dispatchedTimeStats = new SampleStatistic();
+ private final CounterStatistic _asyncWaitStats = new CounterStatistic();
+
+ private final AtomicInteger _asyncDispatches = new AtomicInteger();
+ private final AtomicInteger _expires = new AtomicInteger();
+
+ private final AtomicInteger _responses1xx = new AtomicInteger();
+ private final AtomicInteger _responses2xx = new AtomicInteger();
+ private final AtomicInteger _responses3xx = new AtomicInteger();
+ private final AtomicInteger _responses4xx = new AtomicInteger();
+ private final AtomicInteger _responses5xx = new AtomicInteger();
+ private final AtomicLong _responsesTotalBytes = new AtomicLong();
+
+ private final AtomicReference<FutureCallback> _shutdown=new AtomicReference<>();
+
+ private final AsyncListener _onCompletion = new AsyncListener()
+ {
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ _expires.incrementAndGet();
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ event.getAsyncContext().addListener(this);
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState();
+
+ Request request = state.getBaseRequest();
+ final long elapsed = System.currentTimeMillis()-request.getTimeStamp();
+
+ long d=_requestStats.decrement();
+ _requestTimeStats.set(elapsed);
+
+ updateResponse(request);
+
+ _asyncWaitStats.decrement();
+
+ // If we have no more dispatches, should we signal shutdown?
+ if (d==0)
+ {
+ FutureCallback shutdown = _shutdown.get();
+ if (shutdown!=null)
+ shutdown.succeeded();
+ }
+ }
+ };
+
+ /**
+ * Resets the current request statistics.
+ */
+ @ManagedOperation(value="resets statistics", impact="ACTION")
+ public void statsReset()
+ {
+ _statsStartedAt.set(System.currentTimeMillis());
+
+ _requestStats.reset();
+ _requestTimeStats.reset();
+ _dispatchedStats.reset();
+ _dispatchedTimeStats.reset();
+ _asyncWaitStats.reset();
+
+ _asyncDispatches.set(0);
+ _expires.set(0);
+ _responses1xx.set(0);
+ _responses2xx.set(0);
+ _responses3xx.set(0);
+ _responses4xx.set(0);
+ _responses5xx.set(0);
+ _responsesTotalBytes.set(0L);
+ }
+
+ @Override
+ public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+ {
+ _dispatchedStats.increment();
+
+ final long start;
+ HttpChannelState state = request.getHttpChannelState();
+ if (state.isInitial())
+ {
+ // new request
+ _requestStats.increment();
+ start = request.getTimeStamp();
+ }
+ else
+ {
+ // resumed request
+ start = System.currentTimeMillis();
+ _asyncDispatches.incrementAndGet();
+ }
+
+ try
+ {
+ super.handle(path, request, httpRequest, httpResponse);
+ }
+ finally
+ {
+ final long now = System.currentTimeMillis();
+ final long dispatched=now-start;
+
+ _dispatchedStats.decrement();
+ _dispatchedTimeStats.set(dispatched);
+
+ if (state.isSuspended())
+ {
+ if (state.isInitial())
+ {
+ state.addListener(_onCompletion);
+ _asyncWaitStats.increment();
+ }
+ }
+ else if (state.isInitial())
+ {
+ long d=_requestStats.decrement();
+ _requestTimeStats.set(dispatched);
+ updateResponse(request);
+
+ // If we have no more dispatches, should we signal shutdown?
+ FutureCallback shutdown = _shutdown.get();
+ if (shutdown!=null)
+ {
+ httpResponse.flushBuffer();
+ if (d==0)
+ shutdown.succeeded();
+ }
+ }
+ // else onCompletion will handle it.
+ }
+ }
+
+ private void updateResponse(Request request)
+ {
+ Response response = request.getResponse();
+ switch (response.getStatus() / 100)
+ {
+ case 0:
+ if (request.isHandled())
+ _responses2xx.incrementAndGet();
+ else
+ _responses4xx.incrementAndGet();
+ break;
+ case 1:
+ _responses1xx.incrementAndGet();
+ break;
+ case 2:
+ _responses2xx.incrementAndGet();
+ break;
+ case 3:
+ _responses3xx.incrementAndGet();
+ break;
+ case 4:
+ _responses4xx.incrementAndGet();
+ break;
+ case 5:
+ _responses5xx.incrementAndGet();
+ break;
+ default:
+ break;
+ }
+ _responsesTotalBytes.addAndGet(response.getContentCount());
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ _shutdown.set(null);
+ super.doStart();
+ statsReset();
+ }
+
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ FutureCallback shutdown = _shutdown.get();
+ if (shutdown!=null && !shutdown.isDone())
+ shutdown.failed(new TimeoutException());
+ }
+
+ /**
+ * @return the number of requests handled by this handler
+ * since {@link #statsReset()} was last called, excluding
+ * active requests
+ * @see #getAsyncDispatches()
+ */
+ @ManagedAttribute("number of requests")
+ public int getRequests()
+ {
+ return (int)_requestStats.getTotal();
+ }
+
+ /**
+ * @return the number of requests currently active.
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("number of requests currently active")
+ public int getRequestsActive()
+ {
+ return (int)_requestStats.getCurrent();
+ }
+
+ /**
+ * @return the maximum number of active requests
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("maximum number of active requests")
+ public int getRequestsActiveMax()
+ {
+ return (int)_requestStats.getMax();
+ }
+
+ /**
+ * @return the maximum time (in milliseconds) of request handling
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("maximum time spend handling requests (in ms)")
+ public long getRequestTimeMax()
+ {
+ return _requestTimeStats.getMax();
+ }
+
+ /**
+ * @return the total time (in milliseconds) of requests handling
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("total time spend in all request handling (in ms)")
+ public long getRequestTimeTotal()
+ {
+ return _requestTimeStats.getTotal();
+ }
+
+ /**
+ * @return the mean time (in milliseconds) of request handling
+ * since {@link #statsReset()} was last called.
+ * @see #getRequestTimeTotal()
+ * @see #getRequests()
+ */
+ @ManagedAttribute("mean time spent handling requests (in ms)")
+ public double getRequestTimeMean()
+ {
+ return _requestTimeStats.getMean();
+ }
+
+ /**
+ * @return the standard deviation of time (in milliseconds) of request handling
+ * since {@link #statsReset()} was last called.
+ * @see #getRequestTimeTotal()
+ * @see #getRequests()
+ */
+ @ManagedAttribute("standard deviation for request handling (in ms)")
+ public double getRequestTimeStdDev()
+ {
+ return _requestTimeStats.getStdDev();
+ }
+
+ /**
+ * @return the number of dispatches seen by this handler
+ * since {@link #statsReset()} was last called, excluding
+ * active dispatches
+ */
+ @ManagedAttribute("number of dispatches")
+ public int getDispatched()
+ {
+ return (int)_dispatchedStats.getTotal();
+ }
+
+ /**
+ * @return the number of dispatches currently in this handler
+ * since {@link #statsReset()} was last called, including
+ * resumed requests
+ */
+ @ManagedAttribute("number of dispatches currently active")
+ public int getDispatchedActive()
+ {
+ return (int)_dispatchedStats.getCurrent();
+ }
+
+ /**
+ * @return the max number of dispatches currently in this handler
+ * since {@link #statsReset()} was last called, including
+ * resumed requests
+ */
+ @ManagedAttribute("maximum number of active dispatches being handled")
+ public int getDispatchedActiveMax()
+ {
+ return (int)_dispatchedStats.getMax();
+ }
+
+ /**
+ * @return the maximum time (in milliseconds) of request dispatch
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("maximum time spend in dispatch handling")
+ public long getDispatchedTimeMax()
+ {
+ return _dispatchedTimeStats.getMax();
+ }
+
+ /**
+ * @return the total time (in milliseconds) of requests handling
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("total time spent in dispatch handling (in ms)")
+ public long getDispatchedTimeTotal()
+ {
+ return _dispatchedTimeStats.getTotal();
+ }
+
+ /**
+ * @return the mean time (in milliseconds) of request handling
+ * since {@link #statsReset()} was last called.
+ * @see #getRequestTimeTotal()
+ * @see #getRequests()
+ */
+ @ManagedAttribute("mean time spent in dispatch handling (in ms)")
+ public double getDispatchedTimeMean()
+ {
+ return _dispatchedTimeStats.getMean();
+ }
+
+ /**
+ * @return the standard deviation of time (in milliseconds) of request handling
+ * since {@link #statsReset()} was last called.
+ * @see #getRequestTimeTotal()
+ * @see #getRequests()
+ */
+ @ManagedAttribute("standard deviation for dispatch handling (in ms)")
+ public double getDispatchedTimeStdDev()
+ {
+ return _dispatchedTimeStats.getStdDev();
+ }
+
+ /**
+ * @return the number of requests handled by this handler
+ * since {@link #statsReset()} was last called, including
+ * resumed requests
+ * @see #getAsyncDispatches()
+ */
+ @ManagedAttribute("total number of async requests")
+ public int getAsyncRequests()
+ {
+ return (int)_asyncWaitStats.getTotal();
+ }
+
+ /**
+ * @return the number of requests currently suspended.
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("currently waiting async requests")
+ public int getAsyncRequestsWaiting()
+ {
+ return (int)_asyncWaitStats.getCurrent();
+ }
+
+ /**
+ * @return the maximum number of current suspended requests
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("maximum number of waiting async requests")
+ public int getAsyncRequestsWaitingMax()
+ {
+ return (int)_asyncWaitStats.getMax();
+ }
+
+ /**
+ * @return the number of requests that have been asynchronously dispatched
+ */
+ @ManagedAttribute("number of requested that have been asynchronously dispatched")
+ public int getAsyncDispatches()
+ {
+ return _asyncDispatches.get();
+ }
+
+ /**
+ * @return the number of requests that expired while suspended.
+ * @see #getAsyncDispatches()
+ */
+ @ManagedAttribute("number of async requests requests that have expired")
+ public int getExpires()
+ {
+ return _expires.get();
+ }
+
+ /**
+ * @return the number of responses with a 1xx status returned by this context
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("number of requests with 1xx response status")
+ public int getResponses1xx()
+ {
+ return _responses1xx.get();
+ }
+
+ /**
+ * @return the number of responses with a 2xx status returned by this context
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("number of requests with 2xx response status")
+ public int getResponses2xx()
+ {
+ return _responses2xx.get();
+ }
+
+ /**
+ * @return the number of responses with a 3xx status returned by this context
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("number of requests with 3xx response status")
+ public int getResponses3xx()
+ {
+ return _responses3xx.get();
+ }
+
+ /**
+ * @return the number of responses with a 4xx status returned by this context
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("number of requests with 4xx response status")
+ public int getResponses4xx()
+ {
+ return _responses4xx.get();
+ }
+
+ /**
+ * @return the number of responses with a 5xx status returned by this context
+ * since {@link #statsReset()} was last called.
+ */
+ @ManagedAttribute("number of requests with 5xx response status")
+ public int getResponses5xx()
+ {
+ return _responses5xx.get();
+ }
+
+ /**
+ * @return the milliseconds since the statistics were started with {@link #statsReset()}.
+ */
+ @ManagedAttribute("time in milliseconds stats have been collected for")
+ public long getStatsOnMs()
+ {
+ return System.currentTimeMillis() - _statsStartedAt.get();
+ }
+
+ /**
+ * @return the total bytes of content sent in responses
+ */
+ @ManagedAttribute("total number of bytes across all responses")
+ public long getResponsesBytesTotal()
+ {
+ return _responsesTotalBytes.get();
+ }
+
+ public String toStatsHTML()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("<h1>Statistics:</h1>\n");
+ sb.append("Statistics gathering started ").append(getStatsOnMs()).append("ms ago").append("<br />\n");
+
+ sb.append("<h2>Requests:</h2>\n");
+ sb.append("Total requests: ").append(getRequests()).append("<br />\n");
+ sb.append("Active requests: ").append(getRequestsActive()).append("<br />\n");
+ sb.append("Max active requests: ").append(getRequestsActiveMax()).append("<br />\n");
+ sb.append("Total requests time: ").append(getRequestTimeTotal()).append("<br />\n");
+ sb.append("Mean request time: ").append(getRequestTimeMean()).append("<br />\n");
+ sb.append("Max request time: ").append(getRequestTimeMax()).append("<br />\n");
+ sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("<br />\n");
+
+
+ sb.append("<h2>Dispatches:</h2>\n");
+ sb.append("Total dispatched: ").append(getDispatched()).append("<br />\n");
+ sb.append("Active dispatched: ").append(getDispatchedActive()).append("<br />\n");
+ sb.append("Max active dispatched: ").append(getDispatchedActiveMax()).append("<br />\n");
+ sb.append("Total dispatched time: ").append(getDispatchedTimeTotal()).append("<br />\n");
+ sb.append("Mean dispatched time: ").append(getDispatchedTimeMean()).append("<br />\n");
+ sb.append("Max dispatched time: ").append(getDispatchedTimeMax()).append("<br />\n");
+ sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("<br />\n");
+
+
+ sb.append("Total requests suspended: ").append(getAsyncRequests()).append("<br />\n");
+ sb.append("Total requests expired: ").append(getExpires()).append("<br />\n");
+ sb.append("Total requests resumed: ").append(getAsyncDispatches()).append("<br />\n");
+
+ sb.append("<h2>Responses:</h2>\n");
+ sb.append("1xx responses: ").append(getResponses1xx()).append("<br />\n");
+ sb.append("2xx responses: ").append(getResponses2xx()).append("<br />\n");
+ sb.append("3xx responses: ").append(getResponses3xx()).append("<br />\n");
+ sb.append("4xx responses: ").append(getResponses4xx()).append("<br />\n");
+ sb.append("5xx responses: ").append(getResponses5xx()).append("<br />\n");
+ sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("<br />\n");
+
+ return sb.toString();
+
+ }
+
+ @Override
+ public Future<Void> shutdown()
+ {
+ FutureCallback shutdown=new FutureCallback(false);
+ _shutdown.compareAndSet(null,shutdown);
+ shutdown=_shutdown.get();
+ if (_dispatchedStats.getCurrent()==0)
+ shutdown.succeeded();
+ return shutdown;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Server : Core Handler API
+ */
+package org.eclipse.jetty.server.handler;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.nio;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.NetworkTrafficServerConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * @deprecated use {@link org.eclipse.jetty.server.NetworkTrafficServerConnector} instead.
+ */
+@Deprecated
+public class NetworkTrafficSelectChannelConnector extends NetworkTrafficServerConnector
+{
+ public NetworkTrafficSelectChannelConnector(Server server)
+ {
+ super(server);
+ }
+
+ public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
+ {
+ super(server, connectionFactory, sslContextFactory);
+ }
+
+ public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory)
+ {
+ super(server, connectionFactory);
+ }
+
+ public NetworkTrafficSelectChannelConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
+ {
+ super(server, executor, scheduler, pool, acceptors, selectors, factories);
+ }
+
+ public NetworkTrafficSelectChannelConnector(Server server, SslContextFactory sslContextFactory)
+ {
+ super(server, sslContextFactory);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Server : Core Server Connector
+ */
+package org.eclipse.jetty.server.nio;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Server : Core Server API
+ */
+package org.eclipse.jetty.server;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ *
+ * <p>
+ * Implements {@link javax.servlet.http.HttpSession} from the <code>javax.servlet</code> package.
+ * </p>
+ *
+ */
+@SuppressWarnings("deprecation")
+public abstract class AbstractSession implements AbstractSessionManager.SessionIf
+{
+ final static Logger LOG = SessionHandler.LOG;
+ public final static String SESSION_KNOWN_ONLY_TO_AUTHENTICATED="org.eclipse.jetty.security.sessionKnownOnlytoAuthenticated";
+ private String _clusterId; // ID without any node (ie "worker") id appended
+ private String _nodeId; // ID of session with node(ie "worker") id appended
+ private final AbstractSessionManager _manager;
+ private boolean _idChanged;
+ private final long _created;
+ private long _cookieSet;
+ private long _accessed; // the time of the last access
+ private long _lastAccessed; // the time of the last access excluding this one
+ private boolean _invalid;
+ private boolean _doInvalidate;
+ private long _maxIdleMs;
+ private boolean _newSession;
+ private int _requests;
+
+
+
+ /* ------------------------------------------------------------- */
+ protected AbstractSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
+ {
+ _manager = abstractSessionManager;
+
+ _newSession=true;
+ _created=System.currentTimeMillis();
+ _clusterId=_manager._sessionIdManager.newSessionId(request,_created);
+ _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,request);
+ _accessed=_created;
+ _lastAccessed=_created;
+ _requests=1;
+ _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
+ if (LOG.isDebugEnabled())
+ LOG.debug("new session & id "+_nodeId+" "+_clusterId);
+ }
+
+ /* ------------------------------------------------------------- */
+ protected AbstractSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId)
+ {
+ _manager = abstractSessionManager;
+ _created=created;
+ _clusterId=clusterId;
+ _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,null);
+ _accessed=accessed;
+ _lastAccessed=accessed;
+ _requests=1;
+ _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
+ if (LOG.isDebugEnabled())
+ LOG.debug("new session "+_nodeId+" "+_clusterId);
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * asserts that the session is valid
+ */
+ protected void checkValid() throws IllegalStateException
+ {
+ if (_invalid)
+ throw new IllegalStateException();
+ }
+
+ /* ------------------------------------------------------------- */
+ /** Check to see if session has expired as at the time given.
+ * @param time
+ * @return
+ */
+ protected boolean checkExpiry(long time)
+ {
+ if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time)
+ return true;
+ return false;
+ }
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public AbstractSession getSession()
+ {
+ return this;
+ }
+
+ /* ------------------------------------------------------------- */
+ public long getAccessed()
+ {
+ synchronized (this)
+ {
+ return _accessed;
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ public abstract Map<String,Object> getAttributeMap();
+
+
+
+
+
+ /* ------------------------------------------------------------ */
+ public abstract int getAttributes();
+
+
+
+
+ /* ------------------------------------------------------------ */
+ public abstract Set<String> getNames();
+
+
+ /* ------------------------------------------------------------- */
+ public long getCookieSetTime()
+ {
+ return _cookieSet;
+ }
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public long getCreationTime() throws IllegalStateException
+ {
+ checkValid();
+ return _created;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getId() throws IllegalStateException
+ {
+ return _manager._nodeIdInSessionId?_nodeId:_clusterId;
+ }
+
+ /* ------------------------------------------------------------- */
+ public String getNodeId()
+ {
+ return _nodeId;
+ }
+
+ /* ------------------------------------------------------------- */
+ public String getClusterId()
+ {
+ return _clusterId;
+ }
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public long getLastAccessedTime() throws IllegalStateException
+ {
+ checkValid();
+ return _lastAccessed;
+ }
+
+ /* ------------------------------------------------------------- */
+ public void setLastAccessedTime(long time)
+ {
+ _lastAccessed = time;
+ }
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public int getMaxInactiveInterval()
+ {
+ return (int)(_maxIdleMs/1000);
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpSession#getServletContext()
+ */
+ @Override
+ public ServletContext getServletContext()
+ {
+ return _manager._context;
+ }
+
+ /* ------------------------------------------------------------- */
+ @Deprecated
+ @Override
+ public HttpSessionContext getSessionContext() throws IllegalStateException
+ {
+ checkValid();
+ return AbstractSessionManager.__nullSessionContext;
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @deprecated As of Version 2.2, this method is replaced by
+ * {@link #getAttribute}
+ */
+ @Deprecated
+ @Override
+ public Object getValue(String name) throws IllegalStateException
+ {
+ return getAttribute(name);
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ public void renewId(HttpServletRequest request)
+ {
+ _manager._sessionIdManager.renewSessionId(getClusterId(), getNodeId(), request);
+ setIdChanged(true);
+ }
+
+ /* ------------------------------------------------------------- */
+ public SessionManager getSessionManager()
+ {
+ return _manager;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void setClusterId (String clusterId)
+ {
+ _clusterId = clusterId;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void setNodeId (String nodeId)
+ {
+ _nodeId = nodeId;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ protected boolean access(long time)
+ {
+ synchronized(this)
+ {
+ if (_invalid)
+ return false;
+ _newSession=false;
+ _lastAccessed=_accessed;
+ _accessed=time;
+
+ if (checkExpiry(time))
+ {
+ invalidate();
+ return false;
+ }
+ _requests++;
+ return true;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void complete()
+ {
+ synchronized(this)
+ {
+ _requests--;
+ if (_doInvalidate && _requests<=0 )
+ doInvalidate();
+ }
+ }
+
+
+ /* ------------------------------------------------------------- */
+ protected void timeout() throws IllegalStateException
+ {
+ // remove session from context and invalidate other sessions with same ID.
+ _manager.removeSession(this,true);
+
+ // Notify listeners and unbind values
+ boolean do_invalidate=false;
+ synchronized (this)
+ {
+ if (!_invalid)
+ {
+ if (_requests<=0)
+ do_invalidate=true;
+ else
+ _doInvalidate=true;
+ }
+ }
+ if (do_invalidate)
+ doInvalidate();
+ }
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public void invalidate() throws IllegalStateException
+ {
+ checkValid();
+ // remove session from context and invalidate other sessions with same ID.
+ _manager.removeSession(this,true);
+ doInvalidate();
+ }
+
+ /* ------------------------------------------------------------- */
+ protected void doInvalidate() throws IllegalStateException
+ {
+ try
+ {
+ LOG.debug("invalidate {}",_clusterId);
+ if (isValid())
+ clearAttributes();
+ }
+ finally
+ {
+ synchronized (this)
+ {
+ // mark as invalid
+ _invalid=true;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ public abstract void clearAttributes();
+
+
+ /* ------------------------------------------------------------- */
+ public boolean isIdChanged()
+ {
+ return _idChanged;
+ }
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public boolean isNew() throws IllegalStateException
+ {
+ checkValid();
+ return _newSession;
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @deprecated As of Version 2.2, this method is replaced by
+ * {@link #setAttribute}
+ */
+ @Deprecated
+ @Override
+ public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException
+ {
+ changeAttribute(name,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void removeAttribute(String name)
+ {
+ setAttribute(name,null);
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @deprecated As of Version 2.2, this method is replaced by
+ * {@link #removeAttribute}
+ */
+ @Deprecated
+ @Override
+ public void removeValue(java.lang.String name) throws IllegalStateException
+ {
+ removeAttribute(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ @SuppressWarnings({ "unchecked" })
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ synchronized (this)
+ {
+ checkValid();
+ return doGetAttributeNames();
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ /**
+ * @deprecated As of Version 2.2, this method is replaced by
+ * {@link #getAttributeNames}
+ */
+ @Deprecated
+ @Override
+ public String[] getValueNames() throws IllegalStateException
+ {
+ synchronized(this)
+ {
+ checkValid();
+ Enumeration<String> anames = doGetAttributeNames();
+ if (anames == null)
+ return new String[0];
+ ArrayList<String> names = new ArrayList<String>();
+ while (anames.hasMoreElements())
+ names.add(anames.nextElement());
+ return names.toArray(new String[names.size()]);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public abstract Object doPutOrRemove(String name, Object value);
+
+
+ /* ------------------------------------------------------------ */
+ public abstract Object doGet(String name);
+
+
+ /* ------------------------------------------------------------ */
+ public abstract Enumeration<String> doGetAttributeNames();
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object getAttribute(String name)
+ {
+ synchronized (this)
+ {
+ checkValid();
+ return doGet(name);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setAttribute(String name, Object value)
+ {
+ changeAttribute(name,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param name
+ * @param value
+ * @deprecated use changeAttribute(String,Object) instead
+ * @return
+ */
+ protected boolean updateAttribute (String name, Object value)
+ {
+ Object old=null;
+ synchronized (this)
+ {
+ checkValid();
+ old=doPutOrRemove(name,value);
+ }
+
+ if (value==null || !value.equals(old))
+ {
+ if (old!=null)
+ unbindValue(name,old);
+ if (value!=null)
+ bindValue(name,value);
+
+ _manager.doSessionAttributeListeners(this,name,old,value);
+ return true;
+ }
+ return false;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Either set (perhaps replace) or remove the value of the attribute
+ * in the session. The appropriate session attribute listeners are
+ * also called.
+ *
+ * @param name
+ * @param value
+ * @return
+ */
+ protected Object changeAttribute (String name, Object value)
+ {
+ Object old=null;
+ synchronized (this)
+ {
+ checkValid();
+ old=doPutOrRemove(name,value);
+ }
+
+ callSessionAttributeListeners(name, value, old);
+
+ return old;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Call binding and attribute listeners based on the new and old
+ * values of the attribute.
+ *
+ * @param name name of the attribute
+ * @param newValue new value of the attribute
+ * @param oldValue previous value of the attribute
+ */
+ protected void callSessionAttributeListeners (String name, Object newValue, Object oldValue)
+ {
+ if (newValue==null || !newValue.equals(oldValue))
+ {
+ if (oldValue!=null)
+ unbindValue(name,oldValue);
+ if (newValue!=null)
+ bindValue(name,newValue);
+
+ _manager.doSessionAttributeListeners(this,name,oldValue,newValue);
+ }
+ }
+
+
+ /* ------------------------------------------------------------- */
+ public void setIdChanged(boolean changed)
+ {
+ _idChanged=changed;
+ }
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public void setMaxInactiveInterval(int secs)
+ {
+ _maxIdleMs=(long)secs*1000L;
+ }
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return this.getClass().getName()+":"+getId()+"@"+hashCode();
+ }
+
+ /* ------------------------------------------------------------- */
+ /** If value implements HttpSessionBindingListener, call valueBound() */
+ public void bindValue(java.lang.String name, Object value)
+ {
+ if (value!=null&&value instanceof HttpSessionBindingListener)
+ ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name));
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isValid()
+ {
+ return !_invalid;
+ }
+
+ /* ------------------------------------------------------------- */
+ protected void cookieSet()
+ {
+ synchronized (this)
+ {
+ _cookieSet=_accessed;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getRequests()
+ {
+ synchronized (this)
+ {
+ return _requests;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRequests(int requests)
+ {
+ synchronized (this)
+ {
+ _requests=requests;
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ /** If value implements HttpSessionBindingListener, call valueUnbound() */
+ public void unbindValue(java.lang.String name, Object value)
+ {
+ if (value!=null&&value instanceof HttpSessionBindingListener)
+ ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name));
+ }
+
+ /* ------------------------------------------------------------- */
+ public void willPassivate()
+ {
+ synchronized(this)
+ {
+ HttpSessionEvent event = new HttpSessionEvent(this);
+ for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
+ {
+ Object value = iter.next();
+ if (value instanceof HttpSessionActivationListener)
+ {
+ HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+ listener.sessionWillPassivate(event);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------- */
+ public void didActivate()
+ {
+ synchronized(this)
+ {
+ HttpSessionEvent event = new HttpSessionEvent(this);
+ for (Iterator<Object> iter = getAttributeMap().values().iterator(); iter.hasNext();)
+ {
+ Object value = iter.next();
+ if (value instanceof HttpSessionActivationListener)
+ {
+ HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+ listener.sessionDidActivate(event);
+ }
+ }
+ }
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager
+{
+ private static final Logger LOG = Log.getLogger(AbstractSessionIdManager.class);
+
+ private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
+
+ protected Random _random;
+ protected boolean _weakRandom;
+ protected String _workerName;
+ protected String _workerAttr;
+ protected long _reseed=100000L;
+
+ /* ------------------------------------------------------------ */
+ public AbstractSessionIdManager()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public AbstractSessionIdManager(Random random)
+ {
+ _random=random;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the workname. If set, the workername is dot appended to the session
+ * ID and can be used to assist session affinity in a load balancer.
+ *
+ * @return String or null
+ */
+ @Override
+ public String getWorkerName()
+ {
+ return _workerName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the workname. If set, the workername is dot appended to the session
+ * ID and can be used to assist session affinity in a load balancer.
+ * A worker name starting with $ is used as a request attribute name to
+ * lookup the worker name that can be dynamically set by a request
+ * customiser.
+ *
+ * @param workerName
+ */
+ public void setWorkerName(String workerName)
+ {
+ if (isRunning())
+ throw new IllegalStateException(getState());
+ if (workerName.contains("."))
+ throw new IllegalArgumentException("Name cannot contain '.'");
+ _workerName=workerName;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Random getRandom()
+ {
+ return _random;
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void setRandom(Random random)
+ {
+ _random=random;
+ _weakRandom=false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the reseed probability
+ */
+ public long getReseed()
+ {
+ return _reseed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the reseed probability.
+ * @param reseed If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
+ */
+ public void setReseed(long reseed)
+ {
+ _reseed = reseed;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new session id if necessary.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
+ */
+ @Override
+ public String newSessionId(HttpServletRequest request, long created)
+ {
+ synchronized (this)
+ {
+ if (request==null)
+ return newSessionId(created);
+
+ // A requested session ID can only be used if it is in use already.
+ String requested_id=request.getRequestedSessionId();
+ if (requested_id!=null)
+ {
+ String cluster_id=getClusterId(requested_id);
+ if (idInUse(cluster_id))
+ return cluster_id;
+ }
+
+ // Else reuse any new session ID already defined for this request.
+ String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
+ if (new_id!=null&&idInUse(new_id))
+ return new_id;
+
+ // pick a new unique ID!
+ String id = newSessionId(request.hashCode());
+
+ request.setAttribute(__NEW_SESSION_ID,id);
+ return id;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String newSessionId(long seedTerm)
+ {
+ // pick a new unique ID!
+ String id=null;
+ while (id==null||id.length()==0||idInUse(id))
+ {
+ long r0=_weakRandom
+ ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
+ :_random.nextLong();
+ if (r0<0)
+ r0=-r0;
+
+ // random chance to reseed
+ if (_reseed>0 && (r0%_reseed)== 1L)
+ {
+ LOG.debug("Reseeding {}",this);
+ if (_random instanceof SecureRandom)
+ {
+ SecureRandom secure = (SecureRandom)_random;
+ secure.setSeed(secure.generateSeed(8));
+ }
+ else
+ {
+ _random.setSeed(_random.nextLong()^System.currentTimeMillis()^seedTerm^Runtime.getRuntime().freeMemory());
+ }
+ }
+
+ long r1=_weakRandom
+ ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^((seedTerm)<<32))
+ :_random.nextLong();
+ if (r1<0)
+ r1=-r1;
+
+ id=Long.toString(r0,36)+Long.toString(r1,36);
+
+ //add in the id of the node to ensure unique id across cluster
+ //NOTE this is different to the node suffix which denotes which node the request was received on
+ if (_workerName!=null)
+ id=_workerName + id;
+
+ }
+ return id;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public abstract void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request);
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ initRandom();
+ _workerAttr=(_workerName!=null && _workerName.startsWith("$"))?_workerName.substring(1):null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set up a random number generator for the sessionids.
+ *
+ * By preference, use a SecureRandom but allow to be injected.
+ */
+ public void initRandom ()
+ {
+ if (_random==null)
+ {
+ try
+ {
+ _random=new SecureRandom();
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Could not generate SecureRandom for session-id randomness",e);
+ _random=new Random();
+ _weakRandom=true;
+ }
+ }
+ else
+ _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory());
+ }
+
+ /** Get the session ID with any worker ID.
+ *
+ * @param clusterId
+ * @param request
+ * @return sessionId plus any worker ID.
+ */
+ @Override
+ public String getNodeId(String clusterId, HttpServletRequest request)
+ {
+ if (_workerName!=null)
+ {
+ if (_workerAttr==null)
+ return clusterId+'.'+_workerName;
+
+ String worker=(String)request.getAttribute(_workerAttr);
+ if (worker!=null)
+ return clusterId+'.'+worker;
+ }
+
+ return clusterId;
+ }
+
+ /** Get the session ID without any worker ID.
+ *
+ * @param nodeId the node id
+ * @return sessionId without any worker ID.
+ */
+ @Override
+ public String getClusterId(String nodeId)
+ {
+ int dot=nodeId.lastIndexOf('.');
+ return (dot>0)?nodeId.substring(0,dot):nodeId;
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import static java.lang.Math.round;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+/* ------------------------------------------------------------ */
+/**
+ * An Abstract implementation of SessionManager. The partial implementation of
+ * SessionManager interface provides the majority of the handling required to
+ * implement a SessionManager. Concrete implementations of SessionManager based
+ * on AbstractSessionManager need only implement the newSession method to return
+ * a specialised version of the Session inner class that provides an attribute
+ * Map.
+ * <p>
+ */
+@SuppressWarnings("deprecation")
+@ManagedObject("Abstract Session Manager")
+public abstract class AbstractSessionManager extends ContainerLifeCycle implements SessionManager
+{
+ final static Logger __log = SessionHandler.LOG;
+
+ public Set<SessionTrackingMode> __defaultSessionTrackingModes =
+ Collections.unmodifiableSet(
+ new HashSet<SessionTrackingMode>(
+ Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL})));
+
+
+
+ /* ------------------------------------------------------------ */
+ public final static int __distantFuture=60*60*24*7*52*20;
+
+ static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
+ {
+ @Override
+ public HttpSession getSession(String sessionId)
+ {
+ return null;
+ }
+
+ @Override
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public Enumeration getIds()
+ {
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+ };
+
+ private boolean _usingCookies=true;
+
+ /* ------------------------------------------------------------ */
+ // Setting of max inactive interval for new sessions
+ // -1 means no timeout
+ protected int _dftMaxIdleSecs=-1;
+ protected SessionHandler _sessionHandler;
+ protected boolean _httpOnly=false;
+ protected SessionIdManager _sessionIdManager;
+ protected boolean _secureCookies=false;
+ protected boolean _secureRequestOnly=true;
+
+ protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
+ protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
+ protected final List<HttpSessionIdListener> _sessionIdListeners = new CopyOnWriteArrayList<HttpSessionIdListener>();
+
+ protected ClassLoader _loader;
+ protected ContextHandler.Context _context;
+ protected String _sessionCookie=__DefaultSessionCookie;
+ protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
+ protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
+ protected String _sessionDomain;
+ protected String _sessionPath;
+ protected int _maxCookieAge=-1;
+ protected int _refreshCookieAge;
+ protected boolean _nodeIdInSessionId;
+ protected boolean _checkingRemoteSessionIdEncoding;
+ protected String _sessionComment;
+
+ public Set<SessionTrackingMode> _sessionTrackingModes;
+
+ private boolean _usingURLs;
+
+ protected final CounterStatistic _sessionsStats = new CounterStatistic();
+ protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
+
+
+ /* ------------------------------------------------------------ */
+ public AbstractSessionManager()
+ {
+ setSessionTrackingModes(__defaultSessionTrackingModes);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ContextHandler.Context getContext()
+ {
+ return _context;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ContextHandler getContextHandler()
+ {
+ return _context.getContextHandler();
+ }
+
+ @ManagedAttribute("path of the session cookie, or null for default")
+ public String getSessionPath()
+ {
+ return _sessionPath;
+ }
+
+ @ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
+ public int getMaxCookieAge()
+ {
+ return _maxCookieAge;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpCookie access(HttpSession session,boolean secure)
+ {
+ long now=System.currentTimeMillis();
+
+ AbstractSession s = ((SessionIf)session).getSession();
+
+ if (s.access(now))
+ {
+ // Do we need to refresh the cookie?
+ if (isUsingCookies() &&
+ (s.isIdChanged() ||
+ (getSessionCookieConfig().getMaxAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
+ )
+ )
+ {
+ HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
+ s.cookieSet();
+ s.setIdChanged(false);
+ return cookie;
+ }
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void addEventListener(EventListener listener)
+ {
+ if (listener instanceof HttpSessionAttributeListener)
+ _sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
+ if (listener instanceof HttpSessionListener)
+ _sessionListeners.add((HttpSessionListener)listener);
+ if (listener instanceof HttpSessionIdListener)
+ _sessionIdListeners.add((HttpSessionIdListener)listener);
+ addBean(listener,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void clearEventListeners()
+ {
+ for (EventListener e :getBeans(EventListener.class))
+ removeBean(e);
+ _sessionAttributeListeners.clear();
+ _sessionListeners.clear();
+ _sessionIdListeners.clear();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void complete(HttpSession session)
+ {
+ AbstractSession s = ((SessionIf)session).getSession();
+ s.complete();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doStart() throws Exception
+ {
+ _context=ContextHandler.getCurrentContext();
+ _loader=Thread.currentThread().getContextClassLoader();
+
+ if (_sessionIdManager==null)
+ {
+ final Server server=getSessionHandler().getServer();
+ synchronized (server)
+ {
+ _sessionIdManager=server.getSessionIdManager();
+ if (_sessionIdManager==null)
+ {
+ //create a default SessionIdManager and set it as the shared
+ //SessionIdManager for the Server, being careful NOT to use
+ //the webapp context's classloader, otherwise if the context
+ //is stopped, the classloader is leaked.
+ ClassLoader serverLoader = server.getClass().getClassLoader();
+ try
+ {
+ Thread.currentThread().setContextClassLoader(serverLoader);
+ _sessionIdManager=new HashSessionIdManager();
+ server.setSessionIdManager(_sessionIdManager);
+ server.manage(_sessionIdManager);
+ _sessionIdManager.start();
+ }
+ finally
+ {
+ Thread.currentThread().setContextClassLoader(_loader);
+ }
+ }
+
+ // server session id is never managed by this manager
+ addBean(_sessionIdManager,false);
+ }
+ }
+
+
+ // Look for a session cookie name
+ if (_context!=null)
+ {
+ String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
+ if (tmp!=null)
+ _sessionCookie=tmp;
+
+ tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
+ if (tmp!=null)
+ setSessionIdPathParameterName(tmp);
+
+ // set up the max session cookie age if it isn't already
+ if (_maxCookieAge==-1)
+ {
+ tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty);
+ if (tmp!=null)
+ _maxCookieAge=Integer.parseInt(tmp.trim());
+ }
+
+ // set up the session domain if it isn't already
+ if (_sessionDomain==null)
+ _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
+
+ // set up the sessionPath if it isn't already
+ if (_sessionPath==null)
+ _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
+
+ tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
+ if (tmp!=null)
+ _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
+ }
+
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doStop() throws Exception
+ {
+ super.doStop();
+
+ shutdownSessions();
+
+ _loader=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the httpOnly.
+ */
+ @Override
+ @ManagedAttribute("true if cookies use the http only flag")
+ public boolean getHttpOnly()
+ {
+ return _httpOnly;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpSession getHttpSession(String nodeId)
+ {
+ String cluster_id = getSessionIdManager().getClusterId(nodeId);
+
+ AbstractSession session = getSession(cluster_id);
+ if (session!=null && !session.getNodeId().equals(nodeId))
+ session.setIdChanged(true);
+ return session;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the SessionIdManager used for cross context session management
+ */
+ @Override
+ @ManagedAttribute("Session ID Manager")
+ public SessionIdManager getSessionIdManager()
+ {
+ return _sessionIdManager;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return seconds
+ */
+ @Override
+ @ManagedAttribute("defailt maximum time a session may be idle for (in s)")
+ public int getMaxInactiveInterval()
+ {
+ return _dftMaxIdleSecs;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return maximum number of sessions
+ */
+ @ManagedAttribute("maximum number of simultaneous sessions")
+ public int getSessionsMax()
+ {
+ return (int)_sessionsStats.getMax();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return total number of sessions
+ */
+ @ManagedAttribute("total number of sessions")
+ public int getSessionsTotal()
+ {
+ return (int)_sessionsStats.getTotal();
+ }
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute("time before a session cookie is re-set (in s)")
+ public int getRefreshCookieAge()
+ {
+ return _refreshCookieAge;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return same as SessionCookieConfig.getSecure(). If true, session
+ * cookies are ALWAYS marked as secure. If false, a session cookie is
+ * ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request.
+ */
+ @ManagedAttribute("if true, secure cookie flag is set on session cookies")
+ public boolean getSecureCookies()
+ {
+ return _secureCookies;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if session cookie is to be marked as secure only on HTTPS requests
+ */
+ public boolean isSecureRequestOnly()
+ {
+ return _secureRequestOnly;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true),
+ * in which case the session cookie will be marked as secure on both HTTPS and HTTP.
+ */
+ public void setSecureRequestOnly(boolean secureRequestOnly)
+ {
+ _secureRequestOnly = secureRequestOnly;
+ }
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute("the set session cookie")
+ public String getSessionCookie()
+ {
+ return _sessionCookie;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * A sessioncookie is marked as secure IFF any of the following conditions are true:
+ * <ol>
+ * <li>SessionCookieConfig.setSecure == true</li>
+ * <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li>
+ * </ol>
+ * According to SessionCookieConfig javadoc, case 1 can be used when:
+ * "... even though the request that initiated the session came over HTTP,
+ * is to support a topology where the web container is front-ended by an
+ * SSL offloading load balancer. In this case, the traffic between the client
+ * and the load balancer will be over HTTPS, whereas the traffic between the
+ * load balancer and the web container will be over HTTP."
+ *
+ * For case 2, you can use _secureRequestOnly to determine if you want the
+ * Servlet Spec 3.0 default behaviour when SessionCookieConfig.setSecure==false,
+ * which is:
+ * "they shall be marked as secure only if the request that initiated the
+ * corresponding session was also secure"
+ *
+ * The default for _secureRequestOnly is true, which gives the above behaviour. If
+ * you set it to false, then a session cookie is NEVER marked as secure, even if
+ * the initiating request was secure.
+ *
+ * @see org.eclipse.jetty.server.SessionManager#getSessionCookie(javax.servlet.http.HttpSession, java.lang.String, boolean)
+ */
+ @Override
+ public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
+ {
+ if (isUsingCookies())
+ {
+ String sessionPath = (_cookieConfig.getPath()==null) ? contextPath : _cookieConfig.getPath();
+ sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
+ String id = getNodeId(session);
+ HttpCookie cookie = null;
+ if (_sessionComment == null)
+ {
+ cookie = new HttpCookie(
+ _cookieConfig.getName(),
+ id,
+ _cookieConfig.getDomain(),
+ sessionPath,
+ _cookieConfig.getMaxAge(),
+ _cookieConfig.isHttpOnly(),
+ _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));
+ }
+ else
+ {
+ cookie = new HttpCookie(
+ _cookieConfig.getName(),
+ id,
+ _cookieConfig.getDomain(),
+ sessionPath,
+ _cookieConfig.getMaxAge(),
+ _cookieConfig.isHttpOnly(),
+ _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
+ _sessionComment,
+ 1);
+ }
+
+ return cookie;
+ }
+ return null;
+ }
+
+ @ManagedAttribute("domain of the session cookie, or null for the default")
+ public String getSessionDomain()
+ {
+ return _sessionDomain;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionHandler.
+ */
+ public SessionHandler getSessionHandler()
+ {
+ return _sessionHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute("number of currently active sessions")
+ public int getSessions()
+ {
+ return (int)_sessionsStats.getCurrent();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ @ManagedAttribute("name of use for URL session tracking")
+ public String getSessionIdPathParameterName()
+ {
+ return _sessionIdPathParameterName;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getSessionIdPathParameterNamePrefix()
+ {
+ return _sessionIdPathParameterNamePrefix;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the usingCookies.
+ */
+ @Override
+ public boolean isUsingCookies()
+ {
+ return _usingCookies;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isValid(HttpSession session)
+ {
+ AbstractSession s = ((SessionIf)session).getSession();
+ return s.isValid();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getClusterId(HttpSession session)
+ {
+ AbstractSession s = ((SessionIf)session).getSession();
+ return s.getClusterId();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getNodeId(HttpSession session)
+ {
+ AbstractSession s = ((SessionIf)session).getSession();
+ return s.getNodeId();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new HttpSession for a request
+ */
+ @Override
+ public HttpSession newHttpSession(HttpServletRequest request)
+ {
+ AbstractSession session=newSession(request);
+ session.setMaxInactiveInterval(_dftMaxIdleSecs);
+ addSession(session,true);
+ return session;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void removeEventListener(EventListener listener)
+ {
+ if (listener instanceof HttpSessionAttributeListener)
+ _sessionAttributeListeners.remove(listener);
+ if (listener instanceof HttpSessionListener)
+ _sessionListeners.remove(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Reset statistics values
+ */
+ @ManagedOperation(value="reset statistics", impact="ACTION")
+ public void statsReset()
+ {
+ _sessionsStats.reset(getSessions());
+ _sessionTimeStats.reset();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param httpOnly
+ * The httpOnly to set.
+ */
+ public void setHttpOnly(boolean httpOnly)
+ {
+ _httpOnly=httpOnly;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param metaManager The metaManager used for cross context session management.
+ */
+ @Override
+ public void setSessionIdManager(SessionIdManager metaManager)
+ {
+ updateBean(_sessionIdManager, metaManager);
+ _sessionIdManager=metaManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param seconds
+ */
+ @Override
+ public void setMaxInactiveInterval(int seconds)
+ {
+ _dftMaxIdleSecs=seconds;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRefreshCookieAge(int ageInSeconds)
+ {
+ _refreshCookieAge=ageInSeconds;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setSessionCookie(String cookieName)
+ {
+ _sessionCookie=cookieName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionHandler
+ * The sessionHandler to set.
+ */
+ @Override
+ public void setSessionHandler(SessionHandler sessionHandler)
+ {
+ _sessionHandler=sessionHandler;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setSessionIdPathParameterName(String param)
+ {
+ _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
+ _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
+ }
+ /* ------------------------------------------------------------ */
+ /**
+ * @param usingCookies
+ * The usingCookies to set.
+ */
+ public void setUsingCookies(boolean usingCookies)
+ {
+ _usingCookies=usingCookies;
+ }
+
+
+ protected abstract void addSession(AbstractSession session);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add the session Registers the session with this manager and registers the
+ * session ID with the sessionIDManager;
+ */
+ protected void addSession(AbstractSession session, boolean created)
+ {
+ synchronized (_sessionIdManager)
+ {
+ _sessionIdManager.addSession(session);
+ addSession(session);
+ }
+
+ if (created)
+ {
+ _sessionsStats.increment();
+ if (_sessionListeners!=null)
+ {
+ HttpSessionEvent event=new HttpSessionEvent(session);
+ for (HttpSessionListener listener : _sessionListeners)
+ listener.sessionCreated(event);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get a known existing session
+ * @param idInCluster The session ID in the cluster, stripped of any worker name.
+ * @return A Session or null if none exists.
+ */
+ public abstract AbstractSession getSession(String idInCluster);
+
+ /**
+ * Prepare sessions for session manager shutdown
+ *
+ * @throws Exception
+ */
+ protected abstract void shutdownSessions() throws Exception;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new session instance
+ * @param request
+ * @return the new session
+ */
+ protected abstract AbstractSession newSession(HttpServletRequest request);
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+ */
+ public boolean isNodeIdInSessionId()
+ {
+ return _nodeIdInSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param nodeIdInSessionId true if the cluster node id (worker id) will be returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+ */
+ public void setNodeIdInSessionId(boolean nodeIdInSessionId)
+ {
+ _nodeIdInSessionId=nodeIdInSessionId;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Remove session from manager
+ * @param session The session to remove
+ * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+ * {@link SessionIdManager#invalidateAll(String)} should be called.
+ */
+ public void removeSession(HttpSession session, boolean invalidate)
+ {
+ AbstractSession s = ((SessionIf)session).getSession();
+ removeSession(s,invalidate);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Remove session from manager
+ * @param session The session to remove
+ * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+ * {@link SessionIdManager#invalidateAll(String)} should be called.
+ */
+ public boolean removeSession(AbstractSession session, boolean invalidate)
+ {
+ // Remove session from context and global maps
+ boolean removed = removeSession(session.getClusterId());
+
+ if (removed)
+ {
+ _sessionsStats.decrement();
+ _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
+
+ // Remove session from all context and global id maps
+ _sessionIdManager.removeSession(session);
+ if (invalidate)
+ _sessionIdManager.invalidateAll(session.getClusterId());
+
+ if (invalidate && _sessionListeners!=null)
+ {
+ HttpSessionEvent event=new HttpSessionEvent(session);
+ for (int i = _sessionListeners.size()-1; i>=0; i--)
+ {
+ _sessionListeners.get(i).sessionDestroyed(event);
+ }
+ }
+ }
+
+ return removed;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected abstract boolean removeSession(String idInCluster);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return maximum amount of time session remained valid
+ */
+ @ManagedAttribute("maximum amount of time sessions have remained active (in s)")
+ public long getSessionTimeMax()
+ {
+ return _sessionTimeStats.getMax();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+ {
+ return __defaultSessionTrackingModes;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+ {
+ return Collections.unmodifiableSet(_sessionTrackingModes);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+ {
+ _sessionTrackingModes=new HashSet<SessionTrackingMode>(sessionTrackingModes);
+ _usingCookies=_sessionTrackingModes.contains(SessionTrackingMode.COOKIE);
+ _usingURLs=_sessionTrackingModes.contains(SessionTrackingMode.URL);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isUsingURLs()
+ {
+ return _usingURLs;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public SessionCookieConfig getSessionCookieConfig()
+ {
+ return _cookieConfig;
+ }
+
+ /* ------------------------------------------------------------ */
+ private SessionCookieConfig _cookieConfig =
+ new CookieConfig();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return total amount of time all sessions remained valid
+ */
+ @ManagedAttribute("total time sessions have remained valid")
+ public long getSessionTimeTotal()
+ {
+ return _sessionTimeStats.getTotal();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return mean amount of time session remained valid
+ */
+ @ManagedAttribute("mean time sessions remain valid (in s)")
+ public double getSessionTimeMean()
+ {
+ return _sessionTimeStats.getMean();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return standard deviation of amount of time session remained valid
+ */
+ @ManagedAttribute("standard deviation a session remained valid (in s)")
+ public double getSessionTimeStdDev()
+ {
+ return _sessionTimeStats.getStdDev();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
+ */
+ @Override
+ @ManagedAttribute("check remote session id encoding")
+ public boolean isCheckingRemoteSessionIdEncoding()
+ {
+ return _checkingRemoteSessionIdEncoding;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
+ */
+ @Override
+ public void setCheckingRemoteSessionIdEncoding(boolean remote)
+ {
+ _checkingRemoteSessionIdEncoding=remote;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Tell the HttpSessionIdListeners the id changed.
+ * NOTE: this method must be called LAST in subclass overrides, after the session has been updated
+ * with the new id.
+ * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+ */
+ @Override
+ public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+ {
+ if (!_sessionIdListeners.isEmpty())
+ {
+ AbstractSession session = getSession(newClusterId);
+ HttpSessionEvent event = new HttpSessionEvent(session);
+ for (HttpSessionIdListener l:_sessionIdListeners)
+ {
+ l.sessionIdChanged(event, oldClusterId);
+ }
+ }
+
+ }
+
+ /**
+ * CookieConfig
+ *
+ * Implementation of the javax.servlet.SessionCookieConfig.
+ */
+ public final class CookieConfig implements SessionCookieConfig
+ {
+ @Override
+ public String getComment()
+ {
+ return _sessionComment;
+ }
+
+ @Override
+ public String getDomain()
+ {
+ return _sessionDomain;
+ }
+
+ @Override
+ public int getMaxAge()
+ {
+ return _maxCookieAge;
+ }
+
+ @Override
+ public String getName()
+ {
+ return _sessionCookie;
+ }
+
+ @Override
+ public String getPath()
+ {
+ return _sessionPath;
+ }
+
+ @Override
+ public boolean isHttpOnly()
+ {
+ return _httpOnly;
+ }
+
+ @Override
+ public boolean isSecure()
+ {
+ return _secureCookies;
+ }
+
+ @Override
+ public void setComment(String comment)
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+ _sessionComment = comment;
+ }
+
+ @Override
+ public void setDomain(String domain)
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+ _sessionDomain=domain;
+ }
+
+ @Override
+ public void setHttpOnly(boolean httpOnly)
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+ _httpOnly=httpOnly;
+ }
+
+ @Override
+ public void setMaxAge(int maxAge)
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+ _maxCookieAge=maxAge;
+ }
+
+ @Override
+ public void setName(String name)
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+ _sessionCookie=name;
+ }
+
+ @Override
+ public void setPath(String path)
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+ _sessionPath=path;
+ }
+
+ @Override
+ public void setSecure(boolean secure)
+ {
+ if (_context != null && _context.getContextHandler().isAvailable())
+ throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
+ _secureCookies=secure;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /**
+ * Interface that any session wrapper should implement so that
+ * SessionManager may access the Jetty session implementation.
+ *
+ */
+ public interface SessionIf extends HttpSession
+ {
+ public AbstractSession getSession();
+ }
+
+ public void doSessionAttributeListeners(AbstractSession session, String name, Object old, Object value)
+ {
+ if (!_sessionAttributeListeners.isEmpty())
+ {
+ HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old);
+
+ for (HttpSessionAttributeListener l : _sessionAttributeListeners)
+ {
+ if (old==null)
+ l.attributeAdded(event);
+ else if (value==null)
+ l.attributeRemoved(event);
+ else
+ l.attributeReplaced(event);
+ }
+ }
+ }
+
+ @Override
+ @Deprecated
+ public SessionIdManager getMetaManager()
+ {
+ throw new UnsupportedOperationException();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.server.SessionIdManager;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashSessionIdManager. An in-memory implementation of the session ID manager.
+ */
+public class HashSessionIdManager extends AbstractSessionIdManager
+{
+ private final Map<String, Set<WeakReference<HttpSession>>> _sessions = new HashMap<String, Set<WeakReference<HttpSession>>>();
+
+ /* ------------------------------------------------------------ */
+ public HashSessionIdManager()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public HashSessionIdManager(Random random)
+ {
+ super(random);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Collection of String session IDs
+ */
+ public Collection<String> getSessions()
+ {
+ return Collections.unmodifiableCollection(_sessions.keySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Collection of Sessions for the passed session ID
+ */
+ public Collection<HttpSession> getSession(String id)
+ {
+ ArrayList<HttpSession> sessions = new ArrayList<HttpSession>();
+ Set<WeakReference<HttpSession>> refs =_sessions.get(id);
+ if (refs!=null)
+ {
+ for (WeakReference<HttpSession> ref: refs)
+ {
+ HttpSession session = ref.get();
+ if (session!=null)
+ sessions.add(session);
+ }
+ }
+ return sessions;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ _sessions.clear();
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see SessionIdManager#idInUse(String)
+ */
+ @Override
+ public boolean idInUse(String id)
+ {
+ synchronized (this)
+ {
+ return _sessions.containsKey(id);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see SessionIdManager#addSession(HttpSession)
+ */
+ @Override
+ public void addSession(HttpSession session)
+ {
+ String id = getClusterId(session.getId());
+ WeakReference<HttpSession> ref = new WeakReference<HttpSession>(session);
+
+ synchronized (this)
+ {
+ Set<WeakReference<HttpSession>> sessions = _sessions.get(id);
+ if (sessions==null)
+ {
+ sessions=new HashSet<WeakReference<HttpSession>>();
+ _sessions.put(id,sessions);
+ }
+ sessions.add(ref);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see SessionIdManager#removeSession(HttpSession)
+ */
+ @Override
+ public void removeSession(HttpSession session)
+ {
+ String id = getClusterId(session.getId());
+
+ synchronized (this)
+ {
+ Collection<WeakReference<HttpSession>> sessions = _sessions.get(id);
+ if (sessions!=null)
+ {
+ for (Iterator<WeakReference<HttpSession>> iter = sessions.iterator(); iter.hasNext();)
+ {
+ WeakReference<HttpSession> ref = iter.next();
+ HttpSession s=ref.get();
+ if (s==null)
+ {
+ iter.remove();
+ continue;
+ }
+ if (s==session)
+ {
+ iter.remove();
+ break;
+ }
+ }
+ if (sessions.isEmpty())
+ _sessions.remove(id);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see SessionIdManager#invalidateAll(String)
+ */
+ @Override
+ public void invalidateAll(String id)
+ {
+ Collection<WeakReference<HttpSession>> sessions;
+ synchronized (this)
+ {
+ sessions = _sessions.remove(id);
+ }
+
+ if (sessions!=null)
+ {
+ for (WeakReference<HttpSession> ref: sessions)
+ {
+ AbstractSession session=(AbstractSession)ref.get();
+ if (session!=null && session.isValid())
+ session.invalidate();
+ }
+ sessions.clear();
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
+ {
+ //generate a new id
+ String newClusterId = newSessionId(request.hashCode());
+
+
+ synchronized (this)
+ {
+ Set<WeakReference<HttpSession>> sessions = _sessions.remove(oldClusterId); //get the list of sessions with same id from other contexts
+ if (sessions!=null)
+ {
+ for (Iterator<WeakReference<HttpSession>> iter = sessions.iterator(); iter.hasNext();)
+ {
+ WeakReference<HttpSession> ref = iter.next();
+ HttpSession s = ref.get();
+ if (s == null)
+ {
+ continue;
+ }
+ else
+ {
+ if (s instanceof AbstractSession)
+ {
+ AbstractSession abstractSession = (AbstractSession)s;
+ abstractSession.getSessionManager().renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+ }
+ }
+ }
+ _sessions.put(newClusterId, sessions);
+ }
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * HashSessionManager
+ *
+ * An in-memory implementation of SessionManager.
+ * <p>
+ * This manager supports saving sessions to disk, either periodically or at shutdown.
+ * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions.
+ * <p>
+ * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance
+ * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler.
+ *
+ */
+public class HashSessionManager extends AbstractSessionManager
+{
+ final static Logger LOG = SessionHandler.LOG;
+
+ protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
+ private Scheduler _timer;
+ private Scheduler.Task _task;
+ long _scavengePeriodMs=30000;
+ long _savePeriodMs=0; //don't do period saves by default
+ long _idleSavePeriodMs = 0; // don't idle save sessions by default.
+ private Scheduler.Task _saveTask;
+ File _storeDir;
+ private boolean _lazyLoad=false;
+ private volatile boolean _sessionsLoaded=false;
+ private boolean _deleteUnrestorableSessions=false;
+
+
+ /**
+ * Scavenger
+ *
+ */
+ protected class Scavenger implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ scavenge();
+ }
+ finally
+ {
+ if (_timer != null && _timer.isRunning())
+ _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+
+ /**
+ * Saver
+ *
+ */
+ protected class Saver implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ saveSessions(true);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ finally
+ {
+ if (_timer != null && _timer.isRunning())
+ _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public HashSessionManager()
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see AbstractSessionManager#doStart()
+ */
+ @Override
+ public void doStart() throws Exception
+ {
+ //try shared scheduler from Server first
+ _timer = getSessionHandler().getServer().getBean(Scheduler.class);
+ if (_timer == null)
+ {
+ //try one passed into the context
+ ServletContext context = ContextHandler.getCurrentContext();
+ if (context!=null)
+ _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer");
+ }
+
+ if (_timer == null)
+ {
+ //make a scheduler if none useable
+ _timer=new ScheduledExecutorScheduler(toString()+"Timer",true);
+ addBean(_timer,true);
+ }
+ else
+ addBean(_timer,false);
+
+ super.doStart();
+
+ setScavengePeriod(getScavengePeriod());
+
+ if (_storeDir!=null)
+ {
+ if (!_storeDir.exists())
+ _storeDir.mkdirs();
+
+ if (!_lazyLoad)
+ restoreSessions();
+ }
+
+ setSavePeriod(getSavePeriod());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see AbstractSessionManager#doStop()
+ */
+ @Override
+ public void doStop() throws Exception
+ {
+ // stop the scavengers
+ synchronized(this)
+ {
+ if (_saveTask!=null)
+ _saveTask.cancel();
+ _saveTask=null;
+ if (_task!=null)
+ _task.cancel();
+ _task=null;
+ _timer=null;
+ }
+
+ // This will callback invalidate sessions - where we decide if we will save
+ super.doStop();
+
+ _sessions.clear();
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the period in seconds at which a check is made for sessions to be invalidated.
+ */
+ public int getScavengePeriod()
+ {
+ return (int)(_scavengePeriodMs/1000);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int getSessions()
+ {
+ int sessions=super.getSessions();
+ if (LOG.isDebugEnabled())
+ {
+ if (_sessions.size()!=sessions)
+ LOG.warn("sessions: "+_sessions.size()+"!="+sessions);
+ }
+ return sessions;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return seconds Idle period after which a session is saved
+ */
+ public int getIdleSavePeriod()
+ {
+ if (_idleSavePeriodMs <= 0)
+ return 0;
+
+ return (int)(_idleSavePeriodMs / 1000);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Configures the period in seconds after which a session is deemed idle and saved
+ * to save on session memory.
+ *
+ * The session is persisted, the values attribute map is cleared and the session set to idled.
+ *
+ * @param seconds Idle period after which a session is saved
+ */
+ public void setIdleSavePeriod(int seconds)
+ {
+ _idleSavePeriodMs = seconds * 1000L;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setMaxInactiveInterval(int seconds)
+ {
+ super.setMaxInactiveInterval(seconds);
+ if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L)
+ setScavengePeriod((_dftMaxIdleSecs+9)/10);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param seconds the period is seconds at which sessions are periodically saved to disk
+ */
+ public void setSavePeriod (int seconds)
+ {
+ long period = (seconds * 1000L);
+ if (period < 0)
+ period=0;
+ _savePeriodMs=period;
+
+ if (_timer!=null)
+ {
+ synchronized (this)
+ {
+ if (_saveTask!=null)
+ _saveTask.cancel();
+ _saveTask = null;
+ if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
+ {
+ _saveTask = _timer.schedule(new Saver(),_savePeriodMs,TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the period in seconds at which sessions are periodically saved to disk
+ */
+ public int getSavePeriod ()
+ {
+ if (_savePeriodMs<=0)
+ return 0;
+
+ return (int)(_savePeriodMs/1000);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
+ */
+ public void setScavengePeriod(int seconds)
+ {
+ if (seconds==0)
+ seconds=60;
+
+ long old_period=_scavengePeriodMs;
+ long period=seconds*1000L;
+ if (period>60000)
+ period=60000;
+ if (period<1000)
+ period=1000;
+
+ _scavengePeriodMs=period;
+
+ if (_timer!=null && (period!=old_period || _task==null))
+ {
+ synchronized (this)
+ {
+ if (_task!=null)
+ {
+ _task.cancel();
+ _task = null;
+ }
+ _task = _timer.schedule(new Scavenger(),_scavengePeriodMs, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+
+ /* -------------------------------------------------------------- */
+ /**
+ * Find sessions that have timed out and invalidate them. This runs in the
+ * SessionScavenger thread.
+ */
+ protected void scavenge()
+ {
+ //don't attempt to scavenge if we are shutting down
+ if (isStopping() || isStopped())
+ return;
+
+ Thread thread=Thread.currentThread();
+ ClassLoader old_loader=thread.getContextClassLoader();
+ try
+ {
+ if (_loader!=null)
+ thread.setContextClassLoader(_loader);
+
+ // For each session
+ long now=System.currentTimeMillis();
+ __log.debug("Scavenging sessions at {}", now);
+
+ for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
+ {
+ HashedSession session=i.next();
+ long idleTime=session.getMaxInactiveInterval()*1000L;
+ if (idleTime>0&&session.getAccessed()+idleTime<now)
+ {
+ // Found a stale session, add it to the list
+ try
+ {
+ session.timeout();
+ }
+ catch (Exception e)
+ {
+ __log.warn("Problem scavenging sessions", e);
+ }
+ }
+ else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now)
+ {
+ try
+ {
+ session.idle();
+ }
+ catch (Exception e)
+ {
+ __log.warn("Problem idling session "+ session.getId(), e);
+ }
+ }
+ }
+ }
+ finally
+ {
+ thread.setContextClassLoader(old_loader);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void addSession(AbstractSession session)
+ {
+ if (isRunning())
+ _sessions.put(session.getClusterId(),(HashedSession)session);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public AbstractSession getSession(String idInCluster)
+ {
+ if ( _lazyLoad && !_sessionsLoaded)
+ {
+ try
+ {
+ restoreSessions();
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+ Map<String,HashedSession> sessions=_sessions;
+ if (sessions==null)
+ return null;
+
+ HashedSession session = sessions.get(idInCluster);
+
+ if (session == null && _lazyLoad)
+ session=restoreSession(idInCluster);
+ if (session == null)
+ return null;
+
+ if (_idleSavePeriodMs!=0)
+ session.deIdle();
+
+ return session;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void shutdownSessions() throws Exception
+ {
+ // Invalidate all sessions to cause unbind events
+ ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
+ int loop=100;
+ while (sessions.size()>0 && loop-->0)
+ {
+ // If we are called from doStop
+ if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
+ {
+ // Then we only save and remove the session from memory- it is not invalidated.
+ for (HashedSession session : sessions)
+ {
+ session.save(false);
+ _sessions.remove(session.getClusterId());
+ }
+ }
+ else
+ {
+ for (HashedSession session : sessions)
+ session.invalidate();
+ }
+
+ // check that no new sessions were created while we were iterating
+ sessions=new ArrayList<HashedSession>(_sessions.values());
+ }
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+ */
+ @Override
+ public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+ {
+ try
+ {
+ Map<String,HashedSession> sessions=_sessions;
+ if (sessions == null)
+ return;
+
+ HashedSession session = sessions.remove(oldClusterId);
+ if (session == null)
+ return;
+
+ session.remove(); //delete any previously saved session
+ session.setClusterId(newClusterId); //update ids
+ session.setNodeId(newNodeId);
+ session.save(); //save updated session: TODO consider only saving file if idled
+ sessions.put(newClusterId, session);
+
+ super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected AbstractSession newSession(HttpServletRequest request)
+ {
+ return new HashedSession(this, request);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected AbstractSession newSession(long created, long accessed, String clusterId)
+ {
+ return new HashedSession(this, created,accessed, clusterId);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected boolean removeSession(String clusterId)
+ {
+ return _sessions.remove(clusterId)!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setStoreDirectory (File dir) throws IOException
+ {
+ // CanonicalFile is used to capture the base store directory in a way that will
+ // work on Windows. Case differences may through off later checks using this directory.
+ _storeDir=dir.getCanonicalFile();
+ }
+
+ /* ------------------------------------------------------------ */
+ public File getStoreDirectory ()
+ {
+ return _storeDir;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setLazyLoad(boolean lazyLoad)
+ {
+ _lazyLoad = lazyLoad;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isLazyLoad()
+ {
+ return _lazyLoad;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isDeleteUnrestorableSessions()
+ {
+ return _deleteUnrestorableSessions;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
+ {
+ _deleteUnrestorableSessions = deleteUnrestorableSessions;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void restoreSessions () throws Exception
+ {
+ _sessionsLoaded = true;
+
+ if (_storeDir==null || !_storeDir.exists())
+ {
+ return;
+ }
+
+ if (!_storeDir.canRead())
+ {
+ LOG.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
+ return;
+ }
+
+ String[] files = _storeDir.list();
+ for (int i=0;files!=null&&i<files.length;i++)
+ {
+ restoreSession(files[i]);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected synchronized HashedSession restoreSession(String idInCuster)
+ {
+ File file = new File(_storeDir,idInCuster);
+
+ FileInputStream in = null;
+ Exception error = null;
+ try
+ {
+ if (file.exists())
+ {
+ in = new FileInputStream(file);
+ HashedSession session = restoreSession(in, null);
+ addSession(session, false);
+ session.didActivate();
+ return session;
+ }
+ }
+ catch (Exception e)
+ {
+ error = e;
+ }
+ finally
+ {
+ if (in != null) IO.close(in);
+
+ if (error != null)
+ {
+ if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
+ {
+ file.delete();
+ LOG.warn("Deleting file for unrestorable session "+idInCuster, error);
+ }
+ else
+ {
+ __log.warn("Problem restoring session "+idInCuster, error);
+ }
+ }
+ else
+ file.delete(); //delete successfully restored file
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void saveSessions(boolean reactivate) throws Exception
+ {
+ if (_storeDir==null || !_storeDir.exists())
+ {
+ return;
+ }
+
+ if (!_storeDir.canWrite())
+ {
+ LOG.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
+ return;
+ }
+
+ for (HashedSession session : _sessions.values())
+ session.save(reactivate);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
+ {
+ DataInputStream di = new DataInputStream(is);
+
+ String clusterId = di.readUTF();
+ di.readUTF(); // nodeId
+
+ long created = di.readLong();
+ long accessed = di.readLong();
+ int requests = di.readInt();
+
+ if (session == null)
+ session = (HashedSession)newSession(created, accessed, clusterId);
+ session.setRequests(requests);
+
+ int size = di.readInt();
+
+ restoreSessionAttributes(di, size, session);
+
+ try
+ {
+ int maxIdle = di.readInt();
+ session.setMaxInactiveInterval(maxIdle);
+ }
+ catch (EOFException e)
+ {
+ LOG.debug("No maxInactiveInterval persisted for session "+clusterId);
+ LOG.ignore(e);
+ }
+
+ return session;
+ }
+
+
+ private void restoreSessionAttributes (InputStream is, int size, HashedSession session)
+ throws Exception
+ {
+ if (size>0)
+ {
+ ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is);
+ for (int i=0; i<size;i++)
+ {
+ String key = ois.readUTF();
+ Object value = ois.readObject();
+ session.setAttribute(key,value);
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HashedSession extends MemSession
+{
+ private static final Logger LOG = Log.getLogger(HashedSession.class);
+
+ private final HashSessionManager _hashSessionManager;
+
+ /** Whether the session has been saved because it has been deemed idle;
+ * in which case its attribute map will have been saved and cleared. */
+ private transient boolean _idled = false;
+
+ /** Whether there has already been an attempt to save this session
+ * which has failed. If there has, there will be no more save attempts
+ * for this session. This is to stop the logs being flooded with errors
+ * due to serialization failures that are most likely caused by user
+ * data stored in the session that is not serializable. */
+ private transient boolean _saveFailed = false;
+
+ /**
+ * True if an attempt has been made to de-idle a session and it failed. Once
+ * true, the session will not be attempted to be de-idled again.
+ */
+ private transient boolean _deIdleFailed = false;
+
+ /* ------------------------------------------------------------- */
+ protected HashedSession(HashSessionManager hashSessionManager, HttpServletRequest request)
+ {
+ super(hashSessionManager,request);
+ _hashSessionManager = hashSessionManager;
+ }
+
+ /* ------------------------------------------------------------- */
+ protected HashedSession(HashSessionManager hashSessionManager, long created, long accessed, String clusterId)
+ {
+ super(hashSessionManager,created, accessed, clusterId);
+ _hashSessionManager = hashSessionManager;
+ }
+
+ /* ------------------------------------------------------------- */
+ protected void checkValid()
+ {
+ if (!_deIdleFailed && _hashSessionManager._idleSavePeriodMs!=0)
+ deIdle();
+ super.checkValid();
+ }
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public void setMaxInactiveInterval(int secs)
+ {
+ super.setMaxInactiveInterval(secs);
+ if (getMaxInactiveInterval()>0&&(getMaxInactiveInterval()*1000L/10)<_hashSessionManager._scavengePeriodMs)
+ _hashSessionManager.setScavengePeriod((secs+9)/10);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doInvalidate()
+ throws IllegalStateException
+ {
+ super.doInvalidate();
+ remove();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Remove from the disk
+ */
+ synchronized void remove ()
+ {
+ if (_hashSessionManager._storeDir!=null && getId()!=null)
+ {
+ String id=getId();
+ File f = new File(_hashSessionManager._storeDir, id);
+ f.delete();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ synchronized void save(boolean reactivate)
+ throws Exception
+ {
+ // Only idle the session if not already idled and no previous save/idle has failed
+ if (!isIdled() && !_saveFailed)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Saving {} {}",super.getId(),reactivate);
+
+ try
+ {
+ willPassivate();
+ save();
+ if (reactivate)
+ didActivate();
+ else
+ clearAttributes();
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem saving session " + super.getId(), e);
+ _idled=false; // assume problem was before _values.clear();
+ }
+ }
+ }
+
+
+
+ synchronized void save ()
+ throws Exception
+ {
+ File file = null;
+ FileOutputStream fos = null;
+ if (!_saveFailed && _hashSessionManager._storeDir != null)
+ {
+ try
+ {
+ file = new File(_hashSessionManager._storeDir, super.getId());
+ if (file.exists())
+ file.delete();
+ file.createNewFile();
+ fos = new FileOutputStream(file);
+ save(fos);
+ IO.close(fos);
+ }
+ catch (Exception e)
+ {
+ saveFailed(); // We won't try again for this session
+ if (fos != null) IO.close(fos);
+ if (file != null) file.delete(); // No point keeping the file if we didn't save the whole session
+ throw e;
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public synchronized void save(OutputStream os) throws IOException
+ {
+ DataOutputStream out = new DataOutputStream(os);
+ out.writeUTF(getClusterId());
+ out.writeUTF(getNodeId());
+ out.writeLong(getCreationTime());
+ out.writeLong(getAccessed());
+
+ /* Don't write these out, as they don't make sense to store because they
+ * either they cannot be true or their value will be restored in the
+ * Session constructor.
+ */
+ //out.writeBoolean(_invalid);
+ //out.writeBoolean(_doInvalidate);
+ //out.writeBoolean( _newSession);
+ out.writeInt(getRequests());
+ out.writeInt(getAttributes());
+ ObjectOutputStream oos = new ObjectOutputStream(out);
+ Enumeration<String> e=getAttributeNames();
+ while(e.hasMoreElements())
+ {
+ String key=e.nextElement();
+ oos.writeUTF(key);
+ oos.writeObject(doGet(key));
+ }
+
+ out.writeInt(getMaxInactiveInterval());
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void deIdle()
+ {
+ if (isIdled() && !_deIdleFailed)
+ {
+ // Access now to prevent race with idling period
+ access(System.currentTimeMillis());
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("De-idling " + super.getId());
+
+ FileInputStream fis = null;
+
+ try
+ {
+ File file = new File(_hashSessionManager._storeDir, super.getId());
+ if (!file.exists() || !file.canRead())
+ throw new FileNotFoundException(file.getName());
+
+ fis = new FileInputStream(file);
+ _idled = false;
+ _hashSessionManager.restoreSession(fis, this);
+ IO.close(fis);
+
+ didActivate();
+
+ // If we are doing period saves, then there is no point deleting at this point
+ if (_hashSessionManager._savePeriodMs == 0)
+ file.delete();
+ }
+ catch (Exception e)
+ {
+ deIdleFailed();
+ LOG.warn("Problem de-idling session " + super.getId(), e);
+ if (fis != null) IO.close(fis);//Must ensure closed before invalidate
+ invalidate();
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Idle the session to reduce session memory footprint.
+ *
+ * The session is idled by persisting it, then clearing the session values attribute map and finally setting
+ * it to an idled state.
+ */
+ public synchronized void idle()
+ throws Exception
+ {
+ save(false);
+ _idled = true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized boolean isIdled()
+ {
+ return _idled;
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized boolean isSaveFailed()
+ {
+ return _saveFailed;
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void saveFailed()
+ {
+ _saveFailed = true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void deIdleFailed()
+ {
+ _deIdleFailed = true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized boolean isDeIdleFailed()
+ {
+ return _deIdleFailed;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.sql.Blob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import javax.naming.InitialContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.sql.DataSource;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+
+
+/**
+ * JDBCSessionIdManager
+ *
+ * SessionIdManager implementation that uses a database to store in-use session ids,
+ * to support distributed sessions.
+ *
+ */
+public class JDBCSessionIdManager extends AbstractSessionIdManager
+{
+ final static Logger LOG = SessionHandler.LOG;
+ public final static int MAX_INTERVAL_NOT_SET = -999;
+
+ protected final HashSet<String> _sessionIds = new HashSet<String>();
+ protected Server _server;
+ protected Driver _driver;
+ protected String _driverClassName;
+ protected String _connectionUrl;
+ protected DataSource _datasource;
+ protected String _jndiName;
+
+ protected int _deleteBlockSize = 10; //number of ids to include in where 'in' clause
+
+ protected Scheduler.Task _task; //scavenge task
+ protected Scheduler _scheduler;
+ protected Scavenger _scavenger;
+ protected boolean _ownScheduler;
+ protected long _lastScavengeTime;
+ protected long _scavengeIntervalMs = 1000L * 60 * 10; //10mins
+
+
+ protected String _createSessionIdTable;
+ protected String _createSessionTable;
+
+ protected String _selectBoundedExpiredSessions;
+ private String _selectExpiredSessions;
+
+ protected String _insertId;
+ protected String _deleteId;
+ protected String _queryId;
+
+ protected String _insertSession;
+ protected String _deleteSession;
+ protected String _updateSession;
+ protected String _updateSessionNode;
+ protected String _updateSessionAccessTime;
+
+ protected DatabaseAdaptor _dbAdaptor = new DatabaseAdaptor();
+ protected SessionIdTableSchema _sessionIdTableSchema = new SessionIdTableSchema();
+ protected SessionTableSchema _sessionTableSchema = new SessionTableSchema();
+
+
+
+
+ /**
+ * SessionTableSchema
+ *
+ */
+ public static class SessionTableSchema
+ {
+ protected DatabaseAdaptor _dbAdaptor;
+ protected String _tableName = "JettySessions";
+ protected String _rowIdColumn = "rowId";
+ protected String _idColumn = "sessionId";
+ protected String _contextPathColumn = "contextPath";
+ protected String _virtualHostColumn = "virtualHost";
+ protected String _lastNodeColumn = "lastNode";
+ protected String _accessTimeColumn = "accessTime";
+ protected String _lastAccessTimeColumn = "lastAccessTime";
+ protected String _createTimeColumn = "createTime";
+ protected String _cookieTimeColumn = "cookieTime";
+ protected String _lastSavedTimeColumn = "lastSavedTime";
+ protected String _expiryTimeColumn = "expiryTime";
+ protected String _maxIntervalColumn = "maxInterval";
+ protected String _mapColumn = "map";
+
+
+ protected void setDatabaseAdaptor(DatabaseAdaptor dbadaptor)
+ {
+ _dbAdaptor = dbadaptor;
+ }
+
+
+ public String getTableName()
+ {
+ return _tableName;
+ }
+ public void setTableName(String tableName)
+ {
+ checkNotNull(tableName);
+ _tableName = tableName;
+ }
+ public String getRowIdColumn()
+ {
+ if ("rowId".equals(_rowIdColumn) && _dbAdaptor.isRowIdReserved())
+ _rowIdColumn = "srowId";
+ return _rowIdColumn;
+ }
+ public void setRowIdColumn(String rowIdColumn)
+ {
+ checkNotNull(rowIdColumn);
+ if (_dbAdaptor == null)
+ throw new IllegalStateException ("DbAdaptor is null");
+
+ if (_dbAdaptor.isRowIdReserved() && "rowId".equals(rowIdColumn))
+ throw new IllegalArgumentException("rowId is reserved word for Oracle");
+
+ _rowIdColumn = rowIdColumn;
+ }
+ public String getIdColumn()
+ {
+ return _idColumn;
+ }
+ public void setIdColumn(String idColumn)
+ {
+ checkNotNull(idColumn);
+ _idColumn = idColumn;
+ }
+ public String getContextPathColumn()
+ {
+ return _contextPathColumn;
+ }
+ public void setContextPathColumn(String contextPathColumn)
+ {
+ checkNotNull(contextPathColumn);
+ _contextPathColumn = contextPathColumn;
+ }
+ public String getVirtualHostColumn()
+ {
+ return _virtualHostColumn;
+ }
+ public void setVirtualHostColumn(String virtualHostColumn)
+ {
+ checkNotNull(virtualHostColumn);
+ _virtualHostColumn = virtualHostColumn;
+ }
+ public String getLastNodeColumn()
+ {
+ return _lastNodeColumn;
+ }
+ public void setLastNodeColumn(String lastNodeColumn)
+ {
+ checkNotNull(lastNodeColumn);
+ _lastNodeColumn = lastNodeColumn;
+ }
+ public String getAccessTimeColumn()
+ {
+ return _accessTimeColumn;
+ }
+ public void setAccessTimeColumn(String accessTimeColumn)
+ {
+ checkNotNull(accessTimeColumn);
+ _accessTimeColumn = accessTimeColumn;
+ }
+ public String getLastAccessTimeColumn()
+ {
+ return _lastAccessTimeColumn;
+ }
+ public void setLastAccessTimeColumn(String lastAccessTimeColumn)
+ {
+ checkNotNull(lastAccessTimeColumn);
+ _lastAccessTimeColumn = lastAccessTimeColumn;
+ }
+ public String getCreateTimeColumn()
+ {
+ return _createTimeColumn;
+ }
+ public void setCreateTimeColumn(String createTimeColumn)
+ {
+ checkNotNull(createTimeColumn);
+ _createTimeColumn = createTimeColumn;
+ }
+ public String getCookieTimeColumn()
+ {
+ return _cookieTimeColumn;
+ }
+ public void setCookieTimeColumn(String cookieTimeColumn)
+ {
+ checkNotNull(cookieTimeColumn);
+ _cookieTimeColumn = cookieTimeColumn;
+ }
+ public String getLastSavedTimeColumn()
+ {
+ return _lastSavedTimeColumn;
+ }
+ public void setLastSavedTimeColumn(String lastSavedTimeColumn)
+ {
+ checkNotNull(lastSavedTimeColumn);
+ _lastSavedTimeColumn = lastSavedTimeColumn;
+ }
+ public String getExpiryTimeColumn()
+ {
+ return _expiryTimeColumn;
+ }
+ public void setExpiryTimeColumn(String expiryTimeColumn)
+ {
+ checkNotNull(expiryTimeColumn);
+ _expiryTimeColumn = expiryTimeColumn;
+ }
+ public String getMaxIntervalColumn()
+ {
+ return _maxIntervalColumn;
+ }
+ public void setMaxIntervalColumn(String maxIntervalColumn)
+ {
+ checkNotNull(maxIntervalColumn);
+ _maxIntervalColumn = maxIntervalColumn;
+ }
+ public String getMapColumn()
+ {
+ return _mapColumn;
+ }
+ public void setMapColumn(String mapColumn)
+ {
+ checkNotNull(mapColumn);
+ _mapColumn = mapColumn;
+ }
+
+ public String getCreateStatementAsString ()
+ {
+ if (_dbAdaptor == null)
+ throw new IllegalStateException ("No DBAdaptor");
+
+ String blobType = _dbAdaptor.getBlobType();
+ String longType = _dbAdaptor.getLongType();
+
+ return "create table "+_tableName+" ("+getRowIdColumn()+" varchar(120), "+_idColumn+" varchar(120), "+
+ _contextPathColumn+" varchar(60), "+_virtualHostColumn+" varchar(60), "+_lastNodeColumn+" varchar(60), "+_accessTimeColumn+" "+longType+", "+
+ _lastAccessTimeColumn+" "+longType+", "+_createTimeColumn+" "+longType+", "+_cookieTimeColumn+" "+longType+", "+
+ _lastSavedTimeColumn+" "+longType+", "+_expiryTimeColumn+" "+longType+", "+_maxIntervalColumn+" "+longType+", "+
+ _mapColumn+" "+blobType+", primary key("+getRowIdColumn()+"))";
+ }
+
+ public String getCreateIndexOverExpiryStatementAsString (String indexName)
+ {
+ return "create index "+indexName+" on "+getTableName()+" ("+getExpiryTimeColumn()+")";
+ }
+
+ public String getCreateIndexOverSessionStatementAsString (String indexName)
+ {
+ return "create index "+indexName+" on "+getTableName()+" ("+getIdColumn()+", "+getContextPathColumn()+")";
+ }
+
+ public String getAlterTableForMaxIntervalAsString ()
+ {
+ if (_dbAdaptor == null)
+ throw new IllegalStateException ("No DBAdaptor");
+ String longType = _dbAdaptor.getLongType();
+ return "alter table "+getTableName()+" add "+getMaxIntervalColumn()+" "+longType+" not null default "+MAX_INTERVAL_NOT_SET;
+ }
+
+ private void checkNotNull(String s)
+ {
+ if (s == null)
+ throw new IllegalArgumentException(s);
+ }
+ public String getInsertSessionStatementAsString()
+ {
+ return "insert into "+getTableName()+
+ " ("+getRowIdColumn()+", "+getIdColumn()+", "+getContextPathColumn()+", "+getVirtualHostColumn()+", "+getLastNodeColumn()+
+ ", "+getAccessTimeColumn()+", "+getLastAccessTimeColumn()+", "+getCreateTimeColumn()+", "+getCookieTimeColumn()+
+ ", "+getLastSavedTimeColumn()+", "+getExpiryTimeColumn()+", "+getMaxIntervalColumn()+", "+getMapColumn()+") "+
+ " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ }
+ public String getDeleteSessionStatementAsString()
+ {
+ return "delete from "+getTableName()+
+ " where "+getRowIdColumn()+" = ?";
+ }
+ public String getUpdateSessionStatementAsString()
+ {
+ return "update "+getTableName()+
+ " set "+getIdColumn()+" = ?, "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+
+ getLastAccessTimeColumn()+" = ?, "+getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+
+ getMaxIntervalColumn()+" = ?, "+getMapColumn()+" = ? where "+getRowIdColumn()+" = ?";
+ }
+ public String getUpdateSessionNodeStatementAsString()
+ {
+ return "update "+getTableName()+
+ " set "+getLastNodeColumn()+" = ? where "+getRowIdColumn()+" = ?";
+ }
+ public String getUpdateSessionAccessTimeStatementAsString()
+ {
+ return "update "+getTableName()+
+ " set "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+getLastAccessTimeColumn()+" = ?, "+
+ getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+getMaxIntervalColumn()+" = ? where "+getRowIdColumn()+" = ?";
+ }
+
+ public String getBoundedExpiredSessionsStatementAsString()
+ {
+ return "select * from "+getTableName()+" where "+getLastNodeColumn()+" = ? and "+getExpiryTimeColumn()+" >= ? and "+getExpiryTimeColumn()+" <= ?";
+ }
+
+ public String getSelectExpiredSessionsStatementAsString()
+ {
+ return "select * from "+getTableName()+" where "+getExpiryTimeColumn()+" >0 and "+getExpiryTimeColumn()+" <= ?";
+ }
+
+ public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts)
+ throws SQLException
+ {
+ if (_dbAdaptor == null)
+ throw new IllegalStateException("No DB adaptor");
+
+
+ if (contextPath == null || "".equals(contextPath))
+ {
+ if (_dbAdaptor.isEmptyStringNull())
+ {
+ PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
+ " where "+getIdColumn()+" = ? and "+
+ getContextPathColumn()+" is null and "+
+ getVirtualHostColumn()+" = ?");
+ statement.setString(1, rowId);
+ statement.setString(2, virtualHosts);
+
+ return statement;
+ }
+ }
+
+ PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
+ " where "+getIdColumn()+" = ? and "+getContextPathColumn()+
+ " = ? and "+getVirtualHostColumn()+" = ?");
+ statement.setString(1, rowId);
+ statement.setString(2, contextPath);
+ statement.setString(3, virtualHosts);
+
+ return statement;
+ }
+ }
+
+
+
+ /**
+ * SessionIdTableSchema
+ *
+ */
+ public static class SessionIdTableSchema
+ {
+ protected DatabaseAdaptor _dbAdaptor;
+ protected String _tableName = "JettySessionIds";
+ protected String _idColumn = "id";
+
+ public void setDatabaseAdaptor(DatabaseAdaptor dbAdaptor)
+ {
+ _dbAdaptor = dbAdaptor;
+ }
+ public String getIdColumn()
+ {
+ return _idColumn;
+ }
+
+ public void setIdColumn(String idColumn)
+ {
+ checkNotNull(idColumn);
+ _idColumn = idColumn;
+ }
+
+ public String getTableName()
+ {
+ return _tableName;
+ }
+
+ public void setTableName(String tableName)
+ {
+ checkNotNull(tableName);
+ _tableName = tableName;
+ }
+
+ public String getInsertStatementAsString ()
+ {
+ return "insert into "+_tableName+" ("+_idColumn+") values (?)";
+ }
+
+ public String getDeleteStatementAsString ()
+ {
+ return "delete from "+_tableName+" where "+_idColumn+" = ?";
+ }
+
+ public String getSelectStatementAsString ()
+ {
+ return "select * from "+_tableName+" where "+_idColumn+" = ?";
+ }
+
+ public String getCreateStatementAsString ()
+ {
+ return "create table "+_tableName+" ("+_idColumn+" varchar(120), primary key("+_idColumn+"))";
+ }
+
+ private void checkNotNull(String s)
+ {
+ if (s == null)
+ throw new IllegalArgumentException(s);
+ }
+ }
+
+
+ /**
+ * DatabaseAdaptor
+ *
+ * Handles differences between databases.
+ *
+ * Postgres uses the getBytes and setBinaryStream methods to access
+ * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL
+ * is happy to use the "blob" type and getBlob() methods instead.
+ *
+ * TODO if the differences become more major it would be worthwhile
+ * refactoring this class.
+ */
+ public static class DatabaseAdaptor
+ {
+ String _dbName;
+ boolean _isLower;
+ boolean _isUpper;
+
+ protected String _blobType; //if not set, is deduced from the type of the database at runtime
+ protected String _longType; //if not set, is deduced from the type of the database at runtime
+
+
+ public DatabaseAdaptor ()
+ {
+ }
+
+
+ public void adaptTo(DatabaseMetaData dbMeta)
+ throws SQLException
+ {
+ _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH);
+ LOG.debug ("Using database {}",_dbName);
+ _isLower = dbMeta.storesLowerCaseIdentifiers();
+ _isUpper = dbMeta.storesUpperCaseIdentifiers();
+ }
+
+
+ public void setBlobType(String blobType)
+ {
+ _blobType = blobType;
+ }
+
+ public String getBlobType ()
+ {
+ if (_blobType != null)
+ return _blobType;
+
+ if (_dbName.startsWith("postgres"))
+ return "bytea";
+
+ return "blob";
+ }
+
+
+ public void setLongType(String longType)
+ {
+ _longType = longType;
+ }
+
+
+ public String getLongType ()
+ {
+ if (_longType != null)
+ return _longType;
+
+ if (_dbName == null)
+ throw new IllegalStateException ("DbAdaptor missing metadata");
+
+ if (_dbName.startsWith("oracle"))
+ return "number(20)";
+
+ return "bigint";
+ }
+
+
+ /**
+ * Convert a camel case identifier into either upper or lower
+ * depending on the way the db stores identifiers.
+ *
+ * @param identifier
+ * @return the converted identifier
+ */
+ public String convertIdentifier (String identifier)
+ {
+ if (_dbName == null)
+ throw new IllegalStateException ("DbAdaptor missing metadata");
+
+ if (_isLower)
+ return identifier.toLowerCase(Locale.ENGLISH);
+ if (_isUpper)
+ return identifier.toUpperCase(Locale.ENGLISH);
+
+ return identifier;
+ }
+
+ public String getDBName ()
+ {
+ return _dbName;
+ }
+
+
+ public InputStream getBlobInputStream (ResultSet result, String columnName)
+ throws SQLException
+ {
+ if (_dbName == null)
+ throw new IllegalStateException ("DbAdaptor missing metadata");
+
+ if (_dbName.startsWith("postgres"))
+ {
+ byte[] bytes = result.getBytes(columnName);
+ return new ByteArrayInputStream(bytes);
+ }
+
+ Blob blob = result.getBlob(columnName);
+ return blob.getBinaryStream();
+ }
+
+
+ public boolean isEmptyStringNull ()
+ {
+ if (_dbName == null)
+ throw new IllegalStateException ("DbAdaptor missing metadata");
+
+ return (_dbName.startsWith("oracle"));
+ }
+
+ /**
+ * rowId is a reserved word for Oracle, so change the name of this column
+ * @return true if db in use is oracle
+ */
+ public boolean isRowIdReserved ()
+ {
+ if (_dbName == null)
+ throw new IllegalStateException ("DbAdaptor missing metadata");
+
+ return (_dbName != null && _dbName.startsWith("oracle"));
+ }
+ }
+
+
+ /**
+ * Scavenger
+ *
+ */
+ protected class Scavenger implements Runnable
+ {
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ scavenge();
+ }
+ finally
+ {
+ if (_scheduler != null && _scheduler.isRunning())
+ _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+
+
+ public JDBCSessionIdManager(Server server)
+ {
+ super();
+ _server=server;
+ }
+
+ public JDBCSessionIdManager(Server server, Random random)
+ {
+ super(random);
+ _server=server;
+ }
+
+ /**
+ * Configure jdbc connection information via a jdbc Driver
+ *
+ * @param driverClassName
+ * @param connectionUrl
+ */
+ public void setDriverInfo (String driverClassName, String connectionUrl)
+ {
+ _driverClassName=driverClassName;
+ _connectionUrl=connectionUrl;
+ }
+
+ /**
+ * Configure jdbc connection information via a jdbc Driver
+ *
+ * @param driverClass
+ * @param connectionUrl
+ */
+ public void setDriverInfo (Driver driverClass, String connectionUrl)
+ {
+ _driver=driverClass;
+ _connectionUrl=connectionUrl;
+ }
+
+
+ public void setDatasource (DataSource ds)
+ {
+ _datasource = ds;
+ }
+
+ public DataSource getDataSource ()
+ {
+ return _datasource;
+ }
+
+ public String getDriverClassName()
+ {
+ return _driverClassName;
+ }
+
+ public String getConnectionUrl ()
+ {
+ return _connectionUrl;
+ }
+
+ public void setDatasourceName (String jndi)
+ {
+ _jndiName=jndi;
+ }
+
+ public String getDatasourceName ()
+ {
+ return _jndiName;
+ }
+
+ /**
+ * @param name
+ * @deprecated see DbAdaptor.setBlobType
+ */
+ public void setBlobType (String name)
+ {
+ _dbAdaptor.setBlobType(name);
+ }
+
+ public DatabaseAdaptor getDbAdaptor()
+ {
+ return _dbAdaptor;
+ }
+
+ public void setDbAdaptor(DatabaseAdaptor dbAdaptor)
+ {
+ if (dbAdaptor == null)
+ throw new IllegalStateException ("DbAdaptor cannot be null");
+
+ _dbAdaptor = dbAdaptor;
+ }
+
+ /**
+ * @return
+ * @deprecated see DbAdaptor.getBlobType
+ */
+ public String getBlobType ()
+ {
+ return _dbAdaptor.getBlobType();
+ }
+
+ /**
+ * @return
+ * @deprecated see DbAdaptor.getLogType
+ */
+ public String getLongType()
+ {
+ return _dbAdaptor.getLongType();
+ }
+
+ /**
+ * @param longType
+ * @deprecated see DbAdaptor.setLongType
+ */
+ public void setLongType(String longType)
+ {
+ _dbAdaptor.setLongType(longType);
+ }
+
+ public SessionIdTableSchema getSessionIdTableSchema()
+ {
+ return _sessionIdTableSchema;
+ }
+
+ public void setSessionIdTableSchema(SessionIdTableSchema sessionIdTableSchema)
+ {
+ if (sessionIdTableSchema == null)
+ throw new IllegalArgumentException("Null SessionIdTableSchema");
+
+ _sessionIdTableSchema = sessionIdTableSchema;
+ }
+
+ public SessionTableSchema getSessionTableSchema()
+ {
+ return _sessionTableSchema;
+ }
+
+ public void setSessionTableSchema(SessionTableSchema sessionTableSchema)
+ {
+ _sessionTableSchema = sessionTableSchema;
+ }
+
+ public void setDeleteBlockSize (int bsize)
+ {
+ this._deleteBlockSize = bsize;
+ }
+
+ public int getDeleteBlockSize ()
+ {
+ return this._deleteBlockSize;
+ }
+
+ public void setScavengeInterval (long sec)
+ {
+ if (sec<=0)
+ sec=60;
+
+ long old_period=_scavengeIntervalMs;
+ long period=sec*1000L;
+
+ _scavengeIntervalMs=period;
+
+ //add a bit of variability into the scavenge time so that not all
+ //nodes with the same scavenge time sync up
+ long tenPercent = _scavengeIntervalMs/10;
+ if ((System.currentTimeMillis()%2) == 0)
+ _scavengeIntervalMs += tenPercent;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
+
+ //if (_timer!=null && (period!=old_period || _task==null))
+ if (_scheduler != null && (period!=old_period || _task==null))
+ {
+ synchronized (this)
+ {
+ if (_task!=null)
+ _task.cancel();
+ if (_scavenger == null)
+ _scavenger = new Scavenger();
+ _task = _scheduler.schedule(_scavenger,_scavengeIntervalMs,TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+
+ public long getScavengeInterval ()
+ {
+ return _scavengeIntervalMs/1000;
+ }
+
+
+ @Override
+ public void addSession(HttpSession session)
+ {
+ if (session == null)
+ return;
+
+ synchronized (_sessionIds)
+ {
+ String id = ((JDBCSessionManager.Session)session).getClusterId();
+ try
+ {
+ insert(id);
+ _sessionIds.add(id);
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem storing session id="+id, e);
+ }
+ }
+ }
+
+
+ public void addSession(String id)
+ {
+ if (id == null)
+ return;
+
+ synchronized (_sessionIds)
+ {
+ try
+ {
+ insert(id);
+ _sessionIds.add(id);
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem storing session id="+id, e);
+ }
+ }
+ }
+
+
+
+ @Override
+ public void removeSession(HttpSession session)
+ {
+ if (session == null)
+ return;
+
+ removeSession(((JDBCSessionManager.Session)session).getClusterId());
+ }
+
+
+
+ public void removeSession (String id)
+ {
+
+ if (id == null)
+ return;
+
+ synchronized (_sessionIds)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Removing sessionid="+id);
+ try
+ {
+ _sessionIds.remove(id);
+ delete(id);
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem removing session id="+id, e);
+ }
+ }
+
+ }
+
+
+ @Override
+ public boolean idInUse(String id)
+ {
+ if (id == null)
+ return false;
+
+ String clusterId = getClusterId(id);
+ boolean inUse = false;
+ synchronized (_sessionIds)
+ {
+ inUse = _sessionIds.contains(clusterId);
+ }
+
+
+ if (inUse)
+ return true; //optimisation - if this session is one we've been managing, we can check locally
+
+ //otherwise, we need to go to the database to check
+ try
+ {
+ return exists(clusterId);
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem checking inUse for id="+clusterId, e);
+ return false;
+ }
+ }
+
+ /**
+ * Invalidate the session matching the id on all contexts.
+ *
+ * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
+ */
+ @Override
+ public void invalidateAll(String id)
+ {
+ //take the id out of the list of known sessionids for this node
+ removeSession(id);
+
+ synchronized (_sessionIds)
+ {
+ //tell all contexts that may have a session object with this id to
+ //get rid of them
+ Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+ for (int i=0; contexts!=null && i<contexts.length; i++)
+ {
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ if (sessionHandler != null)
+ {
+ SessionManager manager = sessionHandler.getSessionManager();
+
+ if (manager != null && manager instanceof JDBCSessionManager)
+ {
+ ((JDBCSessionManager)manager).invalidateSession(id);
+ }
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
+ {
+ //generate a new id
+ String newClusterId = newSessionId(request.hashCode());
+
+ synchronized (_sessionIds)
+ {
+ removeSession(oldClusterId);//remove the old one from the list (and database)
+ addSession(newClusterId); //add in the new session id to the list (and database)
+
+ //tell all contexts to update the id
+ Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+ for (int i=0; contexts!=null && i<contexts.length; i++)
+ {
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ if (sessionHandler != null)
+ {
+ SessionManager manager = sessionHandler.getSessionManager();
+
+ if (manager != null && manager instanceof JDBCSessionManager)
+ {
+ ((JDBCSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Start up the id manager.
+ *
+ * Makes necessary database tables and starts a Session
+ * scavenger thread.
+ */
+ @Override
+ public void doStart()
+ throws Exception
+ {
+ initializeDatabase();
+ prepareTables();
+ super.doStart();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
+
+ //try and use a common scheduler, fallback to own
+ _scheduler =_server.getBean(Scheduler.class);
+ if (_scheduler == null)
+ {
+ _scheduler = new ScheduledExecutorScheduler();
+ _ownScheduler = true;
+ _scheduler.start();
+ }
+
+ setScavengeInterval(getScavengeInterval());
+ }
+
+ /**
+ * Stop the scavenger.
+ */
+ @Override
+ public void doStop ()
+ throws Exception
+ {
+ synchronized(this)
+ {
+ if (_task!=null)
+ _task.cancel();
+ _task=null;
+ if (_ownScheduler && _scheduler !=null)
+ _scheduler.stop();
+ _scheduler=null;
+ }
+ _sessionIds.clear();
+ super.doStop();
+ }
+
+ /**
+ * Get a connection from the driver or datasource.
+ *
+ * @return the connection for the datasource
+ * @throws SQLException
+ */
+ protected Connection getConnection ()
+ throws SQLException
+ {
+ if (_datasource != null)
+ return _datasource.getConnection();
+ else
+ return DriverManager.getConnection(_connectionUrl);
+ }
+
+
+
+
+
+
+ /**
+ * Set up the tables in the database
+ * @throws SQLException
+ */
+ /**
+ * @throws SQLException
+ */
+ private void prepareTables()
+ throws SQLException
+ {
+ if (_sessionIdTableSchema == null)
+ throw new IllegalStateException ("No SessionIdTableSchema");
+
+ if (_sessionTableSchema == null)
+ throw new IllegalStateException ("No SessionTableSchema");
+
+ try (Connection connection = getConnection();
+ Statement statement = connection.createStatement())
+ {
+ //make the id table
+ connection.setAutoCommit(true);
+ DatabaseMetaData metaData = connection.getMetaData();
+ _dbAdaptor.adaptTo(metaData);
+ _sessionTableSchema.setDatabaseAdaptor(_dbAdaptor);
+ _sessionIdTableSchema.setDatabaseAdaptor(_dbAdaptor);
+
+ _createSessionIdTable = _sessionIdTableSchema.getCreateStatementAsString();
+ _insertId = _sessionIdTableSchema.getInsertStatementAsString();
+ _deleteId = _sessionIdTableSchema.getDeleteStatementAsString();
+ _queryId = _sessionIdTableSchema.getSelectStatementAsString();
+
+ //checking for table existence is case-sensitive, but table creation is not
+ String tableName = _dbAdaptor.convertIdentifier(_sessionIdTableSchema.getTableName());
+ try (ResultSet result = metaData.getTables(null, null, tableName, null))
+ {
+ if (!result.next())
+ {
+ //table does not exist, so create it
+ statement.executeUpdate(_createSessionIdTable);
+ }
+ }
+
+ //make the session table if necessary
+ tableName = _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName());
+ try (ResultSet result = metaData.getTables(null, null, tableName, null))
+ {
+ if (!result.next())
+ {
+ //table does not exist, so create it
+ _createSessionTable = _sessionTableSchema.getCreateStatementAsString();
+ statement.executeUpdate(_createSessionTable);
+ }
+ else
+ {
+ //session table exists, check it has maxinterval column
+ ResultSet colResult = null;
+ try
+ {
+ colResult = metaData.getColumns(null, null,
+ _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName()),
+ _dbAdaptor.convertIdentifier(_sessionTableSchema.getMaxIntervalColumn()));
+ }
+ catch (SQLException s)
+ {
+ LOG.warn("Problem checking if "+_sessionTableSchema.getTableName()+
+ " table contains "+_sessionTableSchema.getMaxIntervalColumn()+" column. Ensure table contains column definition: \""
+ +_sessionTableSchema.getMaxIntervalColumn()+" long not null default -999\"");
+ throw s;
+ }
+ try
+ {
+ if (!colResult.next())
+ {
+ try
+ {
+ //add the maxinterval column
+ statement.executeUpdate(_sessionTableSchema.getAlterTableForMaxIntervalAsString());
+ }
+ catch (SQLException s)
+ {
+ LOG.warn("Problem adding "+_sessionTableSchema.getMaxIntervalColumn()+
+ " column. Ensure table contains column definition: \""+_sessionTableSchema.getMaxIntervalColumn()+
+ " long not null default -999\"");
+ throw s;
+ }
+ }
+ }
+ finally
+ {
+ colResult.close();
+ }
+ }
+ }
+ //make some indexes on the JettySessions table
+ String index1 = "idx_"+_sessionTableSchema.getTableName()+"_expiry";
+ String index2 = "idx_"+_sessionTableSchema.getTableName()+"_session";
+
+ boolean index1Exists = false;
+ boolean index2Exists = false;
+ try (ResultSet result = metaData.getIndexInfo(null, null, tableName, false, false))
+ {
+ while (result.next())
+ {
+ String idxName = result.getString("INDEX_NAME");
+ if (index1.equalsIgnoreCase(idxName))
+ index1Exists = true;
+ else if (index2.equalsIgnoreCase(idxName))
+ index2Exists = true;
+ }
+ }
+ if (!index1Exists)
+ statement.executeUpdate(_sessionTableSchema.getCreateIndexOverExpiryStatementAsString(index1));
+ if (!index2Exists)
+ statement.executeUpdate(_sessionTableSchema.getCreateIndexOverSessionStatementAsString(index2));
+
+ //set up some strings representing the statements for session manipulation
+ _insertSession = _sessionTableSchema.getInsertSessionStatementAsString();
+ _deleteSession = _sessionTableSchema.getDeleteSessionStatementAsString();
+ _updateSession = _sessionTableSchema.getUpdateSessionStatementAsString();
+ _updateSessionNode = _sessionTableSchema.getUpdateSessionNodeStatementAsString();
+ _updateSessionAccessTime = _sessionTableSchema.getUpdateSessionAccessTimeStatementAsString();
+ _selectBoundedExpiredSessions = _sessionTableSchema.getBoundedExpiredSessionsStatementAsString();
+ _selectExpiredSessions = _sessionTableSchema.getSelectExpiredSessionsStatementAsString();
+ }
+ }
+
+ /**
+ * Insert a new used session id into the table.
+ *
+ * @param id
+ * @throws SQLException
+ */
+ private void insert (String id)
+ throws SQLException
+ {
+ try (Connection connection = getConnection();
+ PreparedStatement query = connection.prepareStatement(_queryId))
+ {
+ connection.setAutoCommit(true);
+ query.setString(1, id);
+ try (ResultSet result = query.executeQuery())
+ {
+ //only insert the id if it isn't in the db already
+ if (!result.next())
+ {
+ try (PreparedStatement statement = connection.prepareStatement(_insertId))
+ {
+ statement.setString(1, id);
+ statement.executeUpdate();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove a session id from the table.
+ *
+ * @param id
+ * @throws SQLException
+ */
+ private void delete (String id)
+ throws SQLException
+ {
+ try (Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(_deleteId))
+ {
+ connection.setAutoCommit(true);
+ statement.setString(1, id);
+ statement.executeUpdate();
+ }
+ }
+
+
+ /**
+ * Check if a session id exists.
+ *
+ * @param id
+ * @return
+ * @throws SQLException
+ */
+ private boolean exists (String id)
+ throws SQLException
+ {
+ try (Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(_queryId))
+ {
+ connection.setAutoCommit(true);
+ statement.setString(1, id);
+ try (ResultSet result = statement.executeQuery())
+ {
+ return result.next();
+ }
+ }
+ }
+
+ /**
+ * Look for sessions in the database that have expired.
+ *
+ * We do this in the SessionIdManager and not the SessionManager so
+ * that we only have 1 scavenger, otherwise if there are n SessionManagers
+ * there would be n scavengers, all contending for the database.
+ *
+ * We look first for sessions that expired in the previous interval, then
+ * for sessions that expired previously - these are old sessions that no
+ * node is managing any more and have become stuck in the database.
+ */
+ private void scavenge ()
+ {
+ Connection connection = null;
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(getWorkerName()+"- Scavenge sweep started at "+System.currentTimeMillis());
+ if (_lastScavengeTime > 0)
+ {
+ connection = getConnection();
+ connection.setAutoCommit(true);
+ Set<String> expiredSessionIds = new HashSet<String>();
+
+
+ //Pass 1: find sessions for which we were last managing node that have just expired since last pass
+ long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
+ long upperBound = _lastScavengeTime;
+ if (LOG.isDebugEnabled())
+ LOG.debug (getWorkerName()+"- Pass 1: Searching for sessions expired between "+lowerBound + " and "+upperBound);
+
+ try (PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions))
+ {
+ statement.setString(1, getWorkerName());
+ statement.setLong(2, lowerBound);
+ statement.setLong(3, upperBound);
+ try (ResultSet result = statement.executeQuery())
+ {
+ while (result.next())
+ {
+ String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+ expiredSessionIds.add(sessionId);
+ if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
+ }
+ }
+ }
+ scavengeSessions(expiredSessionIds, false);
+
+
+ //Pass 2: find sessions that have expired a while ago for which this node was their last manager
+ try (PreparedStatement selectExpiredSessions = connection.prepareStatement(_selectExpiredSessions))
+ {
+ expiredSessionIds.clear();
+ upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
+ if (upperBound > 0)
+ {
+ if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 2: Searching for sessions expired before "+upperBound);
+ selectExpiredSessions.setLong(1, upperBound);
+ try (ResultSet result = selectExpiredSessions.executeQuery())
+ {
+ while (result.next())
+ {
+ String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+ String lastNode = result.getString(_sessionTableSchema.getLastNodeColumn());
+ if ((getWorkerName() == null && lastNode == null) || (getWorkerName() != null && getWorkerName().equals(lastNode)))
+ expiredSessionIds.add(sessionId);
+ if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId+" last managed by "+getWorkerName());
+ }
+ }
+ scavengeSessions(expiredSessionIds, false);
+ }
+
+
+ //Pass 3:
+ //find all sessions that have expired at least a couple of scanIntervals ago
+ //if we did not succeed in loading them (eg their related context no longer exists, can't be loaded etc) then
+ //they are simply deleted
+ upperBound = _lastScavengeTime - (3 * _scavengeIntervalMs);
+ expiredSessionIds.clear();
+ if (upperBound > 0)
+ {
+ if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 3: searching for sessions expired before "+upperBound);
+ selectExpiredSessions.setLong(1, upperBound);
+ try (ResultSet result = selectExpiredSessions.executeQuery())
+ {
+ while (result.next())
+ {
+ String sessionId = result.getString(_sessionTableSchema.getIdColumn());
+ expiredSessionIds.add(sessionId);
+ if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
+ }
+ }
+ scavengeSessions(expiredSessionIds, true);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ if (isRunning())
+ LOG.warn("Problem selecting expired sessions", e);
+ else
+ LOG.ignore(e);
+ }
+ finally
+ {
+ _lastScavengeTime=System.currentTimeMillis();
+ if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Scavenge sweep ended at "+_lastScavengeTime);
+ if (connection != null)
+ {
+ try
+ {
+ connection.close();
+ }
+ catch (SQLException e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * @param expiredSessionIds
+ */
+ private void scavengeSessions (Set<String> expiredSessionIds, boolean forceDelete)
+ {
+ Set<String> remainingIds = new HashSet<String>(expiredSessionIds);
+ Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+ for (int i=0; contexts!=null && i<contexts.length; i++)
+ {
+ SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+ if (sessionHandler != null)
+ {
+ SessionManager manager = sessionHandler.getSessionManager();
+ if (manager != null && manager instanceof JDBCSessionManager)
+ {
+ Set<String> successfullyExpiredIds = ((JDBCSessionManager)manager).expire(expiredSessionIds);
+ if (successfullyExpiredIds != null)
+ remainingIds.removeAll(successfullyExpiredIds);
+ }
+ }
+ }
+
+ //Any remaining ids are of those sessions that no context removed
+ if (!remainingIds.isEmpty() && forceDelete)
+ {
+ LOG.info("Forcibly deleting unrecoverable expired sessions {}", remainingIds);
+ try
+ {
+ //ensure they aren't in the local list of in-use session ids
+ synchronized (_sessionIds)
+ {
+ _sessionIds.removeAll(remainingIds);
+ }
+
+ cleanExpiredSessionIds(remainingIds);
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Error removing expired session ids", e);
+ }
+ }
+ }
+
+
+
+
+ private void cleanExpiredSessionIds (Set<String> expiredIds)
+ throws Exception
+ {
+ if (expiredIds == null || expiredIds.isEmpty())
+ return;
+
+ String[] ids = expiredIds.toArray(new String[expiredIds.size()]);
+ try (Connection con = getConnection())
+ {
+ con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
+ con.setAutoCommit(false);
+
+ int start = 0;
+ int end = 0;
+ int blocksize = _deleteBlockSize;
+ int block = 0;
+
+ try (Statement statement = con.createStatement())
+ {
+ while (end < ids.length)
+ {
+ start = block*blocksize;
+ if ((ids.length - start) >= blocksize)
+ end = start + blocksize;
+ else
+ end = ids.length;
+
+ //take them out of the sessionIds table
+ statement.executeUpdate(fillInClause("delete from "+_sessionIdTableSchema.getTableName()+" where "+_sessionIdTableSchema.getIdColumn()+" in ", ids, start, end));
+ //take them out of the sessions table
+ statement.executeUpdate(fillInClause("delete from "+_sessionTableSchema.getTableName()+" where "+_sessionTableSchema.getIdColumn()+" in ", ids, start, end));
+ block++;
+ }
+ }
+ catch (Exception e)
+ {
+ con.rollback();
+ throw e;
+ }
+ con.commit();
+ }
+ }
+
+
+
+ /**
+ *
+ * @param sql
+ * @param atoms
+ * @throws Exception
+ */
+ private String fillInClause (String sql, String[] literals, int start, int end)
+ throws Exception
+ {
+ StringBuffer buff = new StringBuffer();
+ buff.append(sql);
+ buff.append("(");
+ for (int i=start; i<end; i++)
+ {
+ buff.append("'"+(literals[i])+"'");
+ if (i+1<end)
+ buff.append(",");
+ }
+ buff.append(")");
+ return buff.toString();
+ }
+
+
+
+ private void initializeDatabase ()
+ throws Exception
+ {
+ if (_datasource != null)
+ return; //already set up
+
+ if (_jndiName!=null)
+ {
+ InitialContext ic = new InitialContext();
+ _datasource = (DataSource)ic.lookup(_jndiName);
+ }
+ else if ( _driver != null && _connectionUrl != null )
+ {
+ DriverManager.registerDriver(_driver);
+ }
+ else if (_driverClassName != null && _connectionUrl != null)
+ {
+ Class.forName(_driverClassName);
+ }
+ else
+ throw new IllegalStateException("No database configured for sessions");
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.ObjectOutputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.JDBCSessionIdManager.SessionTableSchema;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * JDBCSessionManager
+ *
+ * SessionManager that persists sessions to a database to enable clustering.
+ *
+ * Session data is persisted to the JettySessions table:
+ *
+ * rowId (unique in cluster: webapp name/path + virtualhost + sessionId)
+ * contextPath (of the context owning the session)
+ * sessionId (unique in a context)
+ * lastNode (name of node last handled session)
+ * accessTime (time in milliseconds session was accessed)
+ * lastAccessTime (previous time in milliseconds session was accessed)
+ * createTime (time in milliseconds session created)
+ * cookieTime (time in milliseconds session cookie created)
+ * lastSavedTime (last time in milliseconds session access times were saved)
+ * expiryTime (time in milliseconds that the session is due to expire)
+ * map (attribute map)
+ *
+ * As an optimization, to prevent thrashing the database, we do not persist
+ * the accessTime and lastAccessTime every time the session is accessed. Rather,
+ * we write it out every so often. The frequency is controlled by the saveIntervalSec
+ * field.
+ */
+public class JDBCSessionManager extends AbstractSessionManager
+{
+ private static final Logger LOG = Log.getLogger(JDBCSessionManager.class);
+
+ private ConcurrentHashMap<String, Session> _sessions;
+ protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
+ protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
+ protected SessionTableSchema _sessionTableSchema;
+
+
+
+
+ /**
+ * Session
+ *
+ * Session instance.
+ */
+ public class Session extends MemSession
+ {
+ private static final long serialVersionUID = 5208464051134226143L;
+
+ /**
+ * If dirty, session needs to be (re)persisted
+ */
+ protected boolean _dirty=false;
+
+
+ /**
+ * Time in msec since the epoch that a session cookie was set for this session
+ */
+ protected long _cookieSet;
+
+
+ /**
+ * Time in msec since the epoch that the session will expire
+ */
+ protected long _expiryTime;
+
+
+ /**
+ * Time in msec since the epoch that the session was last persisted
+ */
+ protected long _lastSaved;
+
+
+ /**
+ * Unique identifier of the last node to host the session
+ */
+ protected String _lastNode;
+
+
+ /**
+ * Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
+ */
+ protected String _virtualHost;
+
+
+ /**
+ * Unique row in db for session
+ */
+ protected String _rowId;
+
+
+ /**
+ * Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
+ */
+ protected String _canonicalContext;
+
+
+ /**
+ * Session from a request.
+ *
+ * @param request
+ */
+ protected Session (HttpServletRequest request)
+ {
+ super(JDBCSessionManager.this,request);
+ int maxInterval=getMaxInactiveInterval();
+ _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+ _virtualHost = JDBCSessionManager.getVirtualHost(_context);
+ _canonicalContext = canonicalize(_context.getContextPath());
+ _lastNode = getSessionIdManager().getWorkerName();
+ }
+
+
+ /**
+ * Session restored from database
+ * @param sessionId
+ * @param rowId
+ * @param created
+ * @param accessed
+ */
+ protected Session (String sessionId, String rowId, long created, long accessed, long maxInterval)
+ {
+ super(JDBCSessionManager.this, created, accessed, sessionId);
+ _rowId = rowId;
+ super.setMaxInactiveInterval((int)maxInterval); //restore the session's previous inactivity interval setting
+ _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+ }
+
+
+ protected synchronized String getRowId()
+ {
+ return _rowId;
+ }
+
+ protected synchronized void setRowId(String rowId)
+ {
+ _rowId = rowId;
+ }
+
+ public synchronized void setVirtualHost (String vhost)
+ {
+ _virtualHost=vhost;
+ }
+
+ public synchronized String getVirtualHost ()
+ {
+ return _virtualHost;
+ }
+
+ public synchronized long getLastSaved ()
+ {
+ return _lastSaved;
+ }
+
+ public synchronized void setLastSaved (long time)
+ {
+ _lastSaved=time;
+ }
+
+ public synchronized void setExpiryTime (long time)
+ {
+ _expiryTime=time;
+ }
+
+ public synchronized long getExpiryTime ()
+ {
+ return _expiryTime;
+ }
+
+
+ public synchronized void setCanonicalContext(String str)
+ {
+ _canonicalContext=str;
+ }
+
+ public synchronized String getCanonicalContext ()
+ {
+ return _canonicalContext;
+ }
+
+ public void setCookieSet (long ms)
+ {
+ _cookieSet = ms;
+ }
+
+ public synchronized long getCookieSet ()
+ {
+ return _cookieSet;
+ }
+
+ public synchronized void setLastNode (String node)
+ {
+ _lastNode=node;
+ }
+
+ public synchronized String getLastNode ()
+ {
+ return _lastNode;
+ }
+
+ @Override
+ public void setAttribute (String name, Object value)
+ {
+ Object old = changeAttribute(name, value);
+ if (value == null && old == null)
+ return; //if same as remove attribute but attribute was already removed, no change
+
+ _dirty = true;
+ }
+
+ @Override
+ public void removeAttribute (String name)
+ {
+ Object old = changeAttribute(name, null);
+ if (old != null) //only dirty if there was a previous value
+ _dirty=true;
+ }
+
+ @Override
+ protected void cookieSet()
+ {
+ _cookieSet = getAccessed();
+ }
+
+ /**
+ * Entry to session.
+ * Called by SessionHandler on inbound request and the session already exists in this node's memory.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSession#access(long)
+ */
+ @Override
+ protected boolean access(long time)
+ {
+ synchronized (this)
+ {
+ if (super.access(time))
+ {
+ int maxInterval=getMaxInactiveInterval();
+ _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
+ return true;
+ }
+ return false;
+ }
+ }
+
+
+
+
+
+ /**
+ * Change the max idle time for this session. This recalculates the expiry time.
+ * @see org.eclipse.jetty.server.session.AbstractSession#setMaxInactiveInterval(int)
+ */
+ @Override
+ public void setMaxInactiveInterval(int secs)
+ {
+ synchronized (this)
+ {
+ super.setMaxInactiveInterval(secs);
+ int maxInterval=getMaxInactiveInterval();
+ _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+ //force the session to be written out right now
+ try
+ {
+ updateSessionAccessTime(this);
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem saving changed max idle time for session "+ this, e);
+ }
+ }
+ }
+
+
+ /**
+ * Exit from session
+ * @see org.eclipse.jetty.server.session.AbstractSession#complete()
+ */
+ @Override
+ protected void complete()
+ {
+ synchronized (this)
+ {
+ super.complete();
+ try
+ {
+ if (isValid())
+ {
+ if (_dirty)
+ {
+ //The session attributes have changed, write to the db, ensuring
+ //http passivation/activation listeners called
+ save(true);
+ }
+ else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
+ {
+ updateSessionAccessTime(this);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem persisting changed session data id="+getId(), e);
+ }
+ finally
+ {
+ _dirty=false;
+ }
+ }
+ }
+
+ protected void save() throws Exception
+ {
+ synchronized (this)
+ {
+ try
+ {
+ updateSession(this);
+ }
+ finally
+ {
+ _dirty = false;
+ }
+ }
+ }
+
+ protected void save (boolean reactivate) throws Exception
+ {
+ synchronized (this)
+ {
+ if (_dirty)
+ {
+ //The session attributes have changed, write to the db, ensuring
+ //http passivation/activation listeners called
+ willPassivate();
+ updateSession(this);
+ if (reactivate)
+ didActivate();
+ }
+ }
+ }
+
+
+ @Override
+ protected void timeout() throws IllegalStateException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Timing out session id="+getClusterId());
+ super.timeout();
+ }
+
+
+ @Override
+ public String toString ()
+ {
+ return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+
+ ",created="+getCreationTime()+",accessed="+getAccessed()+
+ ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+
+ ",maxInterval="+getMaxInactiveInterval()+",lastSaved="+_lastSaved+",expiry="+_expiryTime;
+ }
+ }
+
+
+
+
+ /**
+ * Set the time in seconds which is the interval between
+ * saving the session access time to the database.
+ *
+ * This is an optimization that prevents the database from
+ * being overloaded when a session is accessed very frequently.
+ *
+ * On session exit, if the session attributes have NOT changed,
+ * the time at which we last saved the accessed
+ * time is compared to the current accessed time. If the interval
+ * is at least saveIntervalSecs, then the access time will be
+ * persisted to the database.
+ *
+ * If any session attribute does change, then the attributes and
+ * the accessed time are persisted.
+ *
+ * @param sec
+ */
+ public void setSaveInterval (long sec)
+ {
+ _saveIntervalSec=sec;
+ }
+
+ public long getSaveInterval ()
+ {
+ return _saveIntervalSec;
+ }
+
+
+
+ /**
+ * A method that can be implemented in subclasses to support
+ * distributed caching of sessions. This method will be
+ * called whenever the session is written to the database
+ * because the session data has changed.
+ *
+ * This could be used eg with a JMS backplane to notify nodes
+ * that the session has changed and to delete the session from
+ * the node's cache, and re-read it from the database.
+ * @param session
+ */
+ public void cacheInvalidate (Session session)
+ {
+
+ }
+
+
+ /**
+ * A session has been requested by its id on this node.
+ *
+ * Load the session by id AND context path from the database.
+ * Multiple contexts may share the same session id (due to dispatching)
+ * but they CANNOT share the same contents.
+ *
+ * Check if last node id is my node id, if so, then the session we have
+ * in memory cannot be stale. If another node used the session last, then
+ * we need to refresh from the db.
+ *
+ * NOTE: this method will go to the database, so if you only want to check
+ * for the existence of a Session in memory, use _sessions.get(id) instead.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
+ */
+ @Override
+ public Session getSession(String idInCluster)
+ {
+ Session session = null;
+
+ synchronized (this)
+ {
+ Session memSession = (Session)_sessions.get(idInCluster);
+
+ //check if we need to reload the session -
+ //as an optimization, don't reload on every access
+ //to reduce the load on the database. This introduces a window of
+ //possibility that the node may decide that the session is local to it,
+ //when the session has actually been live on another node, and then
+ //re-migrated to this node. This should be an extremely rare occurrence,
+ //as load-balancers are generally well-behaved and consistently send
+ //sessions to the same node, changing only iff that node fails.
+ //Session data = null;
+ long now = System.currentTimeMillis();
+ if (LOG.isDebugEnabled())
+ {
+ if (memSession==null)
+ LOG.debug("getSession("+idInCluster+"): not in session map,"+
+ " now="+now+
+ " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+ " interval="+(_saveIntervalSec * 1000L));
+ else
+ LOG.debug("getSession("+idInCluster+"): in session map, "+
+ " hashcode="+memSession.hashCode()+
+ " now="+now+
+ " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+ " interval="+(_saveIntervalSec * 1000L)+
+ " lastNode="+memSession._lastNode+
+ " thisNode="+getSessionIdManager().getWorkerName()+
+ " difference="+(now - memSession._lastSaved));
+ }
+
+ try
+ {
+ if (memSession==null)
+ {
+ LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db.");
+ session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+ }
+ else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
+ {
+ LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
+ session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+ }
+ else
+ {
+ LOG.debug("getSession("+idInCluster+"): session in session map");
+ session = memSession;
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Unable to load session "+idInCluster, e);
+ return null;
+ }
+
+
+ //If we have a session
+ if (session != null)
+ {
+ //If the session was last used on a different node, or session doesn't exist on this node
+ if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
+ {
+ //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory
+ if (session._expiryTime <= 0 || session._expiryTime > now)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
+
+ session.setLastNode(getSessionIdManager().getWorkerName());
+ _sessions.put(idInCluster, session);
+
+ //update in db
+ try
+ {
+ updateSessionNode(session);
+ session.didActivate();
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
+ return null;
+ }
+ }
+ else
+ {
+ LOG.debug("getSession ({}): Session has expired", idInCluster);
+ //ensure that the session id for the expired session is deleted so that a new session with the
+ //same id cannot be created (because the idInUse() test would succeed)
+ _jdbcSessionIdMgr.removeSession(idInCluster);
+ session=null;
+ }
+
+ }
+ else
+ {
+ //the session loaded from the db and the one in memory are the same, so keep using the one in memory
+ session = memSession;
+ LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
+ }
+ }
+ else
+ {
+ //No session in db with matching id and context path.
+ LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
+ }
+
+ return session;
+ }
+ }
+
+
+ /**
+ * Get the number of sessions.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions()
+ */
+ @Override
+ public int getSessions()
+ {
+ return _sessions.size();
+ }
+
+
+ /**
+ * Start the session manager.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
+ */
+ @Override
+ public void doStart() throws Exception
+ {
+ if (_sessionIdManager==null)
+ throw new IllegalStateException("No session id manager defined");
+
+ _jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager;
+ _sessionTableSchema = _jdbcSessionIdMgr.getSessionTableSchema();
+
+ _sessions = new ConcurrentHashMap<String, Session>();
+
+ super.doStart();
+ }
+
+
+ /**
+ * Stop the session manager.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop()
+ */
+ @Override
+ public void doStop() throws Exception
+ {
+ super.doStop();
+ _sessions.clear();
+ _sessions = null;
+ }
+
+ @Override
+ protected void shutdownSessions()
+ {
+ //Save the current state of all of our sessions,
+ //do NOT delete them (so other nodes can manage them)
+ long gracefulStopMs = getContextHandler().getServer().getStopTimeout();
+ long stopTime = 0;
+ if (gracefulStopMs > 0)
+ stopTime = System.nanoTime() + (TimeUnit.NANOSECONDS.convert(gracefulStopMs, TimeUnit.MILLISECONDS));
+
+ ArrayList<Session> sessions = (_sessions == null? new ArrayList<Session>() :new ArrayList<Session>(_sessions.values()) );
+
+ // loop while there are sessions, and while there is stop time remaining, or if no stop time, just 1 loop
+ while (sessions.size() > 0 && ((stopTime > 0 && (System.nanoTime() < stopTime)) || (stopTime == 0)))
+ {
+ for (Session session : sessions)
+ {
+ try
+ {
+ session.save(false);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ _sessions.remove(session.getClusterId());
+ }
+
+ //check if we should terminate our loop if we're not using the stop timer
+ if (stopTime == 0)
+ break;
+
+ // Get any sessions that were added by other requests during processing and go around the loop again
+ sessions=new ArrayList<Session>(_sessions.values());
+ }
+ }
+
+
+ /**
+ *
+ * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void renewSessionId (String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+ {
+ Session session = null;
+ try
+ {
+ session = (Session)_sessions.remove(oldClusterId);
+ if (session != null)
+ {
+ synchronized (session)
+ {
+ session.setClusterId(newClusterId); //update ids
+ session.setNodeId(newNodeId);
+ _sessions.put(newClusterId, session); //put it into list in memory
+ updateSession(session); //update database
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+
+ super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+ }
+
+
+
+ /**
+ * Invalidate a session.
+ *
+ * @param idInCluster
+ */
+ protected void invalidateSession (String idInCluster)
+ {
+ Session session = (Session)_sessions.get(idInCluster);
+
+ if (session != null)
+ {
+ session.invalidate();
+ }
+ }
+
+ /**
+ * Delete an existing session, both from the in-memory map and
+ * the database.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
+ */
+ @Override
+ protected boolean removeSession(String idInCluster)
+ {
+ Session session = (Session)_sessions.remove(idInCluster);
+ try
+ {
+ if (session != null)
+ deleteSession(session);
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Problem deleting session id="+idInCluster, e);
+ }
+ return session!=null;
+ }
+
+
+ /**
+ * Add a newly created session to our in-memory list for this node and persist it.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
+ */
+ @Override
+ protected void addSession(AbstractSession session)
+ {
+ if (session==null)
+ return;
+
+ _sessions.put(session.getClusterId(), (Session)session);
+
+ try
+ {
+ synchronized (session)
+ {
+ session.willPassivate();
+ storeSession(((JDBCSessionManager.Session)session));
+ session.didActivate();
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Unable to store new session id="+session.getId() , e);
+ }
+ }
+
+
+ /**
+ * Make a new Session.
+ *
+ * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
+ */
+ @Override
+ protected AbstractSession newSession(HttpServletRequest request)
+ {
+ return new Session(request);
+ }
+
+
+ /**
+ * @param sessionId
+ * @param rowId
+ * @param created
+ * @param accessed
+ * @param maxInterval
+ * @return
+ */
+ protected AbstractSession newSession (String sessionId, String rowId, long created, long accessed, long maxInterval)
+ {
+ return new Session(sessionId, rowId, created, accessed, maxInterval);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Remove session from manager
+ * @param session The session to remove
+ * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+ * {@link SessionIdManager#invalidateAll(String)} should be called.
+ */
+ @Override
+ public boolean removeSession(AbstractSession session, boolean invalidate)
+ {
+ // Remove session from context and global maps
+ boolean removed = super.removeSession(session, invalidate);
+
+ if (removed)
+ {
+ if (!invalidate)
+ {
+ session.willPassivate();
+ }
+ }
+
+ return removed;
+ }
+
+
+ /**
+ * Expire any Sessions we have in memory matching the list of
+ * expired Session ids.
+ *
+ * @param sessionIds
+ */
+ protected Set<String> expire (Set<String> sessionIds)
+ {
+ //don't attempt to scavenge if we are shutting down
+ if (isStopping() || isStopped())
+ return null;
+
+
+ Thread thread=Thread.currentThread();
+ ClassLoader old_loader=thread.getContextClassLoader();
+
+ Set<String> successfullyExpiredIds = new HashSet<String>();
+ try
+ {
+ Iterator<?> itor = sessionIds.iterator();
+ while (itor.hasNext())
+ {
+ String sessionId = (String)itor.next();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Expiring session id "+sessionId);
+
+ Session session = (Session)_sessions.get(sessionId);
+
+ //if session is not in our memory, then fetch from db so we can call the usual listeners on it
+ if (session == null)
+ {
+ if (LOG.isDebugEnabled())LOG.debug("Force loading session id "+sessionId);
+ session = loadSession(sessionId, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+ if (session != null)
+ {
+ //loaded an expired session last managed on this node for this context, add it to the list so we can
+ //treat it like a normal expired session
+ _sessions.put(session.getClusterId(), session);
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Unrecognized session id="+sessionId);
+ continue;
+ }
+ }
+
+ if (session != null)
+ {
+ session.timeout();
+ successfullyExpiredIds.add(session.getClusterId());
+ }
+ }
+ return successfullyExpiredIds;
+ }
+ catch (Throwable t)
+ {
+ LOG.warn("Problem expiring sessions", t);
+ return successfullyExpiredIds;
+ }
+ finally
+ {
+ thread.setContextClassLoader(old_loader);
+ }
+ }
+
+
+ /**
+ * Load a session from the database
+ * @param id
+ * @return the session data that was loaded
+ * @throws Exception
+ */
+ protected Session loadSession (final String id, final String canonicalContextPath, final String vhost)
+ throws Exception
+ {
+ final AtomicReference<Session> _reference = new AtomicReference<Session>();
+ final AtomicReference<Exception> _exception = new AtomicReference<Exception>();
+ Runnable load = new Runnable()
+ {
+ /**
+ * @see java.lang.Runnable#run()
+ */
+ @SuppressWarnings("unchecked")
+ public void run()
+ {
+ try (Connection connection = getConnection();
+ PreparedStatement statement = _sessionTableSchema.getLoadStatement(connection, id, canonicalContextPath, vhost);
+ ResultSet result = statement.executeQuery())
+ {
+ Session session = null;
+ if (result.next())
+ {
+ long maxInterval = result.getLong(_sessionTableSchema.getMaxIntervalColumn());
+ if (maxInterval == JDBCSessionIdManager.MAX_INTERVAL_NOT_SET)
+ {
+ maxInterval = getMaxInactiveInterval(); //if value not saved for maxInactiveInterval, use current value from sessionmanager
+ }
+ session = (Session)newSession(id, result.getString(_sessionTableSchema.getRowIdColumn()),
+ result.getLong(_sessionTableSchema.getCreateTimeColumn()),
+ result.getLong(_sessionTableSchema.getAccessTimeColumn()),
+ maxInterval);
+ session.setCookieSet(result.getLong(_sessionTableSchema.getCookieTimeColumn()));
+ session.setLastAccessedTime(result.getLong(_sessionTableSchema.getLastAccessTimeColumn()));
+ session.setLastNode(result.getString(_sessionTableSchema.getLastNodeColumn()));
+ session.setLastSaved(result.getLong(_sessionTableSchema.getLastSavedTimeColumn()));
+ session.setExpiryTime(result.getLong(_sessionTableSchema.getExpiryTimeColumn()));
+ session.setCanonicalContext(result.getString(_sessionTableSchema.getContextPathColumn()));
+ session.setVirtualHost(result.getString(_sessionTableSchema.getVirtualHostColumn()));
+
+ try (InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, _sessionTableSchema.getMapColumn());
+ ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is))
+ {
+ Object o = ois.readObject();
+ session.addAttributes((Map<String,Object>)o);
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("LOADED session "+session);
+ }
+ else
+ if (LOG.isDebugEnabled())
+ LOG.debug("Failed to load session "+id);
+ _reference.set(session);
+ }
+ catch (Exception e)
+ {
+ _exception.set(e);
+ }
+ }
+ };
+
+ if (_context==null)
+ load.run();
+ else
+ _context.getContextHandler().handle(load);
+
+ if (_exception.get()!=null)
+ {
+ //if the session could not be restored, take its id out of the pool of currently-in-use
+ //session ids
+ _jdbcSessionIdMgr.removeSession(id);
+ throw _exception.get();
+ }
+
+ return _reference.get();
+ }
+
+ /**
+ * Insert a session into the database.
+ *
+ * @param session
+ * @throws Exception
+ */
+ protected void storeSession (Session session)
+ throws Exception
+ {
+ if (session==null)
+ return;
+
+ //put into the database
+ try (Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession))
+ {
+ String rowId = calculateRowId(session);
+
+ long now = System.currentTimeMillis();
+ connection.setAutoCommit(true);
+ statement.setString(1, rowId); //rowId
+ statement.setString(2, session.getClusterId()); //session id
+ statement.setString(3, session.getCanonicalContext()); //context path
+ statement.setString(4, session.getVirtualHost()); //first vhost
+ statement.setString(5, getSessionIdManager().getWorkerName());//my node id
+ statement.setLong(6, session.getAccessed());//accessTime
+ statement.setLong(7, session.getLastAccessedTime()); //lastAccessTime
+ statement.setLong(8, session.getCreationTime()); //time created
+ statement.setLong(9, session.getCookieSet());//time cookie was set
+ statement.setLong(10, now); //last saved time
+ statement.setLong(11, session.getExpiryTime());
+ statement.setLong(12, session.getMaxInactiveInterval());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(session.getAttributeMap());
+ oos.flush();
+ byte[] bytes = baos.toByteArray();
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+ statement.setBinaryStream(13, bais, bytes.length);//attribute map as blob
+
+
+ statement.executeUpdate();
+ session.setRowId(rowId); //set it on the in-memory data as well as in db
+ session.setLastSaved(now);
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Stored session "+session);
+ }
+
+
+ /**
+ * Update data on an existing persisted session.
+ *
+ * @param data the session
+ * @throws Exception
+ */
+ protected void updateSession (Session data)
+ throws Exception
+ {
+ if (data==null)
+ return;
+
+ try (Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession))
+ {
+ long now = System.currentTimeMillis();
+ connection.setAutoCommit(true);
+ statement.setString(1, data.getClusterId());
+ statement.setString(2, getSessionIdManager().getWorkerName());//my node id
+ statement.setLong(3, data.getAccessed());//accessTime
+ statement.setLong(4, data.getLastAccessedTime()); //lastAccessTime
+ statement.setLong(5, now); //last saved time
+ statement.setLong(6, data.getExpiryTime());
+ statement.setLong(7, data.getMaxInactiveInterval());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(data.getAttributeMap());
+ oos.flush();
+ byte[] bytes = baos.toByteArray();
+ ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+
+ statement.setBinaryStream(8, bais, bytes.length);//attribute map as blob
+ statement.setString(9, data.getRowId()); //rowId
+ statement.executeUpdate();
+
+ data.setLastSaved(now);
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Updated session "+data);
+ }
+
+
+ /**
+ * Update the node on which the session was last seen to be my node.
+ *
+ * @param data the session
+ * @throws Exception
+ */
+ protected void updateSessionNode (Session data)
+ throws Exception
+ {
+ String nodeId = getSessionIdManager().getWorkerName();
+ try (Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode))
+ {
+ connection.setAutoCommit(true);
+ statement.setString(1, nodeId);
+ statement.setString(2, data.getRowId());
+ statement.executeUpdate();
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
+ }
+
+ /**
+ * Persist the time the session was last accessed.
+ *
+ * @param data the session
+ * @throws Exception
+ */
+ private void updateSessionAccessTime (Session data)
+ throws Exception
+ {
+ try (Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime))
+ {
+ long now = System.currentTimeMillis();
+ connection.setAutoCommit(true);
+ statement.setString(1, getSessionIdManager().getWorkerName());
+ statement.setLong(2, data.getAccessed());
+ statement.setLong(3, data.getLastAccessedTime());
+ statement.setLong(4, now);
+ statement.setLong(5, data.getExpiryTime());
+ statement.setLong(6, data.getMaxInactiveInterval());
+ statement.setString(7, data.getRowId());
+
+ statement.executeUpdate();
+ data.setLastSaved(now);
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Updated access time session id="+data.getId()+" with lastsaved="+data.getLastSaved());
+ }
+
+
+
+
+ /**
+ * Delete a session from the database. Should only be called
+ * when the session has been invalidated.
+ *
+ * @param data
+ * @throws Exception
+ */
+ protected void deleteSession (Session data)
+ throws Exception
+ {
+ try (Connection connection = getConnection();
+ PreparedStatement statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession))
+ {
+ connection.setAutoCommit(true);
+ statement.setString(1, data.getRowId());
+ statement.executeUpdate();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Deleted Session "+data);
+ }
+ }
+
+
+
+ /**
+ * Get a connection from the driver.
+ * @return
+ * @throws SQLException
+ */
+ private Connection getConnection ()
+ throws SQLException
+ {
+ return ((JDBCSessionIdManager)getSessionIdManager()).getConnection();
+ }
+
+ /**
+ * Calculate a unique id for this session across the cluster.
+ *
+ * Unique id is composed of: contextpath_virtualhost0_sessionid
+ * @param data
+ * @return
+ */
+ private String calculateRowId (Session data)
+ {
+ String rowId = canonicalize(_context.getContextPath());
+ rowId = rowId + "_" + getVirtualHost(_context);
+ rowId = rowId+"_"+data.getId();
+ return rowId;
+ }
+
+ /**
+ * Get the first virtual host for the context.
+ *
+ * Used to help identify the exact session/contextPath.
+ *
+ * @return 0.0.0.0 if no virtual host is defined
+ */
+ private static String getVirtualHost (ContextHandler.Context context)
+ {
+ String vhost = "0.0.0.0";
+
+ if (context==null)
+ return vhost;
+
+ String [] vhosts = context.getContextHandler().getVirtualHosts();
+ if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
+ return vhost;
+
+ return vhosts[0];
+ }
+
+ /**
+ * Make an acceptable file name from a context path.
+ *
+ * @param path
+ * @return
+ */
+ private static String canonicalize (String path)
+ {
+ if (path==null)
+ return "";
+
+ return path.replace('/', '_').replace('.','_').replace('\\','_');
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * MemSession
+ *
+ * A session whose data is kept in memory
+ */
+public class MemSession extends AbstractSession
+{
+
+ private final Map<String,Object> _attributes=new HashMap<String, Object>();
+
+ protected MemSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
+ {
+ super(abstractSessionManager, request);
+ }
+
+ public MemSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId)
+ {
+ super(abstractSessionManager, created, accessed, clusterId);
+ }
+
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public Map<String,Object> getAttributeMap()
+ {
+ return _attributes;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int getAttributes()
+ {
+ synchronized (this)
+ {
+ checkValid();
+ return _attributes.size();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @SuppressWarnings({ "unchecked" })
+ @Override
+ public Enumeration<String> doGetAttributeNames()
+ {
+ List<String> names=_attributes==null?Collections.EMPTY_LIST:new ArrayList<String>(_attributes.keySet());
+ return Collections.enumeration(names);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Set<String> getNames()
+ {
+ synchronized (this)
+ {
+ return new HashSet<String>(_attributes.keySet());
+ }
+ }
+
+
+ /* ------------------------------------------------------------- */
+ @Override
+ public void clearAttributes()
+ {
+ while (_attributes!=null && _attributes.size()>0)
+ {
+ ArrayList<String> keys;
+ synchronized(this)
+ {
+ keys=new ArrayList<String>(_attributes.keySet());
+ }
+
+ Iterator<String> iter=keys.iterator();
+ while (iter.hasNext())
+ {
+ String key=(String)iter.next();
+
+ Object value;
+ synchronized(this)
+ {
+ value=doPutOrRemove(key,null);
+ }
+ unbindValue(key,value);
+
+ ((AbstractSessionManager)getSessionManager()).doSessionAttributeListeners(this,key,value,null);
+ }
+ }
+ if (_attributes!=null)
+ _attributes.clear();
+ }
+
+ /* ------------------------------------------------------------ */
+ public void addAttributes(Map<String,Object> map)
+ {
+ _attributes.putAll(map);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object doPutOrRemove(String name, Object value)
+ {
+ return value==null?_attributes.remove(name):_attributes.put(name,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object doGet(String name)
+ {
+ return _attributes.get(name);
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.server.session;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.EventListener;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ScopedHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * SessionHandler.
+ */
+public class SessionHandler extends ScopedHandler
+{
+ final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
+
+ public final static EnumSet<SessionTrackingMode> DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL);
+
+ public static final Class[] SESSION_LISTENER_TYPES = new Class[] {HttpSessionAttributeListener.class,
+ HttpSessionIdListener.class,
+ HttpSessionListener.class};
+
+
+
+ /* -------------------------------------------------------------- */
+ private SessionManager _sessionManager;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Constructor. Construct a SessionHandler witha a HashSessionManager with a standard java.util.Random generator is created.
+ */
+ public SessionHandler()
+ {
+ this(new HashSessionManager());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param manager
+ * The session manager
+ */
+ public SessionHandler(SessionManager manager)
+ {
+ setSessionManager(manager);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionManager.
+ */
+ public SessionManager getSessionManager()
+ {
+ return _sessionManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionManager
+ * The sessionManager to set.
+ */
+ public void setSessionManager(SessionManager sessionManager)
+ {
+ if (isStarted())
+ throw new IllegalStateException();
+ if (sessionManager != null)
+ sessionManager.setSessionHandler(this);
+ updateBean(_sessionManager,sessionManager);
+ _sessionManager=sessionManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (_sessionManager==null)
+ setSessionManager(new HashSessionManager());
+ super.doStart();
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ // Destroy sessions before destroying servlets/filters see JETTY-1266
+ super.doStop();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ SessionManager old_session_manager = null;
+ HttpSession old_session = null;
+ HttpSession access = null;
+ try
+ {
+ old_session_manager = baseRequest.getSessionManager();
+ old_session = baseRequest.getSession(false);
+
+ if (old_session_manager != _sessionManager)
+ {
+ // new session context
+ baseRequest.setSessionManager(_sessionManager);
+ baseRequest.setSession(null);
+ checkRequestedSessionId(baseRequest,request);
+ }
+
+ // access any existing session
+ HttpSession session = null;
+ if (_sessionManager != null)
+ {
+ session = baseRequest.getSession(false);
+ if (session != null)
+ {
+ if (session != old_session)
+ {
+ access = session;
+ HttpCookie cookie = _sessionManager.access(session,request.isSecure());
+ if (cookie != null) // Handle changed ID or max-age refresh
+ baseRequest.getResponse().addCookie(cookie);
+ }
+ }
+ else
+ {
+ session = baseRequest.recoverNewSession(_sessionManager);
+ if (session != null)
+ baseRequest.setSession(session);
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("sessionManager=" + _sessionManager);
+ LOG.debug("session=" + session);
+ }
+
+ // start manual inline of nextScope(target,baseRequest,request,response);
+ if (_nextScope != null)
+ _nextScope.doScope(target,baseRequest,request,response);
+ else if (_outerScope != null)
+ _outerScope.doHandle(target,baseRequest,request,response);
+ else
+ doHandle(target,baseRequest,request,response);
+ // end manual inline (pathentic attempt to reduce stack depth)
+
+ }
+ finally
+ {
+ if (access != null)
+ _sessionManager.complete(access);
+
+ HttpSession session = baseRequest.getSession(false);
+ if (session != null && old_session == null && session != access)
+ _sessionManager.complete(session);
+
+ if (old_session_manager != null && old_session_manager != _sessionManager)
+ {
+ baseRequest.setSessionManager(old_session_manager);
+ baseRequest.setSession(old_session);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ // start manual inline of nextHandle(target,baseRequest,request,response);
+ if (never())
+ nextHandle(target,baseRequest,request,response);
+ else if (_nextScope != null && _nextScope == _handler)
+ _nextScope.doHandle(target,baseRequest,request,response);
+ else if (_handler != null)
+ _handler.handle(target,baseRequest,request,response);
+ // end manual inline
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Look for a requested session ID in cookies and URI parameters
+ *
+ * @param baseRequest
+ * @param request
+ */
+ protected void checkRequestedSessionId(Request baseRequest, HttpServletRequest request)
+ {
+ String requested_session_id = request.getRequestedSessionId();
+
+ SessionManager sessionManager = getSessionManager();
+
+ if (requested_session_id != null && sessionManager != null)
+ {
+ HttpSession session = sessionManager.getHttpSession(requested_session_id);
+ if (session != null && sessionManager.isValid(session))
+ baseRequest.setSession(session);
+ return;
+ }
+ else if (!DispatcherType.REQUEST.equals(baseRequest.getDispatcherType()))
+ return;
+
+ boolean requested_session_id_from_cookie = false;
+ HttpSession session = null;
+
+ // Look for session id cookie
+ if (_sessionManager.isUsingCookies())
+ {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null && cookies.length > 0)
+ {
+ final String sessionCookie=sessionManager.getSessionCookieConfig().getName();
+ for (int i = 0; i < cookies.length; i++)
+ {
+ if (sessionCookie.equalsIgnoreCase(cookies[i].getName()))
+ {
+ requested_session_id = cookies[i].getValue();
+ requested_session_id_from_cookie = true;
+
+ LOG.debug("Got Session ID {} from cookie",requested_session_id);
+
+ if (requested_session_id != null)
+ {
+ session = sessionManager.getHttpSession(requested_session_id);
+
+ if (session != null && sessionManager.isValid(session))
+ {
+ break;
+ }
+ }
+ else
+ {
+ LOG.warn("null session id from cookie");
+ }
+ }
+ }
+ }
+ }
+
+ if (requested_session_id == null || session == null)
+ {
+ String uri = request.getRequestURI();
+
+ String prefix = sessionManager.getSessionIdPathParameterNamePrefix();
+ if (prefix != null)
+ {
+ int s = uri.indexOf(prefix);
+ if (s >= 0)
+ {
+ s += prefix.length();
+ int i = s;
+ while (i < uri.length())
+ {
+ char c = uri.charAt(i);
+ if (c == ';' || c == '#' || c == '?' || c == '/')
+ break;
+ i++;
+ }
+
+ requested_session_id = uri.substring(s,i);
+ requested_session_id_from_cookie = false;
+ session = sessionManager.getHttpSession(requested_session_id);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Got Session ID {} from URL",requested_session_id);
+ }
+ }
+ }
+
+ baseRequest.setRequestedSessionId(requested_session_id);
+ baseRequest.setRequestedSessionIdFromCookie(requested_session_id!=null && requested_session_id_from_cookie);
+ if (session != null && sessionManager.isValid(session))
+ baseRequest.setSession(session);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param listener
+ */
+ public void addEventListener(EventListener listener)
+ {
+ if (_sessionManager != null)
+ _sessionManager.addEventListener(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param listener
+ */
+ public void removeEventListener(EventListener listener)
+ {
+ if (_sessionManager != null)
+ _sessionManager.removeEventListener(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void clearEventListeners()
+ {
+ if (_sessionManager != null)
+ _sessionManager.clearEventListeners();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Server : Session Management Implementations
+ */
+package org.eclipse.jetty.server.session;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.UnavailableException;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * AbstractHolder
+ *
+ * Base class for all servlet-related classes that may be lazily instantiated (eg servlet, filter,
+ * listener), and/or require metadata to be held regarding their origin
+ * (web.xml, annotation, programmatic api etc).
+ *
+ */
+public abstract class BaseHolder<T> extends AbstractLifeCycle implements Dumpable
+{
+ private static final Logger LOG = Log.getLogger(BaseHolder.class);
+
+
+ public enum Source { EMBEDDED, JAVAX_API, DESCRIPTOR, ANNOTATION };
+
+ final protected Source _source;
+ protected transient Class<? extends T> _class;
+ protected String _className;
+ protected boolean _extInstance;
+ protected ServletHandler _servletHandler;
+
+ /* ---------------------------------------------------------------- */
+ protected BaseHolder(Source source)
+ {
+ _source=source;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Source getSource()
+ {
+ return _source;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Do any setup necessary after starting
+ * @throws Exception
+ */
+ public void initialize()
+ throws Exception
+ {
+ if (!isStarted())
+ throw new IllegalStateException("Not started: "+this);
+ }
+
+ /* ------------------------------------------------------------ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void doStart()
+ throws Exception
+ {
+ //if no class already loaded and no classname, make permanently unavailable
+ if (_class==null && (_className==null || _className.equals("")))
+ throw new UnavailableException("No class in holder");
+
+ //try to load class
+ if (_class==null)
+ {
+ try
+ {
+ _class=Loader.loadClass(Holder.class, _className);
+ if(LOG.isDebugEnabled())
+ LOG.debug("Holding {} from {}",_class,_class.getClassLoader());
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ throw new UnavailableException(e.getMessage());
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doStop()
+ throws Exception
+ {
+ if (!_extInstance)
+ _class=null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute(value="Class Name", readonly=true)
+ public String getClassName()
+ {
+ return _className;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Class<? extends T> getHeldClass()
+ {
+ return _class;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the servletHandler.
+ */
+ public ServletHandler getServletHandler()
+ {
+ return _servletHandler;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletHandler The {@link ServletHandler} that will handle requests dispatched to this servlet.
+ */
+ public void setServletHandler(ServletHandler servletHandler)
+ {
+ _servletHandler = servletHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param className The className to set.
+ */
+ public void setClassName(String className)
+ {
+ _className = className;
+ _class=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param held The class to hold
+ */
+ public void setHeldClass(Class<? extends T> held)
+ {
+ _class=held;
+ if (held!=null)
+ {
+ _className=held.getName();
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ protected void illegalStateIfContextStarted()
+ {
+ if (_servletHandler!=null)
+ {
+ ServletContext context=_servletHandler.getServletContext();
+ if ((context instanceof ContextHandler.Context) && ((ContextHandler.Context)context).getContextHandler().isStarted())
+ throw new IllegalStateException("Started");
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if this holder was created for a specific instance.
+ */
+ public boolean isInstance()
+ {
+ return _extInstance;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ out.append(toString())
+ .append(" - ").append(AbstractLifeCycle.getState(this)).append("\n");
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator.CachedHttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.PathMap.MappedEntry;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.InclusiveByteRange;
+import org.eclipse.jetty.server.ResourceCache;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiPartOutputStream;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+
+/* ------------------------------------------------------------ */
+/** The default servlet.
+ * This servlet, normally mapped to /, provides the handling for static
+ * content, OPTION and TRACE methods for the context.
+ * The following initParameters are supported, these can be set either
+ * on the servlet itself or as ServletContext initParameters with a prefix
+ * of org.eclipse.jetty.servlet.Default. :
+ * <PRE>
+ * acceptRanges If true, range requests and responses are
+ * supported
+ *
+ * dirAllowed If true, directory listings are returned if no
+ * welcome file is found. Else 403 Forbidden.
+ *
+ * welcomeServlets If true, attempt to dispatch to welcome files
+ * that are servlets, but only after no matching static
+ * resources could be found. If false, then a welcome
+ * file must exist on disk. If "exact", then exact
+ * servlet matches are supported without an existing file.
+ * Default is true.
+ *
+ * This must be false if you want directory listings,
+ * but have index.jsp in your welcome file list.
+ *
+ * redirectWelcome If true, welcome files are redirected rather than
+ * forwarded to.
+ *
+ * gzip If set to true, then static content will be served as
+ * gzip content encoded if a matching resource is
+ * found ending with ".gz"
+ *
+ * resourceBase Set to replace the context resource base
+ *
+ * resourceCache If set, this is a context attribute name, which the servlet
+ * will use to look for a shared ResourceCache instance.
+ *
+ * relativeResourceBase
+ * Set with a pathname relative to the base of the
+ * servlet context root. Useful for only serving static content out
+ * of only specific subdirectories.
+ *
+ * pathInfoOnly If true, only the path info will be applied to the resourceBase
+ *
+ * stylesheet Set with the location of an optional stylesheet that will be used
+ * to decorate the directory listing html.
+ *
+ * etags If True, weak etags will be generated and handled.
+ *
+ * maxCacheSize The maximum total size of the cache or 0 for no cache.
+ * maxCachedFileSize The maximum size of a file to cache
+ * maxCachedFiles The maximum number of files to cache
+ *
+ * useFileMappedBuffer
+ * If set to true, it will use mapped file buffer to serve static content
+ * when using NIO connector. Setting this value to false means that
+ * a direct buffer will be used instead of a mapped file buffer.
+ * By default, this is set to true.
+ *
+ * cacheControl If set, all static content will have this value set as the cache-control
+ * header.
+ *
+ *
+ * </PRE>
+ *
+ *
+ *
+ *
+ */
+public class DefaultServlet extends HttpServlet implements ResourceFactory
+{
+ private static final Logger LOG = Log.getLogger(DefaultServlet.class);
+
+ private static final long serialVersionUID = 4930458713846881193L;
+
+ private static final CachedHttpField ACCEPT_RANGES = new CachedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
+
+ private ServletContext _servletContext;
+ private ContextHandler _contextHandler;
+
+ private boolean _acceptRanges=true;
+ private boolean _dirAllowed=true;
+ private boolean _welcomeServlets=false;
+ private boolean _welcomeExactServlets=false;
+ private boolean _redirectWelcome=false;
+ private boolean _gzip=false;
+ private boolean _pathInfoOnly=false;
+ private boolean _etags=false;
+
+ private Resource _resourceBase;
+ private ResourceCache _cache;
+
+ private MimeTypes _mimeTypes;
+ private String[] _welcomes;
+ private Resource _stylesheet;
+ private boolean _useFileMappedBuffer=false;
+ private HttpField _cacheControl;
+ private String _relativeResourceBase;
+ private ServletHandler _servletHandler;
+ private ServletHolder _defaultHolder;
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void init()
+ throws UnavailableException
+ {
+ _servletContext=getServletContext();
+ _contextHandler = initContextHandler(_servletContext);
+
+ _mimeTypes = _contextHandler.getMimeTypes();
+
+ _welcomes = _contextHandler.getWelcomeFiles();
+ if (_welcomes==null)
+ _welcomes=new String[] {"index.html","index.jsp"};
+
+ _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
+ _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
+ _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
+ _gzip=getInitBoolean("gzip",_gzip);
+ _pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly);
+
+ if ("exact".equals(getInitParameter("welcomeServlets")))
+ {
+ _welcomeExactServlets=true;
+ _welcomeServlets=false;
+ }
+ else
+ _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
+
+ _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
+
+ _relativeResourceBase = getInitParameter("relativeResourceBase");
+
+ String rb=getInitParameter("resourceBase");
+ if (rb!=null)
+ {
+ if (_relativeResourceBase!=null)
+ throw new UnavailableException("resourceBase & relativeResourceBase");
+ try{_resourceBase=_contextHandler.newResource(rb);}
+ catch (Exception e)
+ {
+ LOG.warn(Log.EXCEPTION,e);
+ throw new UnavailableException(e.toString());
+ }
+ }
+
+ String css=getInitParameter("stylesheet");
+ try
+ {
+ if(css!=null)
+ {
+ _stylesheet = Resource.newResource(css);
+ if(!_stylesheet.exists())
+ {
+ LOG.warn("!" + css);
+ _stylesheet = null;
+ }
+ }
+ if(_stylesheet == null)
+ {
+ _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+
+ String cc=getInitParameter("cacheControl");
+ if (cc!=null)
+ _cacheControl=new CachedHttpField(HttpHeader.CACHE_CONTROL, cc);
+
+ String resourceCache = getInitParameter("resourceCache");
+ int max_cache_size=getInitInt("maxCacheSize", -2);
+ int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
+ int max_cached_files=getInitInt("maxCachedFiles", -2);
+ if (resourceCache!=null)
+ {
+ if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
+ LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
+ if (_relativeResourceBase!=null || _resourceBase!=null)
+ throw new UnavailableException("resourceCache specified with resource bases");
+ _cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
+
+ LOG.debug("Cache {}={}",resourceCache,_cache);
+ }
+
+ _etags = getInitBoolean("etags",_etags);
+
+ try
+ {
+ if (_cache==null && max_cached_files>0)
+ {
+ _cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags);
+
+ if (max_cache_size>0)
+ _cache.setMaxCacheSize(max_cache_size);
+ if (max_cached_file_size>=-1)
+ _cache.setMaxCachedFileSize(max_cached_file_size);
+ if (max_cached_files>=-1)
+ _cache.setMaxCachedFiles(max_cached_files);
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn(Log.EXCEPTION,e);
+ throw new UnavailableException(e.toString());
+ }
+
+ _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
+ for (ServletHolder h :_servletHandler.getServlets())
+ if (h.getServletInstance()==this)
+ _defaultHolder=h;
+
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("resource base = "+_resourceBase);
+ }
+
+ /**
+ * Compute the field _contextHandler.<br/>
+ * In the case where the DefaultServlet is deployed on the HttpService it is likely that
+ * this method needs to be overwritten to unwrap the ServletContext facade until we reach
+ * the original jetty's ContextHandler.
+ * @param servletContext The servletContext of this servlet.
+ * @return the jetty's ContextHandler for this servletContext.
+ */
+ protected ContextHandler initContextHandler(ServletContext servletContext)
+ {
+ ContextHandler.Context scontext=ContextHandler.getCurrentContext();
+ if (scontext==null)
+ {
+ if (servletContext instanceof ContextHandler.Context)
+ return ((ContextHandler.Context)servletContext).getContextHandler();
+ else
+ throw new IllegalArgumentException("The servletContext " + servletContext + " " +
+ servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
+ }
+ else
+ return ContextHandler.getCurrentContext().getContextHandler();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getInitParameter(String name)
+ {
+ String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
+ if (value==null)
+ value=super.getInitParameter(name);
+ return value;
+ }
+
+ /* ------------------------------------------------------------ */
+ private boolean getInitBoolean(String name, boolean dft)
+ {
+ String value=getInitParameter(name);
+ if (value==null || value.length()==0)
+ return dft;
+ return (value.startsWith("t")||
+ value.startsWith("T")||
+ value.startsWith("y")||
+ value.startsWith("Y")||
+ value.startsWith("1"));
+ }
+
+ /* ------------------------------------------------------------ */
+ private int getInitInt(String name, int dft)
+ {
+ String value=getInitParameter(name);
+ if (value==null)
+ value=getInitParameter(name);
+ if (value!=null && value.length()>0)
+ return Integer.parseInt(value);
+ return dft;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** get Resource to serve.
+ * Map a path to a resource. The default implementation calls
+ * HttpContext.getResource but derived servlets may provide
+ * their own mapping.
+ * @param pathInContext The path to find a resource for.
+ * @return The resource to serve.
+ */
+ @Override
+ public Resource getResource(String pathInContext)
+ {
+ Resource r=null;
+ if (_relativeResourceBase!=null)
+ pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
+
+ try
+ {
+ if (_resourceBase!=null)
+ {
+ r = _resourceBase.addPath(pathInContext);
+ if (!_contextHandler.checkAlias(pathInContext,r))
+ r=null;
+ }
+ else if (_servletContext instanceof ContextHandler.Context)
+ {
+ r = _contextHandler.getResource(pathInContext);
+ }
+ else
+ {
+ URL u = _servletContext.getResource(pathInContext);
+ r = _contextHandler.newResource(u);
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Resource "+pathInContext+"="+r);
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+
+ if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
+ r=_stylesheet;
+
+ return r;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ String servletPath=null;
+ String pathInfo=null;
+ Enumeration<String> reqRanges = null;
+ Boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
+ if (included!=null && included.booleanValue())
+ {
+ servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+ if (servletPath==null)
+ {
+ servletPath=request.getServletPath();
+ pathInfo=request.getPathInfo();
+ }
+ }
+ else
+ {
+ included = Boolean.FALSE;
+ servletPath = _pathInfoOnly?"/":request.getServletPath();
+ pathInfo = request.getPathInfo();
+
+ // Is this a Range request?
+ reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
+ if (!hasDefinedRange(reqRanges))
+ reqRanges = null;
+ }
+
+ String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+ boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
+
+
+ // Find the resource and content
+ Resource resource=null;
+ HttpContent content=null;
+ try
+ {
+ // is gzip enabled?
+ String pathInContextGz=null;
+ boolean gzip=false;
+ if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
+ {
+ // Look for a gzip resource
+ pathInContextGz=pathInContext+".gz";
+ if (_cache==null)
+ resource=getResource(pathInContextGz);
+ else
+ {
+ content=_cache.lookup(pathInContextGz);
+ resource=(content==null)?null:content.getResource();
+ }
+
+ // Does a gzip resource exist?
+ if (resource!=null && resource.exists() && !resource.isDirectory())
+ {
+ // Tell caches that response may vary by accept-encoding
+ response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
+
+ // Does the client accept gzip?
+ String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
+ if (accept!=null && accept.indexOf("gzip")>=0)
+ gzip=true;
+ }
+ }
+
+ // find resource
+ if (!gzip)
+ {
+ if (_cache==null)
+ resource=getResource(pathInContext);
+ else
+ {
+ content=_cache.lookup(pathInContext);
+ resource=content==null?null:content.getResource();
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("uri="+request.getRequestURI()+" resource="+resource+(content!=null?" content":""));
+
+ // Handle resource
+ if (resource==null || !resource.exists())
+ {
+ if (included)
+ throw new FileNotFoundException("!" + pathInContext);
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ else if (!resource.isDirectory())
+ {
+ if (endsWithSlash && pathInContext.length()>1)
+ {
+ String q=request.getQueryString();
+ pathInContext=pathInContext.substring(0,pathInContext.length()-1);
+ if (q!=null&&q.length()!=0)
+ pathInContext+="?"+q;
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
+ }
+ else
+ {
+ // ensure we have content
+ if (content==null)
+ content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),response.getBufferSize(),_etags);
+
+ if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+ {
+ if (gzip)
+ {
+ response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
+ String mt=_servletContext.getMimeType(pathInContext);
+ if (mt!=null)
+ response.setContentType(mt);
+ }
+ sendData(request,response,included.booleanValue(),resource,content,reqRanges);
+ }
+ }
+ }
+ else
+ {
+ String welcome=null;
+
+ if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
+ {
+ StringBuffer buf=request.getRequestURL();
+ synchronized(buf)
+ {
+ int param=buf.lastIndexOf(";");
+ if (param<0)
+ buf.append('/');
+ else
+ buf.insert(param,'/');
+ String q=request.getQueryString();
+ if (q!=null&&q.length()!=0)
+ {
+ buf.append('?');
+ buf.append(q);
+ }
+ response.setContentLength(0);
+ response.sendRedirect(response.encodeRedirectURL(buf.toString()));
+ }
+ }
+ // else look for a welcome file
+ else if (null!=(welcome=getWelcomeFile(pathInContext)))
+ {
+ LOG.debug("welcome={}",welcome);
+ if (_redirectWelcome)
+ {
+ // Redirect to the index
+ response.setContentLength(0);
+ String q=request.getQueryString();
+ if (q!=null&&q.length()!=0)
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
+ else
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
+ }
+ else
+ {
+ // Forward to the index
+ RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
+ if (dispatcher!=null)
+ {
+ if (included.booleanValue())
+ dispatcher.include(request,response);
+ else
+ {
+ request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
+ dispatcher.forward(request,response);
+ }
+ }
+ }
+ }
+ else
+ {
+ content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_etags);
+ if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+ sendDirectory(request,response,resource,pathInContext);
+ }
+ }
+ }
+ catch(IllegalArgumentException e)
+ {
+ LOG.warn(Log.EXCEPTION,e);
+ if(!response.isCommitted())
+ response.sendError(500, e.getMessage());
+ }
+ finally
+ {
+ if (content!=null)
+ content.release();
+ else if (resource!=null)
+ resource.close();
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ private boolean hasDefinedRange(Enumeration<String> reqRanges)
+ {
+ return (reqRanges!=null && reqRanges.hasMoreElements());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ doGet(request,response);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* (non-Javadoc)
+ * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ @Override
+ protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException
+ {
+ resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of
+ * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>.
+ * If the resource is not a directory, or no matching file is found, then it may look for a valid servlet mapping.
+ * If there is none, then <code>null</code> is returned.
+ * The list of welcome files is read from the {@link ContextHandler} for this servlet, or
+ * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>.
+ * @param resource
+ * @return The path of the matching welcome file in context or null.
+ * @throws IOException
+ * @throws MalformedURLException
+ */
+ private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
+ {
+ if (_welcomes==null)
+ return null;
+
+ String welcome_servlet=null;
+ for (int i=0;i<_welcomes.length;i++)
+ {
+ String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
+ Resource welcome=getResource(welcome_in_context);
+ if (welcome!=null && welcome.exists())
+ return _welcomes[i];
+
+ if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
+ {
+ MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
+ if (entry!=null && entry.getValue()!=_defaultHolder &&
+ (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
+ welcome_servlet=welcome_in_context;
+
+ }
+ }
+ return welcome_servlet;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Check modification date headers.
+ */
+ protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
+ throws IOException
+ {
+ try
+ {
+ if (!HttpMethod.HEAD.is(request.getMethod()))
+ {
+ if (_etags)
+ {
+ String ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
+ if (ifm!=null)
+ {
+ boolean match=false;
+ if (content.getETag()!=null)
+ {
+ QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
+ while (!match && quoted.hasMoreTokens())
+ {
+ String tag = quoted.nextToken();
+ if (content.getETag().equals(tag))
+ match=true;
+ }
+ }
+
+ if (!match)
+ {
+ response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return false;
+ }
+ }
+
+ String if_non_match_etag=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+ if (if_non_match_etag!=null && content.getETag()!=null)
+ {
+ // Look for GzipFiltered version of etag
+ if (content.getETag().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ response.setHeader(HttpHeader.ETAG.asString(),if_non_match_etag);
+ return false;
+ }
+
+ // Handle special case of exact match.
+ if (content.getETag().equals(if_non_match_etag))
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+ return false;
+ }
+
+ // Handle list of tags
+ QuotedStringTokenizer quoted = new QuotedStringTokenizer(if_non_match_etag,", ",false,true);
+ while (quoted.hasMoreTokens())
+ {
+ String tag = quoted.nextToken();
+ if (content.getETag().equals(tag))
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+ return false;
+ }
+ }
+
+ // If etag requires content to be served, then do not check if-modified-since
+ return true;
+ }
+ }
+
+ // Handle if modified since
+ String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+ if (ifms!=null)
+ {
+ //Get jetty's Response impl
+ String mdlm=content.getLastModified();
+ if (mdlm!=null && ifms.equals(mdlm))
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ if (_etags)
+ response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+ response.flushBuffer();
+ return false;
+ }
+
+ long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+ if (ifmsl!=-1 && resource.lastModified()/1000 <= ifmsl/1000)
+ {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ if (_etags)
+ response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+ response.flushBuffer();
+ return false;
+ }
+ }
+
+ // Parse the if[un]modified dates and compare to resource
+ long date=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
+ if (date!=-1 && resource.lastModified()/1000 > date/1000)
+ {
+ response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+ return false;
+ }
+
+ }
+ }
+ catch(IllegalArgumentException iae)
+ {
+ if(!response.isCommitted())
+ response.sendError(400, iae.getMessage());
+ throw iae;
+ }
+ return true;
+ }
+
+
+ /* ------------------------------------------------------------------- */
+ protected void sendDirectory(HttpServletRequest request,
+ HttpServletResponse response,
+ Resource resource,
+ String pathInContext)
+ throws IOException
+ {
+ if (!_dirAllowed)
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ byte[] data=null;
+ String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
+
+ //If the DefaultServlet has a resource base set, use it
+ if (_resourceBase != null)
+ {
+ // handle ResourceCollection
+ if (_resourceBase instanceof ResourceCollection)
+ resource=_resourceBase.addPath(pathInContext);
+ }
+ //Otherwise, try using the resource base of its enclosing context handler
+ else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
+ resource=_contextHandler.getBaseResource().addPath(pathInContext);
+
+ String dir = resource.getListHTML(base,pathInContext.length()>1);
+ if (dir==null)
+ {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN,
+ "No directory");
+ return;
+ }
+
+ data=dir.getBytes("UTF-8");
+ response.setContentType("text/html; charset=UTF-8");
+ response.setContentLength(data.length);
+ response.getOutputStream().write(data);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void sendData(HttpServletRequest request,
+ HttpServletResponse response,
+ boolean include,
+ Resource resource,
+ HttpContent content,
+ Enumeration<String> reqRanges)
+ throws IOException
+ {
+ final long content_length = (content==null)?resource.length():content.getContentLength();
+
+ // Get the output stream (or writer)
+ OutputStream out =null;
+ boolean written;
+ try
+ {
+ out = response.getOutputStream();
+
+ // has a filter already written to the response?
+ written = out instanceof HttpOutput
+ ? ((HttpOutput)out).isWritten()
+ : true;
+ }
+ catch(IllegalStateException e)
+ {
+ out = new WriterOutputStream(response.getWriter());
+ written=true; // there may be data in writer buffer, so assume written
+ }
+
+ if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
+ {
+ // if there were no ranges, send entire entity
+ if (include)
+ {
+ resource.writeTo(out,0,content_length);
+ }
+ // else if we can't do a bypass write because of wrapping
+ else if (content==null || written || !(out instanceof HttpOutput))
+ {
+ // write normally
+ writeHeaders(response,content,written?-1:content_length);
+ ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer();
+ if (buffer!=null)
+ BufferUtil.writeTo(buffer,out);
+ else
+ resource.writeTo(out,0,content_length);
+ }
+ // else do a bypass write
+ else
+ {
+ // write the headers
+ if (response instanceof Response)
+ {
+ Response r = (Response)response;
+ writeOptionHeaders(r.getHttpFields());
+ r.setHeaders(content);
+ }
+ else
+ writeHeaders(response,content,content_length);
+
+ // write the content asynchronously if supported
+ if (request.isAsyncSupported())
+ {
+ final AsyncContext context = request.startAsync();
+
+ ((HttpOutput)out).sendContent(content,new Callback()
+ {
+ @Override
+ public void succeeded()
+ {
+ context.complete();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ if (x instanceof IOException)
+ LOG.debug(x);
+ else
+ LOG.warn(x);
+ context.complete();
+ }
+ });
+ }
+ // otherwise write content blocking
+ else
+ {
+ ((HttpOutput)out).sendContent(content);
+ }
+ }
+ }
+ else
+ {
+ // Parse the satisfiable ranges
+ List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
+
+ // if there are no satisfiable ranges, send 416 response
+ if (ranges==null || ranges.size()==0)
+ {
+ writeHeaders(response, content, content_length);
+ response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+ response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+ InclusiveByteRange.to416HeaderRangeString(content_length));
+ resource.writeTo(out,0,content_length);
+ return;
+ }
+
+ // if there is only a single valid range (must be satisfiable
+ // since were here now), send that range with a 216 response
+ if ( ranges.size()== 1)
+ {
+ InclusiveByteRange singleSatisfiableRange = ranges.get(0);
+ long singleLength = singleSatisfiableRange.getSize(content_length);
+ writeHeaders(response,content,singleLength );
+ response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ if (!response.containsHeader(HttpHeader.DATE.asString()))
+ response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+ response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+ singleSatisfiableRange.toHeaderRangeString(content_length));
+ resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
+ return;
+ }
+
+ // multiple non-overlapping valid ranges cause a multipart
+ // 216 response which does not require an overall
+ // content-length header
+ //
+ writeHeaders(response,content,-1);
+ String mimetype=(content==null?null:content.getContentType());
+ if (mimetype==null)
+ LOG.warn("Unknown mimetype for "+request.getRequestURI());
+ MultiPartOutputStream multi = new MultiPartOutputStream(out);
+ response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ if (!response.containsHeader(HttpHeader.DATE.asString()))
+ response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+
+ // If the request has a "Request-Range" header then we need to
+ // send an old style multipart/x-byteranges Content-Type. This
+ // keeps Netscape and acrobat happy. This is what Apache does.
+ String ctp;
+ if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
+ ctp = "multipart/x-byteranges; boundary=";
+ else
+ ctp = "multipart/byteranges; boundary=";
+ response.setContentType(ctp+multi.getBoundary());
+
+ InputStream in=resource.getInputStream();
+ long pos=0;
+
+ // calculate the content-length
+ int length=0;
+ String[] header = new String[ranges.size()];
+ for (int i=0;i<ranges.size();i++)
+ {
+ InclusiveByteRange ibr = ranges.get(i);
+ header[i]=ibr.toHeaderRangeString(content_length);
+ length+=
+ ((i>0)?2:0)+
+ 2+multi.getBoundary().length()+2+
+ (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
+ HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
+ 2+
+ (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
+ }
+ length+=2+2+multi.getBoundary().length()+2+2;
+ response.setContentLength(length);
+
+ for (int i=0;i<ranges.size();i++)
+ {
+ InclusiveByteRange ibr = ranges.get(i);
+ multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
+
+ long start=ibr.getFirst(content_length);
+ long size=ibr.getSize(content_length);
+ if (in!=null)
+ {
+ // Handle non cached resource
+ if (start<pos)
+ {
+ in.close();
+ in=resource.getInputStream();
+ pos=0;
+ }
+ if (pos<start)
+ {
+ in.skip(start-pos);
+ pos=start;
+ }
+
+ IO.copy(in,multi,size);
+ pos+=size;
+ }
+ else
+ // Handle cached resource
+ (resource).writeTo(multi,start,size);
+ }
+ if (in!=null)
+ in.close();
+ multi.close();
+ }
+ return;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
+ {
+ if (content.getContentType()!=null && response.getContentType()==null)
+ response.setContentType(content.getContentType().toString());
+
+ if (response instanceof Response)
+ {
+ Response r=(Response)response;
+ HttpFields fields = r.getHttpFields();
+
+ if (content.getLastModified()!=null)
+ fields.put(HttpHeader.LAST_MODIFIED,content.getLastModified());
+ else if (content.getResource()!=null)
+ {
+ long lml=content.getResource().lastModified();
+ if (lml!=-1)
+ fields.putDateField(HttpHeader.LAST_MODIFIED,lml);
+ }
+
+ if (count != -1)
+ r.setLongContentLength(count);
+
+ writeOptionHeaders(fields);
+
+ if (_etags)
+ fields.put(HttpHeader.ETAG,content.getETag());
+ }
+ else
+ {
+ long lml=content.getResource().lastModified();
+ if (lml>=0)
+ response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
+
+ if (count != -1)
+ {
+ if (count<Integer.MAX_VALUE)
+ response.setContentLength((int)count);
+ else
+ response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(count));
+ }
+
+ writeOptionHeaders(response);
+
+ if (_etags)
+ response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeOptionHeaders(HttpFields fields)
+ {
+ if (_acceptRanges)
+ fields.put(ACCEPT_RANGES);
+
+ if (_cacheControl!=null)
+ fields.put(_cacheControl);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void writeOptionHeaders(HttpServletResponse response)
+ {
+ if (_acceptRanges)
+ response.setHeader(HttpHeader.ACCEPT_RANGES.asString(),"bytes");
+
+ if (_cacheControl!=null)
+ response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl.getValue());
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.Servlet#destroy()
+ */
+ @Override
+ public void destroy()
+ {
+ if (_cache!=null)
+ _cache.flushCache();
+ super.destroy();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+
+/* ------------------------------------------------------------ */
+/** Error Page Error Handler
+ *
+ * An ErrorHandler that maps exceptions and status codes to URIs for dispatch using
+ * the internal ERROR style of dispatch.
+ *
+ */
+public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.ErrorPageMapper
+{
+ public final static String GLOBAL_ERROR_PAGE = "org.eclipse.jetty.server.error_page.global";
+
+ protected ServletContext _servletContext;
+ private final Map<String,String> _errorPages= new HashMap<String,String>(); // code or exception to URL
+ private final List<ErrorCodeRange> _errorPageList=new ArrayList<ErrorCodeRange>(); // list of ErrorCode by range
+
+ /* ------------------------------------------------------------ */
+ public ErrorPageErrorHandler()
+ {}
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getErrorPage(HttpServletRequest request)
+ {
+ String error_page= null;
+
+ Throwable th= (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
+
+ // Walk the cause hierarchy
+ while (error_page == null && th != null )
+ {
+ Class<?> exClass=th.getClass();
+ error_page= (String)_errorPages.get(exClass.getName());
+
+ // walk the inheritance hierarchy
+ while (error_page == null)
+ {
+ exClass= exClass.getSuperclass();
+ if (exClass==null)
+ break;
+ error_page= (String)_errorPages.get(exClass.getName());
+ }
+
+ th=(th instanceof ServletException)?((ServletException)th).getRootCause():null;
+ }
+
+ if (error_page == null)
+ {
+ // look for an exact code match
+ Integer code=(Integer)request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
+ if (code!=null)
+ {
+ error_page= (String)_errorPages.get(Integer.toString(code));
+
+ // if still not found
+ if ((error_page == null) && (_errorPageList != null))
+ {
+ // look for an error code range match.
+ for (int i = 0; i < _errorPageList.size(); i++)
+ {
+ ErrorCodeRange errCode = (ErrorCodeRange) _errorPageList.get(i);
+ if (errCode.isInRange(code))
+ {
+ error_page = errCode.getUri();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ //try servlet 3.x global error page
+ if (error_page == null)
+ error_page = _errorPages.get(GLOBAL_ERROR_PAGE);
+
+ return error_page;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the errorPages.
+ */
+ public Map<String,String> getErrorPages()
+ {
+ return _errorPages;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param errorPages The errorPages to set. A map of Exception class name or error code as a string to URI string
+ */
+ public void setErrorPages(Map<String,String> errorPages)
+ {
+ _errorPages.clear();
+ if (errorPages!=null)
+ _errorPages.putAll(errorPages);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add Error Page mapping for an exception class
+ * This method is called as a result of an exception-type element in a web.xml file
+ * or may be called directly
+ * @param exception The exception
+ * @param uri The URI of the error page.
+ */
+ public void addErrorPage(Class<? extends Throwable> exception,String uri)
+ {
+ _errorPages.put(exception.getName(),uri);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add Error Page mapping for an exception class
+ * This method is called as a result of an exception-type element in a web.xml file
+ * or may be called directly
+ * @param exceptionClassName The exception
+ * @param uri The URI of the error page.
+ */
+ public void addErrorPage(String exceptionClassName,String uri)
+ {
+ _errorPages.put(exceptionClassName,uri);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add Error Page mapping for a status code.
+ * This method is called as a result of an error-code element in a web.xml file
+ * or may be called directly
+ * @param code The HTTP status code to match
+ * @param uri The URI of the error page.
+ */
+ public void addErrorPage(int code,String uri)
+ {
+ _errorPages.put(Integer.toString(code),uri);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add Error Page mapping for a status code range.
+ * This method is not available from web.xml and must be called
+ * directly.
+ * @param from The lowest matching status code
+ * @param to The highest matching status code
+ * @param uri The URI of the error page.
+ */
+ public void addErrorPage(int from, int to, String uri)
+ {
+ _errorPageList.add(new ErrorCodeRange(from, to, uri));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ _servletContext=ContextHandler.getCurrentContext();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private class ErrorCodeRange
+ {
+ private int _from;
+ private int _to;
+ private String _uri;
+
+ ErrorCodeRange(int from, int to, String uri)
+ throws IllegalArgumentException
+ {
+ if (from > to)
+ throw new IllegalArgumentException("from>to");
+
+ _from = from;
+ _to = to;
+ _uri = uri;
+ }
+
+ boolean isInRange(int value)
+ {
+ if ((value >= _from) && (value <= _to))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ String getUri()
+ {
+ return _uri;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "from: " + _from + ",to: " + _to + ",uri: " + _uri;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* --------------------------------------------------------------------- */
+/**
+ *
+ */
+public class FilterHolder extends Holder<Filter>
+{
+ private static final Logger LOG = Log.getLogger(FilterHolder.class);
+
+ /* ------------------------------------------------------------ */
+ private transient Filter _filter;
+ private transient Config _config;
+ private transient FilterRegistration.Dynamic _registration;
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor
+ */
+ public FilterHolder()
+ {
+ this(Source.EMBEDDED);
+ }
+
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor
+ */
+ public FilterHolder(Holder.Source source)
+ {
+ super(source);
+ }
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor
+ */
+ public FilterHolder(Class<? extends Filter> filter)
+ {
+ this(Source.EMBEDDED);
+ setHeldClass(filter);
+ }
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor for existing filter.
+ */
+ public FilterHolder(Filter filter)
+ {
+ this(Source.EMBEDDED);
+ setFilter(filter);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doStart()
+ throws Exception
+ {
+ super.doStart();
+
+ if (!javax.servlet.Filter.class
+ .isAssignableFrom(_class))
+ {
+ String msg = _class+" is not a javax.servlet.Filter";
+ super.stop();
+ throw new IllegalStateException(msg);
+ }
+ }
+
+
+
+
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void initialize() throws Exception
+ {
+ super.initialize();
+
+ if (_filter==null)
+ {
+ try
+ {
+ ServletContext context=_servletHandler.getServletContext();
+ _filter=(context instanceof ServletContextHandler.Context)
+ ?((ServletContextHandler.Context)context).createFilter(getHeldClass())
+ :getHeldClass().newInstance();
+ }
+ catch (ServletException se)
+ {
+ Throwable cause = se.getRootCause();
+ if (cause instanceof InstantiationException)
+ throw (InstantiationException)cause;
+ if (cause instanceof IllegalAccessException)
+ throw (IllegalAccessException)cause;
+ throw se;
+ }
+ }
+
+ _config=new Config();
+ LOG.debug("Filter.init {}",_filter);
+ _filter.init(_config);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doStop()
+ throws Exception
+ {
+ if (_filter!=null)
+ {
+ try
+ {
+ destroyInstance(_filter);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ if (!_extInstance)
+ _filter=null;
+
+ _config=null;
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void destroyInstance (Object o)
+ throws Exception
+ {
+ if (o==null)
+ return;
+ Filter f = (Filter)o;
+ f.destroy();
+ getServletHandler().destroyFilter(f);
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void setFilter(Filter filter)
+ {
+ _filter=filter;
+ _extInstance=true;
+ setHeldClass(filter.getClass());
+ if (getName()==null)
+ setName(filter.getClass().getName());
+ }
+
+ /* ------------------------------------------------------------ */
+ public Filter getFilter()
+ {
+ return _filter;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return getName();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ super.dump(out, indent);
+ if(_filter instanceof Dumpable) {
+ ((Dumpable) _filter).dump(out, indent);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public FilterRegistration.Dynamic getRegistration()
+ {
+ if (_registration == null)
+ _registration = new Registration();
+ return _registration;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected class Registration extends HolderRegistration implements FilterRegistration.Dynamic
+ {
+ public void addMappingForServletNames(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... servletNames)
+ {
+ illegalStateIfContextStarted();
+ FilterMapping mapping = new FilterMapping();
+ mapping.setFilterHolder(FilterHolder.this);
+ mapping.setServletNames(servletNames);
+ mapping.setDispatcherTypes(dispatcherTypes);
+ if (isMatchAfter)
+ _servletHandler.addFilterMapping(mapping);
+ else
+ _servletHandler.prependFilterMapping(mapping);
+ }
+
+ public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns)
+ {
+ illegalStateIfContextStarted();
+ FilterMapping mapping = new FilterMapping();
+ mapping.setFilterHolder(FilterHolder.this);
+ mapping.setPathSpecs(urlPatterns);
+ mapping.setDispatcherTypes(dispatcherTypes);
+ if (isMatchAfter)
+ _servletHandler.addFilterMapping(mapping);
+ else
+ _servletHandler.prependFilterMapping(mapping);
+ }
+
+ public Collection<String> getServletNameMappings()
+ {
+ FilterMapping[] mappings =_servletHandler.getFilterMappings();
+ List<String> names=new ArrayList<String>();
+ for (FilterMapping mapping : mappings)
+ {
+ if (mapping.getFilterHolder()!=FilterHolder.this)
+ continue;
+ String[] servlets=mapping.getServletNames();
+ if (servlets!=null && servlets.length>0)
+ names.addAll(Arrays.asList(servlets));
+ }
+ return names;
+ }
+
+ public Collection<String> getUrlPatternMappings()
+ {
+ FilterMapping[] mappings =_servletHandler.getFilterMappings();
+ List<String> patterns=new ArrayList<String>();
+ for (FilterMapping mapping : mappings)
+ {
+ if (mapping.getFilterHolder()!=FilterHolder.this)
+ continue;
+ String[] specs=mapping.getPathSpecs();
+ patterns.addAll(TypeUtil.asList(specs));
+ }
+ return patterns;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ class Config extends HolderConfig implements FilterConfig
+ {
+ /* ------------------------------------------------------------ */
+ public String getFilterName()
+ {
+ return _name;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+@ManagedObject("Filter Mappings")
+public class FilterMapping implements Dumpable
+{
+ /** Dispatch types */
+ public static final int DEFAULT=0;
+ public static final int REQUEST=1;
+ public static final int FORWARD=2;
+ public static final int INCLUDE=4;
+ public static final int ERROR=8;
+ public static final int ASYNC=16;
+ public static final int ALL=31;
+
+
+ /* ------------------------------------------------------------ */
+ /** Dispatch type from name
+ */
+ public static DispatcherType dispatch(String type)
+ {
+ if ("request".equalsIgnoreCase(type))
+ return DispatcherType.REQUEST;
+ if ("forward".equalsIgnoreCase(type))
+ return DispatcherType.FORWARD;
+ if ("include".equalsIgnoreCase(type))
+ return DispatcherType.INCLUDE;
+ if ("error".equalsIgnoreCase(type))
+ return DispatcherType.ERROR;
+ if ("async".equalsIgnoreCase(type))
+ return DispatcherType.ASYNC;
+ throw new IllegalArgumentException(type);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Dispatch type from name
+ */
+ public static int dispatch(DispatcherType type)
+ {
+ switch(type)
+ {
+ case REQUEST:
+ return REQUEST;
+ case ASYNC:
+ return ASYNC;
+ case FORWARD:
+ return FORWARD;
+ case INCLUDE:
+ return INCLUDE;
+ case ERROR:
+ return ERROR;
+ }
+ throw new IllegalArgumentException(type.toString());
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+
+
+ private int _dispatches=DEFAULT;
+ private String _filterName;
+ private transient FilterHolder _holder;
+ private String[] _pathSpecs;
+ private String[] _servletNames;
+
+ /* ------------------------------------------------------------ */
+ public FilterMapping()
+ {}
+
+ /* ------------------------------------------------------------ */
+ /** Check if this filter applies to a path.
+ * @param path The path to check or null to just check type
+ * @param type The type of request: __REQUEST,__FORWARD,__INCLUDE, __ASYNC or __ERROR.
+ * @return True if this filter applies
+ */
+ boolean appliesTo(String path, int type)
+ {
+ if (appliesTo(type))
+ {
+ for (int i=0;i<_pathSpecs.length;i++)
+ if (_pathSpecs[i]!=null && PathMap.match(_pathSpecs[i], path,true))
+ return true;
+ }
+
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Check if this filter applies to a particular dispatch type.
+ * @param type The type of request:
+ * {@link Handler#REQUEST}, {@link Handler#FORWARD}, {@link Handler#INCLUDE} or {@link Handler#ERROR}.
+ * @return <code>true</code> if this filter applies
+ */
+ boolean appliesTo(int type)
+ {
+ if (_dispatches==0)
+ return type==REQUEST || type==ASYNC && _holder.isAsyncSupported();
+ return (_dispatches&type)!=0;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean appliesTo(DispatcherType t)
+ {
+ return appliesTo(dispatch(t));
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isDefaultDispatches()
+ {
+ return _dispatches==0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the filterName.
+ */
+ @ManagedAttribute(value="filter name", readonly=true)
+ public String getFilterName()
+ {
+ return _filterName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the holder.
+ */
+ FilterHolder getFilterHolder()
+ {
+ return _holder;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the pathSpec.
+ */
+ @ManagedAttribute(value="url patterns", readonly=true)
+ public String[] getPathSpecs()
+ {
+ return _pathSpecs;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setDispatcherTypes(EnumSet<DispatcherType> dispatcherTypes)
+ {
+ _dispatches=DEFAULT;
+ if (dispatcherTypes!=null)
+ {
+ if (dispatcherTypes.contains(DispatcherType.ERROR))
+ _dispatches|=ERROR;
+ if (dispatcherTypes.contains(DispatcherType.FORWARD))
+ _dispatches|=FORWARD;
+ if (dispatcherTypes.contains(DispatcherType.INCLUDE))
+ _dispatches|=INCLUDE;
+ if (dispatcherTypes.contains(DispatcherType.REQUEST))
+ _dispatches|=REQUEST;
+ if (dispatcherTypes.contains(DispatcherType.ASYNC))
+ _dispatches|=ASYNC;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param dispatches The dispatches to set.
+ * @see #DEFAULT
+ * @see #REQUEST
+ * @see #ERROR
+ * @see #FORWARD
+ * @see #INCLUDE
+ */
+ public void setDispatches(int dispatches)
+ {
+ _dispatches = dispatches;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filterName The filterName to set.
+ */
+ public void setFilterName(String filterName)
+ {
+ _filterName = filterName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param holder The holder to set.
+ */
+ void setFilterHolder(FilterHolder holder)
+ {
+ _holder = holder;
+ setFilterName(holder.getName());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathSpecs The Path specifications to which this filter should be mapped.
+ */
+ public void setPathSpecs(String[] pathSpecs)
+ {
+ _pathSpecs = pathSpecs;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathSpec The pathSpec to set.
+ */
+ public void setPathSpec(String pathSpec)
+ {
+ _pathSpecs = new String[]{pathSpec};
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the servletName.
+ */
+ @ManagedAttribute(value="servlet names", readonly=true)
+ public String[] getServletNames()
+ {
+ return _servletNames;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletNames Maps the {@link #setFilterName(String) named filter} to multiple servlets
+ * @see #setServletName
+ */
+ public void setServletNames(String[] servletNames)
+ {
+ _servletNames = servletNames;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletName Maps the {@link #setFilterName(String) named filter} to a single servlet
+ * @see #setServletNames
+ */
+ public void setServletName(String servletName)
+ {
+ _servletNames = new String[]{servletName};
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return
+ TypeUtil.asList(_pathSpecs)+"/"+
+ TypeUtil.asList(_servletNames)+"=="+
+ _dispatches+"=>"+
+ _filterName;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ out.append(String.valueOf(this)).append("\n");
+ }
+
+ /* ------------------------------------------------------------ */
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Registration;
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* --------------------------------------------------------------------- */
+/**
+ * Holder
+ *
+ * Specialization of AbstractHolder for servlet-related classes that
+ * have init-params etc
+ *
+ */
+@ManagedObject("Holder - a container for servlets and the like")
+public class Holder<T> extends BaseHolder<T>
+{
+ private static final Logger LOG = Log.getLogger(Holder.class);
+
+ protected final Map<String,String> _initParams=new HashMap<String,String>(3);
+ protected String _displayName;
+ protected boolean _asyncSupported;
+ protected String _name;
+
+
+ /* ---------------------------------------------------------------- */
+ protected Holder(Source source)
+ {
+ super(source);
+ switch(_source)
+ {
+ case JAVAX_API:
+ case DESCRIPTOR:
+ case ANNOTATION:
+ _asyncSupported=false;
+ break;
+ default:
+ _asyncSupported=true;
+ }
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute(value="Display Name", readonly=true)
+ public String getDisplayName()
+ {
+ return _displayName;
+ }
+
+ /* ---------------------------------------------------------------- */
+ public String getInitParameter(String param)
+ {
+ if (_initParams==null)
+ return null;
+ return (String)_initParams.get(param);
+ }
+
+ /* ------------------------------------------------------------ */
+ public Enumeration getInitParameterNames()
+ {
+ if (_initParams==null)
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ return Collections.enumeration(_initParams.keySet());
+ }
+
+ /* ---------------------------------------------------------------- */
+ @ManagedAttribute(value="Initial Parameters", readonly=true)
+ public Map<String,String> getInitParameters()
+ {
+ return _initParams;
+ }
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute(value="Name", readonly=true)
+ public String getName()
+ {
+ return _name;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void destroyInstance(Object instance)
+ throws Exception
+ {
+ }
+ /* ------------------------------------------------------------ */
+ /**
+ * @param className The className to set.
+ */
+ public void setClassName(String className)
+ {
+ super.setClassName(className);
+ if (_name==null)
+ _name=className+"-"+Integer.toHexString(this.hashCode());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param held The class to hold
+ */
+ public void setHeldClass(Class<? extends T> held)
+ {
+ super.setHeldClass(held);
+ if (held!=null)
+ {
+ if (_name==null)
+ _name=held.getName()+"-"+Integer.toHexString(this.hashCode());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setDisplayName(String name)
+ {
+ _displayName=name;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setInitParameter(String param,String value)
+ {
+ _initParams.put(param,value);
+ }
+
+ /* ---------------------------------------------------------------- */
+ public void setInitParameters(Map<String,String> map)
+ {
+ _initParams.clear();
+ _initParams.putAll(map);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * The name is a primary key for the held object.
+ * Ensure that the name is set BEFORE adding a Holder
+ * (eg ServletHolder or FilterHolder) to a ServletHandler.
+ * @param name The name to set.
+ */
+ public void setName(String name)
+ {
+ _name = name;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void setAsyncSupported(boolean suspendable)
+ {
+ _asyncSupported=suspendable;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isAsyncSupported()
+ {
+ return _asyncSupported;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ super.dump(out,indent);
+ ContainerLifeCycle.dump(out,indent,_initParams.entrySet());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String dump()
+ {
+ return super.dump();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x==%s",_name,hashCode(),_className);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected class HolderConfig
+ {
+
+ /* -------------------------------------------------------- */
+ public ServletContext getServletContext()
+ {
+ return _servletHandler.getServletContext();
+ }
+
+ /* -------------------------------------------------------- */
+ public String getInitParameter(String param)
+ {
+ return Holder.this.getInitParameter(param);
+ }
+
+ /* -------------------------------------------------------- */
+ public Enumeration getInitParameterNames()
+ {
+ return Holder.this.getInitParameterNames();
+ }
+ }
+
+ /* -------------------------------------------------------- */
+ /* -------------------------------------------------------- */
+ /* -------------------------------------------------------- */
+ protected class HolderRegistration implements Registration.Dynamic
+ {
+ public void setAsyncSupported(boolean isAsyncSupported)
+ {
+ illegalStateIfContextStarted();
+ Holder.this.setAsyncSupported(isAsyncSupported);
+ }
+
+ public void setDescription(String description)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(this+" is "+description);
+ }
+
+ public String getClassName()
+ {
+ return Holder.this.getClassName();
+ }
+
+ public String getInitParameter(String name)
+ {
+ return Holder.this.getInitParameter(name);
+ }
+
+ public Map<String, String> getInitParameters()
+ {
+ return Holder.this.getInitParameters();
+ }
+
+ public String getName()
+ {
+ return Holder.this.getName();
+ }
+
+ public boolean setInitParameter(String name, String value)
+ {
+ illegalStateIfContextStarted();
+ if (name == null) {
+ throw new IllegalArgumentException("init parameter name required");
+ }
+ if (value == null) {
+ throw new IllegalArgumentException("non-null value required for init parameter " + name);
+ }
+ if (Holder.this.getInitParameter(name)!=null)
+ return false;
+ Holder.this.setInitParameter(name,value);
+ return true;
+ }
+
+ public Set<String> setInitParameters(Map<String, String> initParameters)
+ {
+ illegalStateIfContextStarted();
+ Set<String> clash=null;
+ for (Map.Entry<String, String> entry : initParameters.entrySet())
+ {
+ if (entry.getKey() == null) {
+ throw new IllegalArgumentException("init parameter name required");
+ }
+ if (entry.getValue() == null) {
+ throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey());
+ }
+ if (Holder.this.getInitParameter(entry.getKey())!=null)
+ {
+ if (clash==null)
+ clash=new HashSet<String>();
+ clash.add(entry.getKey());
+ }
+ }
+ if (clash!=null)
+ return clash;
+ Holder.this.getInitParameters().putAll(initParameters);
+ return Collections.emptySet();
+ }
+ }
+}
+
+
+
+
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Dynamic Servlet Invoker.
+ * This servlet invokes anonymous servlets that have not been defined
+ * in the web.xml or by other means. The first element of the pathInfo
+ * of a request passed to the envoker is treated as a servlet name for
+ * an existing servlet, or as a class name of a new servlet.
+ * This servlet is normally mapped to /servlet/*
+ * This servlet support the following initParams:
+ * <PRE>
+ * nonContextServlets If false, the invoker can only load
+ * servlets from the contexts classloader.
+ * This is false by default and setting this
+ * to true may have security implications.
+ *
+ * verbose If true, log dynamic loads
+ *
+ * * All other parameters are copied to the
+ * each dynamic servlet as init parameters
+ * </PRE>
+ * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $
+ *
+ */
+public class Invoker extends HttpServlet
+{
+ private static final Logger LOG = Log.getLogger(Invoker.class);
+
+
+ private ContextHandler _contextHandler;
+ private ServletHandler _servletHandler;
+ private Map.Entry _invokerEntry;
+ private Map _parameters;
+ private boolean _nonContextServlets;
+ private boolean _verbose;
+
+ /* ------------------------------------------------------------ */
+ public void init()
+ {
+ ServletContext config=getServletContext();
+ _contextHandler=((ContextHandler.Context)config).getContextHandler();
+
+ Handler handler=_contextHandler.getHandler();
+ while (handler!=null && !(handler instanceof ServletHandler) && (handler instanceof HandlerWrapper))
+ handler=((HandlerWrapper)handler).getHandler();
+ _servletHandler = (ServletHandler)handler;
+ Enumeration e = getInitParameterNames();
+ while(e.hasMoreElements())
+ {
+ String param=(String)e.nextElement();
+ String value=getInitParameter(param);
+ String lvalue=value.toLowerCase(Locale.ENGLISH);
+ if ("nonContextServlets".equals(param))
+ {
+ _nonContextServlets=value.length()>0 && lvalue.startsWith("t");
+ }
+ if ("verbose".equals(param))
+ {
+ _verbose=value.length()>0 && lvalue.startsWith("t");
+ }
+ else
+ {
+ if (_parameters==null)
+ _parameters=new HashMap();
+ _parameters.put(param,value);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void service(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ // Get the requested path and info
+ boolean included=false;
+ String servlet_path=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+ if (servlet_path==null)
+ servlet_path=request.getServletPath();
+ else
+ included=true;
+ String path_info = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+ if (path_info==null)
+ path_info=request.getPathInfo();
+
+ // Get the servlet class
+ String servlet = path_info;
+ if (servlet==null || servlet.length()<=1 )
+ {
+ response.sendError(404);
+ return;
+ }
+
+
+ int i0=servlet.charAt(0)=='/'?1:0;
+ int i1=servlet.indexOf('/',i0);
+ servlet=i1<0?servlet.substring(i0):servlet.substring(i0,i1);
+
+ // look for a named holder
+ ServletHolder[] holders = _servletHandler.getServlets();
+ ServletHolder holder = getHolder (holders, servlet);
+
+ if (holder!=null)
+ {
+ // Found a named servlet (from a user's web.xml file) so
+ // now we add a mapping for it
+ if (LOG.isDebugEnabled())
+ LOG.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*");
+ ServletMapping mapping = new ServletMapping();
+ mapping.setServletName(servlet);
+ mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*");
+ _servletHandler.setServletMappings((ServletMapping[])ArrayUtil.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class));
+ }
+ else
+ {
+ // look for a class mapping
+ if (servlet.endsWith(".class"))
+ servlet=servlet.substring(0,servlet.length()-6);
+ if (servlet==null || servlet.length()==0)
+ {
+ response.sendError(404);
+ return;
+ }
+
+ synchronized(_servletHandler)
+ {
+ // find the entry for the invoker (me)
+ _invokerEntry=_servletHandler.getHolderEntry(servlet_path);
+
+ // Check for existing mapping (avoid threaded race).
+ String path=URIUtil.addPaths(servlet_path,servlet);
+ Map.Entry entry = _servletHandler.getHolderEntry(path);
+
+ if (entry!=null && !entry.equals(_invokerEntry))
+ {
+ // Use the holder
+ holder=(ServletHolder)entry.getValue();
+ }
+ else
+ {
+ // Make a holder
+ if (LOG.isDebugEnabled())
+ LOG.debug("Making new servlet="+servlet+" with path="+path+"/*");
+ holder=_servletHandler.addServletWithMapping(servlet, path+"/*");
+
+ if (_parameters!=null)
+ holder.setInitParameters(_parameters);
+
+ try {holder.start();}
+ catch (Exception e)
+ {
+ LOG.debug(e);
+ throw new UnavailableException(e.toString());
+ }
+
+ // Check it is from an allowable classloader
+ if (!_nonContextServlets)
+ {
+ Object s=holder.getServlet();
+
+ if (_contextHandler.getClassLoader()!=
+ s.getClass().getClassLoader())
+ {
+ try
+ {
+ holder.stop();
+ }
+ catch (Exception e)
+ {
+ LOG.ignore(e);
+ }
+
+ LOG.warn("Dynamic servlet "+s+
+ " not loaded from context "+
+ request.getContextPath());
+ throw new UnavailableException("Not in context");
+ }
+ }
+
+ if (_verbose && LOG.isDebugEnabled())
+ LOG.debug("Dynamic load '"+servlet+"' at "+path);
+ }
+ }
+ }
+
+ if (holder!=null)
+ {
+ final Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+ holder.handle(baseRequest,
+ new InvokedRequest(request,included,servlet,servlet_path,path_info),
+ response);
+ }
+ else
+ {
+ LOG.info("Can't find holder for servlet: "+servlet);
+ response.sendError(404);
+ }
+
+
+ }
+
+ /* ------------------------------------------------------------ */
+ class InvokedRequest extends HttpServletRequestWrapper
+ {
+ String _servletPath;
+ String _pathInfo;
+ boolean _included;
+
+ /* ------------------------------------------------------------ */
+ InvokedRequest(HttpServletRequest request,
+ boolean included,
+ String name,
+ String servletPath,
+ String pathInfo)
+ {
+ super(request);
+ _included=included;
+ _servletPath=URIUtil.addPaths(servletPath,name);
+ _pathInfo=pathInfo.substring(name.length()+1);
+ if (_pathInfo.length()==0)
+ _pathInfo=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getServletPath()
+ {
+ if (_included)
+ return super.getServletPath();
+ return _servletPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getPathInfo()
+ {
+ if (_included)
+ return super.getPathInfo();
+ return _pathInfo;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getAttribute(String name)
+ {
+ if (_included)
+ {
+ if (name.equals(Dispatcher.INCLUDE_REQUEST_URI))
+ return URIUtil.addPaths(URIUtil.addPaths(getContextPath(),_servletPath),_pathInfo);
+ if (name.equals(Dispatcher.INCLUDE_PATH_INFO))
+ return _pathInfo;
+ if (name.equals(Dispatcher.INCLUDE_SERVLET_PATH))
+ return _servletPath;
+ }
+ return super.getAttribute(name);
+ }
+ }
+
+
+ private ServletHolder getHolder(ServletHolder[] holders, String servlet)
+ {
+ if (holders == null)
+ return null;
+
+ ServletHolder holder = null;
+ for (int i=0; holder==null && i<holders.length; i++)
+ {
+ if (holders[i].getName().equals(servlet))
+ {
+ holder = holders[i];
+ }
+ }
+ return holder;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Servlet handling JSP Property Group mappings
+ * <p>
+ * This servlet is mapped to by any URL pattern for a JSP property group.
+ * Resources handled by this servlet that are not directories will be passed
+ * directly to the JSP servlet. Resources that are directories will be
+ * passed directly to the default servlet.
+ */
+public class JspPropertyGroupServlet extends GenericServlet
+{
+ private static final long serialVersionUID = 3681783214726776945L;
+
+ public final static String NAME = "__org.eclipse.jetty.servlet.JspPropertyGroupServlet__";
+ private final ServletHandler _servletHandler;
+ private final ContextHandler _contextHandler;
+ private ServletHolder _dftServlet;
+ private ServletHolder _jspServlet;
+ private boolean _starJspMapped;
+
+ public JspPropertyGroupServlet(ContextHandler context, ServletHandler servletHandler)
+ {
+ _contextHandler=context;
+ _servletHandler=servletHandler;
+ }
+
+ @Override
+ public void init() throws ServletException
+ {
+ String jsp_name = "jsp";
+ ServletMapping servlet_mapping =_servletHandler.getServletMapping("*.jsp");
+ if (servlet_mapping!=null)
+ {
+ _starJspMapped=true;
+
+ //now find the jsp servlet, ignoring the mapping that is for ourself
+ ServletMapping[] mappings = _servletHandler.getServletMappings();
+ for (ServletMapping m:mappings)
+ {
+ String[] paths = m.getPathSpecs();
+ if (paths!=null)
+ {
+ for (String path:paths)
+ {
+ if ("*.jsp".equals(path) && !NAME.equals(m.getServletName()))
+ servlet_mapping = m;
+ }
+ }
+ }
+
+ jsp_name=servlet_mapping.getServletName();
+ }
+ _jspServlet=_servletHandler.getServlet(jsp_name);
+
+ String dft_name="default";
+ ServletMapping default_mapping=_servletHandler.getServletMapping("/");
+ if (default_mapping!=null)
+ dft_name=default_mapping.getServletName();
+ _dftServlet=_servletHandler.getServlet(dft_name);
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+ {
+ HttpServletRequest request = null;
+ if (req instanceof HttpServletRequest)
+ request = (HttpServletRequest)req;
+ else
+ throw new ServletException("Request not HttpServletRequest");
+
+ String servletPath=null;
+ String pathInfo=null;
+ if (request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI)!=null)
+ {
+ servletPath=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+ pathInfo=(String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+ if (servletPath==null)
+ {
+ servletPath=request.getServletPath();
+ pathInfo=request.getPathInfo();
+ }
+ }
+ else
+ {
+ servletPath = request.getServletPath();
+ pathInfo = request.getPathInfo();
+ }
+
+ String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+
+ if (pathInContext.endsWith("/"))
+ {
+ _dftServlet.getServlet().service(req,res);
+ }
+ else if (_starJspMapped && pathInContext.toLowerCase().endsWith(".jsp"))
+ {
+ _jspServlet.getServlet().service(req,res);
+ }
+ else
+ {
+
+ Resource resource = _contextHandler.getResource(pathInContext);
+ if (resource!=null && resource.isDirectory())
+ _dftServlet.getServlet().service(req,res);
+ else
+ _jspServlet.getServlet().service(req,res);
+ }
+
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.util.EventListener;
+
+/**
+ * ListenerHolder
+ *
+ * Specialization of AbstractHolder for servlet listeners. This
+ * allows us to record where the listener originated - web.xml,
+ * annotation, api etc.
+ */
+public class ListenerHolder extends BaseHolder<EventListener>
+{
+ private EventListener _listener;
+
+
+ public ListenerHolder(Source source)
+ {
+ super(source);
+ }
+
+
+ public void setListener(EventListener listener)
+ {
+ _listener = listener;
+ setClassName(listener.getClass().getName());
+ setHeldClass(listener.getClass());
+ _extInstance=true;
+ }
+
+ public EventListener getListener()
+ {
+ return _listener;
+ }
+
+
+ @Override
+ public void doStart() throws Exception
+ {
+ //Listeners always have an instance eagerly created, it cannot be deferred to the doStart method
+ if (_listener == null)
+ throw new IllegalStateException("No listener instance");
+
+ super.doStart();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class NoJspServlet extends HttpServlet
+{
+ private boolean _warned;
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
+ {
+ if (!_warned)
+ getServletContext().log("No JSP support. Check that JSP jars are in lib/jsp and that the JSP option has been specified to start.jar");
+ _warned=true;
+
+ response.sendError(500,"JSP support not configured");
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+
+import org.eclipse.jetty.security.ConstraintAware;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+
+/* ------------------------------------------------------------ */
+/** Servlet Context.
+ * This extension to the ContextHandler allows for
+ * simple construction of a context with ServletHandler and optionally
+ * session and security handlers, et.<pre>
+ * new ServletContext("/context",Context.SESSIONS|Context.NO_SECURITY);
+ * </pre>
+ * <p/>
+ * This class should have been called ServletContext, but this would have
+ * cause confusion with {@link ServletContext}.
+ */
+@ManagedObject("Servlet Context Handler")
+public class ServletContextHandler extends ContextHandler
+{
+ public final static int SESSIONS=1;
+ public final static int SECURITY=2;
+ public final static int NO_SESSIONS=0;
+ public final static int NO_SECURITY=0;
+
+ public interface ServletContainerInitializerCaller extends LifeCycle {};
+
+ protected final List<Decorator> _decorators= new ArrayList<>();
+ protected Class<? extends SecurityHandler> _defaultSecurityHandlerClass=org.eclipse.jetty.security.ConstraintSecurityHandler.class;
+ protected SessionHandler _sessionHandler;
+ protected SecurityHandler _securityHandler;
+ protected ServletHandler _servletHandler;
+ protected int _options;
+ protected JspConfigDescriptor _jspConfig;
+
+ /* ------------------------------------------------------------ */
+ public ServletContextHandler()
+ {
+ this(null,null,null,null,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletContextHandler(int options)
+ {
+ this(null,null,options);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletContextHandler(HandlerContainer parent, String contextPath)
+ {
+ this(parent,contextPath,null,null,null,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletContextHandler(HandlerContainer parent, String contextPath, int options)
+ {
+ this(parent,contextPath,null,null,null,null,options);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletContextHandler(HandlerContainer parent, String contextPath, boolean sessions, boolean security)
+ {
+ this(parent,contextPath,(sessions?SESSIONS:0)|(security?SECURITY:0));
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletContextHandler(HandlerContainer parent, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
+ {
+ this(parent,null,sessionHandler,securityHandler,servletHandler,errorHandler);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
+ {
+ this(parent,contextPath,sessionHandler,securityHandler,servletHandler,errorHandler,0);
+ }
+
+ public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler,int options)
+ {
+ super((ContextHandler.Context)null);
+ _options=options;
+ _scontext = new Context();
+ _sessionHandler = sessionHandler;
+ _securityHandler = securityHandler;
+ _servletHandler = servletHandler;
+
+ if (contextPath!=null)
+ setContextPath(contextPath);
+
+ if (parent instanceof HandlerWrapper)
+ ((HandlerWrapper)parent).setHandler(this);
+ else if (parent instanceof HandlerCollection)
+ ((HandlerCollection)parent).addHandler(this);
+
+
+ // Link the handlers
+ relinkHandlers();
+
+ if (errorHandler!=null)
+ setErrorHandler(errorHandler);
+
+ }
+
+ /* ------------------------------------------------------------ */
+ private void relinkHandlers()
+ {
+ HandlerWrapper handler=this;
+
+ // Skip any injected handlers
+ while (handler.getHandler() instanceof HandlerWrapper)
+ {
+ HandlerWrapper wrapper = (HandlerWrapper)handler.getHandler();
+ if (wrapper instanceof SessionHandler ||
+ wrapper instanceof SecurityHandler ||
+ wrapper instanceof ServletHandler)
+ break;
+ handler=wrapper;
+ }
+
+ if (getSessionHandler()!=null)
+ {
+ if (handler==this)
+ super.setHandler(_sessionHandler);
+ else
+ handler.setHandler(_sessionHandler);
+ handler=_sessionHandler;
+ }
+
+ if (getSecurityHandler()!=null)
+ {
+ if (handler==this)
+ super.setHandler(_securityHandler);
+ else
+ handler.setHandler(_securityHandler);
+ handler=_securityHandler;
+ }
+
+ if (getServletHandler()!=null)
+ {
+ if (handler==this)
+ super.setHandler(_servletHandler);
+ else
+ handler.setHandler(_servletHandler);
+ handler=_servletHandler;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.handler.ContextHandler#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (_decorators != null)
+ _decorators.clear();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the defaultSecurityHandlerClass.
+ * @return the defaultSecurityHandlerClass
+ */
+ public Class<? extends SecurityHandler> getDefaultSecurityHandlerClass()
+ {
+ return _defaultSecurityHandlerClass;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the defaultSecurityHandlerClass.
+ * @param defaultSecurityHandlerClass the defaultSecurityHandlerClass to set
+ */
+ public void setDefaultSecurityHandlerClass(Class<? extends SecurityHandler> defaultSecurityHandlerClass)
+ {
+ _defaultSecurityHandlerClass = defaultSecurityHandlerClass;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected SessionHandler newSessionHandler()
+ {
+ return new SessionHandler();
+ }
+
+ /* ------------------------------------------------------------ */
+ protected SecurityHandler newSecurityHandler()
+ {
+ try
+ {
+ return (SecurityHandler)_defaultSecurityHandlerClass.newInstance();
+ }
+ catch(Exception e)
+ {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected ServletHandler newServletHandler()
+ {
+ return new ServletHandler();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Finish constructing handlers and link them together.
+ *
+ * @see org.eclipse.jetty.server.handler.ContextHandler#startContext()
+ */
+ @Override
+ protected void startContext() throws Exception
+ {
+ ServletContainerInitializerCaller sciBean = getBean(ServletContainerInitializerCaller.class);
+ if (sciBean!=null)
+ sciBean.start();
+
+ if (_servletHandler != null)
+ {
+ //Call decorators on all holders, and also on any EventListeners before
+ //decorators are called on any other classes (like servlets and filters)
+ for (int i=_decorators.size()-1;i>=0; i--)
+ {
+ Decorator decorator = _decorators.get(i);
+ //Do any decorations on the ListenerHolders AND the listener instances first up
+ if (_servletHandler.getListeners()!=null)
+ {
+ for (ListenerHolder holder:_servletHandler.getListeners())
+ {
+ decorator.decorate(holder.getListener());
+ }
+ }
+ }
+ }
+
+ super.startContext();
+
+ // OK to Initialize servlet handler now that all relevant object trees have been started
+ if (_servletHandler != null)
+ _servletHandler.initialize();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the securityHandler.
+ */
+ @ManagedAttribute(value="context security handler", readonly=true)
+ public SecurityHandler getSecurityHandler()
+ {
+ if (_securityHandler==null && (_options&SECURITY)!=0 && !isStarted())
+ _securityHandler=newSecurityHandler();
+
+ return _securityHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the servletHandler.
+ */
+ @ManagedAttribute(value="context servlet handler", readonly=true)
+ public ServletHandler getServletHandler()
+ {
+ if (_servletHandler==null && !isStarted())
+ _servletHandler=newServletHandler();
+ return _servletHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the sessionHandler.
+ */
+ @ManagedAttribute(value="context session handler", readonly=true)
+ public SessionHandler getSessionHandler()
+ {
+ if (_sessionHandler==null && (_options&SESSIONS)!=0 && !isStarted())
+ _sessionHandler=newSessionHandler();
+ return _sessionHandler;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** conveniance method to add a servlet.
+ */
+ public ServletHolder addServlet(String className,String pathSpec)
+ {
+ return getServletHandler().addServletWithMapping(className, pathSpec);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** conveniance method to add a servlet.
+ */
+ public ServletHolder addServlet(Class<? extends Servlet> servlet,String pathSpec)
+ {
+ return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** conveniance method to add a servlet.
+ */
+ public void addServlet(ServletHolder servlet,String pathSpec)
+ {
+ getServletHandler().addServletWithMapping(servlet, pathSpec);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** conveniance method to add a filter
+ */
+ public void addFilter(FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
+ {
+ getServletHandler().addFilterWithMapping(holder,pathSpec,dispatches);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** convenience method to add a filter
+ */
+ public FilterHolder addFilter(Class<? extends Filter> filterClass,String pathSpec,EnumSet<DispatcherType> dispatches)
+ {
+ return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** convenience method to add a filter
+ */
+ public FilterHolder addFilter(String filterClass,String pathSpec,EnumSet<DispatcherType> dispatches)
+ {
+ return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
+ }
+
+ /**
+ * notification that a ServletRegistration has been created so we can track the annotations
+ * @param holder new holder created through the api.
+ * @return the ServletRegistration.Dynamic
+ */
+ protected ServletRegistration.Dynamic dynamicHolderAdded(ServletHolder holder) {
+ return holder.getRegistration();
+ }
+
+ /**
+ * delegate for ServletContext.declareRole method
+ * @param roleNames role names to add
+ */
+ protected void addRoles(String... roleNames) {
+ //Get a reference to the SecurityHandler, which must be ConstraintAware
+ if (_securityHandler != null && _securityHandler instanceof ConstraintAware)
+ {
+ HashSet<String> union = new HashSet<String>();
+ Set<String> existing = ((ConstraintAware)_securityHandler).getRoles();
+ if (existing != null)
+ union.addAll(existing);
+ union.addAll(Arrays.asList(roleNames));
+ ((ConstraintSecurityHandler)_securityHandler).setRoles(union);
+ }
+ }
+
+ /**
+ * Delegate for ServletRegistration.Dynamic.setServletSecurity method
+ * @param registration ServletRegistration.Dynamic instance that setServletSecurity was called on
+ * @param servletSecurityElement new security info
+ * @return the set of exact URL mappings currently associated with the registration that are also present in the web.xml
+ * security constraints and thus will be unaffected by this call.
+ */
+ public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement)
+ {
+ //Default implementation is to just accept them all. If using a webapp, then this behaviour is overridden in WebAppContext.setServletSecurity
+ Collection<String> pathSpecs = registration.getMappings();
+ if (pathSpecs != null)
+ {
+ for (String pathSpec:pathSpecs)
+ {
+ List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
+ for (ConstraintMapping m:mappings)
+ ((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
+ }
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void callContextInitialized(ServletContextListener l, ServletContextEvent e)
+ {
+ try
+ {
+ //toggle state of the dynamic API so that the listener cannot use it
+ if(isProgrammaticListener(l))
+ this.getServletContext().setEnabled(false);
+
+ super.callContextInitialized(l, e);
+ }
+ finally
+ {
+ //untoggle the state of the dynamic API
+ this.getServletContext().setEnabled(true);
+ }
+ }
+
+
+ @Override
+ public void callContextDestroyed(ServletContextListener l, ServletContextEvent e)
+ {
+ super.callContextDestroyed(l, e);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param sessionHandler The sessionHandler to set.
+ */
+ public void setSessionHandler(SessionHandler sessionHandler)
+ {
+ if (isStarted())
+ throw new IllegalStateException("STARTED");
+
+ if (_sessionHandler!=null)
+ _sessionHandler.setHandler(null);
+
+ _sessionHandler = sessionHandler;
+ relinkHandlers();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param securityHandler The {@link SecurityHandler} to set on this context.
+ */
+ public void setSecurityHandler(SecurityHandler securityHandler)
+ {
+ if (isStarted())
+ throw new IllegalStateException("STARTED");
+
+ if (_securityHandler!=null)
+ _securityHandler.setHandler(null);
+ _securityHandler = securityHandler;
+ relinkHandlers();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletHandler The servletHandler to set.
+ */
+ public void setServletHandler(ServletHandler servletHandler)
+ {
+ if (isStarted())
+ throw new IllegalStateException("STARTED");
+
+ Handler next=null;
+ if (_servletHandler!=null)
+ {
+ next=_servletHandler.getHandler();
+ _servletHandler.setHandler(null);
+ }
+ _servletHandler = servletHandler;
+ relinkHandlers();
+ _servletHandler.setHandler(next);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setHandler(Handler handler)
+ {
+ if (handler instanceof ServletHandler)
+ setServletHandler((ServletHandler) handler);
+ else if (handler instanceof SessionHandler)
+ setSessionHandler((SessionHandler) handler);
+ else if (handler instanceof SecurityHandler)
+ setSecurityHandler((SecurityHandler)handler);
+ else if (handler == null || handler instanceof HandlerWrapper)
+ {
+ super.setHandler(handler);
+ relinkHandlers();
+ }
+ else
+ throw new IllegalArgumentException();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Insert a HandlerWrapper before the first Session,Security or ServletHandler
+ * but after any other HandlerWrappers.
+ */
+ public void insertHandler(HandlerWrapper handler)
+ {
+ HandlerWrapper h=this;
+
+ // Skip any injected handlers
+ while (h.getHandler() instanceof HandlerWrapper)
+ {
+ HandlerWrapper wrapper = (HandlerWrapper)h.getHandler();
+ if (wrapper instanceof SessionHandler ||
+ wrapper instanceof SecurityHandler ||
+ wrapper instanceof ServletHandler)
+ break;
+ h=wrapper;
+ }
+
+ h.setHandler(handler);
+ relinkHandlers();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The decorator list used to resource inject new Filters, Servlets and EventListeners
+ */
+ public List<Decorator> getDecorators()
+ {
+ return Collections.unmodifiableList(_decorators);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param decorators The lis of {@link Decorator}s
+ */
+ public void setDecorators(List<Decorator> decorators)
+ {
+ _decorators.clear();
+ _decorators.addAll(decorators);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param decorator The decorator to add
+ */
+ public void addDecorator(Decorator decorator)
+ {
+ _decorators.add(decorator);
+ }
+
+ /* ------------------------------------------------------------ */
+ void destroyServlet(Servlet servlet)
+ {
+ for (Decorator decorator : _decorators)
+ decorator.destroy(servlet);
+ }
+
+ /* ------------------------------------------------------------ */
+ void destroyFilter(Filter filter)
+ {
+ for (Decorator decorator : _decorators)
+ decorator.destroy(filter);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static class JspPropertyGroup implements JspPropertyGroupDescriptor
+ {
+ private List<String> _urlPatterns = new ArrayList<String>();
+ private String _elIgnored;
+ private String _pageEncoding;
+ private String _scriptingInvalid;
+ private String _isXml;
+ private List<String> _includePreludes = new ArrayList<String>();
+ private List<String> _includeCodas = new ArrayList<String>();
+ private String _deferredSyntaxAllowedAsLiteral;
+ private String _trimDirectiveWhitespaces;
+ private String _defaultContentType;
+ private String _buffer;
+ private String _errorOnUndeclaredNamespace;
+
+
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getUrlPatterns()
+ */
+ public Collection<String> getUrlPatterns()
+ {
+ return new ArrayList<String>(_urlPatterns); // spec says must be a copy
+ }
+
+ public void addUrlPattern (String s)
+ {
+ if (!_urlPatterns.contains(s))
+ _urlPatterns.add(s);
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getElIgnored()
+ */
+ public String getElIgnored()
+ {
+ return _elIgnored;
+ }
+
+ public void setElIgnored (String s)
+ {
+ _elIgnored = s;
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getPageEncoding()
+ */
+ public String getPageEncoding()
+ {
+ return _pageEncoding;
+ }
+
+ public void setPageEncoding(String pageEncoding)
+ {
+ _pageEncoding = pageEncoding;
+ }
+
+ public void setScriptingInvalid(String scriptingInvalid)
+ {
+ _scriptingInvalid = scriptingInvalid;
+ }
+
+ public void setIsXml(String isXml)
+ {
+ _isXml = isXml;
+ }
+
+ public void setDeferredSyntaxAllowedAsLiteral(String deferredSyntaxAllowedAsLiteral)
+ {
+ _deferredSyntaxAllowedAsLiteral = deferredSyntaxAllowedAsLiteral;
+ }
+
+ public void setTrimDirectiveWhitespaces(String trimDirectiveWhitespaces)
+ {
+ _trimDirectiveWhitespaces = trimDirectiveWhitespaces;
+ }
+
+ public void setDefaultContentType(String defaultContentType)
+ {
+ _defaultContentType = defaultContentType;
+ }
+
+ public void setBuffer(String buffer)
+ {
+ _buffer = buffer;
+ }
+
+ public void setErrorOnUndeclaredNamespace(String errorOnUndeclaredNamespace)
+ {
+ _errorOnUndeclaredNamespace = errorOnUndeclaredNamespace;
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getScriptingInvalid()
+ */
+ public String getScriptingInvalid()
+ {
+ return _scriptingInvalid;
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIsXml()
+ */
+ public String getIsXml()
+ {
+ return _isXml;
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludePreludes()
+ */
+ public Collection<String> getIncludePreludes()
+ {
+ return new ArrayList<String>(_includePreludes); //must be a copy
+ }
+
+ public void addIncludePrelude(String prelude)
+ {
+ if (!_includePreludes.contains(prelude))
+ _includePreludes.add(prelude);
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludeCodas()
+ */
+ public Collection<String> getIncludeCodas()
+ {
+ return new ArrayList<String>(_includeCodas); //must be a copy
+ }
+
+ public void addIncludeCoda (String coda)
+ {
+ if (!_includeCodas.contains(coda))
+ _includeCodas.add(coda);
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDeferredSyntaxAllowedAsLiteral()
+ */
+ public String getDeferredSyntaxAllowedAsLiteral()
+ {
+ return _deferredSyntaxAllowedAsLiteral;
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getTrimDirectiveWhitespaces()
+ */
+ public String getTrimDirectiveWhitespaces()
+ {
+ return _trimDirectiveWhitespaces;
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDefaultContentType()
+ */
+ public String getDefaultContentType()
+ {
+ return _defaultContentType;
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getBuffer()
+ */
+ public String getBuffer()
+ {
+ return _buffer;
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getErrorOnUndeclaredNamespace()
+ */
+ public String getErrorOnUndeclaredNamespace()
+ {
+ return _errorOnUndeclaredNamespace;
+ }
+
+ public String toString ()
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append("JspPropertyGroupDescriptor:");
+ sb.append(" el-ignored="+_elIgnored);
+ sb.append(" is-xml="+_isXml);
+ sb.append(" page-encoding="+_pageEncoding);
+ sb.append(" scripting-invalid="+_scriptingInvalid);
+ sb.append(" deferred-syntax-allowed-as-literal="+_deferredSyntaxAllowedAsLiteral);
+ sb.append(" trim-directive-whitespaces"+_trimDirectiveWhitespaces);
+ sb.append(" default-content-type="+_defaultContentType);
+ sb.append(" buffer="+_buffer);
+ sb.append(" error-on-undeclared-namespace="+_errorOnUndeclaredNamespace);
+ for (String prelude:_includePreludes)
+ sb.append(" include-prelude="+prelude);
+ for (String coda:_includeCodas)
+ sb.append(" include-coda="+coda);
+ return sb.toString();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static class TagLib implements TaglibDescriptor
+ {
+ private String _uri;
+ private String _location;
+
+ /**
+ * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibURI()
+ */
+ public String getTaglibURI()
+ {
+ return _uri;
+ }
+
+ public void setTaglibURI(String uri)
+ {
+ _uri = uri;
+ }
+
+ /**
+ * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibLocation()
+ */
+ public String getTaglibLocation()
+ {
+ return _location;
+ }
+
+ public void setTaglibLocation(String location)
+ {
+ _location = location;
+ }
+
+ public String toString()
+ {
+ return ("TagLibDescriptor: taglib-uri="+_uri+" location="+_location);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static class JspConfig implements JspConfigDescriptor
+ {
+ private List<TaglibDescriptor> _taglibs = new ArrayList<TaglibDescriptor>();
+ private List<JspPropertyGroupDescriptor> _jspPropertyGroups = new ArrayList<JspPropertyGroupDescriptor>();
+
+ public JspConfig() {}
+
+ /**
+ * @see javax.servlet.descriptor.JspConfigDescriptor#getTaglibs()
+ */
+ public Collection<TaglibDescriptor> getTaglibs()
+ {
+ return new ArrayList<TaglibDescriptor>(_taglibs);
+ }
+
+ public void addTaglibDescriptor (TaglibDescriptor d)
+ {
+ _taglibs.add(d);
+ }
+
+ /**
+ * @see javax.servlet.descriptor.JspConfigDescriptor#getJspPropertyGroups()
+ */
+ public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups()
+ {
+ return new ArrayList<JspPropertyGroupDescriptor>(_jspPropertyGroups);
+ }
+
+ public void addJspPropertyGroup(JspPropertyGroupDescriptor g)
+ {
+ _jspPropertyGroups.add(g);
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append("JspConfigDescriptor: \n");
+ for (TaglibDescriptor taglib:_taglibs)
+ sb.append(taglib+"\n");
+ for (JspPropertyGroupDescriptor jpg:_jspPropertyGroups)
+ sb.append(jpg+"\n");
+ return sb.toString();
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public class Context extends ContextHandler.Context
+ {
+ /* ------------------------------------------------------------ */
+ /*
+ * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+ */
+ @Override
+ public RequestDispatcher getNamedDispatcher(String name)
+ {
+ ContextHandler context=org.eclipse.jetty.servlet.ServletContextHandler.this;
+ if (_servletHandler==null)
+ return null;
+ ServletHolder holder = _servletHandler.getServlet(name);
+ if (holder==null || !holder.isEnabled())
+ return null;
+ return new Dispatcher(context, name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @since servlet-api-3.0
+ */
+ @Override
+ public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
+ {
+ if (isStarted())
+ throw new IllegalStateException();
+
+ if (filterName == null || "".equals(filterName.trim()))
+ throw new IllegalStateException("Missing filter name");
+
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+ FilterHolder holder = handler.getFilter(filterName);
+ if (holder == null)
+ {
+ //new filter
+ holder = handler.newFilterHolder(Source.JAVAX_API);
+ holder.setName(filterName);
+ holder.setHeldClass(filterClass);
+ handler.addFilter(holder);
+ return holder.getRegistration();
+ }
+ if (holder.getClassName()==null && holder.getHeldClass()==null)
+ {
+ //preliminary filter registration completion
+ holder.setHeldClass(filterClass);
+ return holder.getRegistration();
+ }
+ else
+ return null; //existing filter
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @since servlet-api-3.0
+ */
+ @Override
+ public FilterRegistration.Dynamic addFilter(String filterName, String className)
+ {
+ if (isStarted())
+ throw new IllegalStateException();
+
+ if (filterName == null || "".equals(filterName.trim()))
+ throw new IllegalStateException("Missing filter name");
+
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+ FilterHolder holder = handler.getFilter(filterName);
+ if (holder == null)
+ {
+ //new filter
+ holder = handler.newFilterHolder(Source.JAVAX_API);
+ holder.setName(filterName);
+ holder.setClassName(className);
+ handler.addFilter(holder);
+ return holder.getRegistration();
+ }
+ if (holder.getClassName()==null && holder.getHeldClass()==null)
+ {
+ //preliminary filter registration completion
+ holder.setClassName(className);
+ return holder.getRegistration();
+ }
+ else
+ return null; //existing filter
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @since servlet-api-3.0
+ */
+ @Override
+ public FilterRegistration.Dynamic addFilter(String filterName, Filter filter)
+ {
+ if (isStarted())
+ throw new IllegalStateException();
+
+ if (filterName == null || "".equals(filterName.trim()))
+ throw new IllegalStateException("Missing filter name");
+
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+ FilterHolder holder = handler.getFilter(filterName);
+ if (holder == null)
+ {
+ //new filter
+ holder = handler.newFilterHolder(Source.JAVAX_API);
+ holder.setName(filterName);
+ holder.setFilter(filter);
+ handler.addFilter(holder);
+ return holder.getRegistration();
+ }
+
+ if (holder.getClassName()==null && holder.getHeldClass()==null)
+ {
+ //preliminary filter registration completion
+ holder.setFilter(filter);
+ return holder.getRegistration();
+ }
+ else
+ return null; //existing filter
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @since servlet-api-3.0
+ */
+ @Override
+ public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
+ {
+ if (!isStarting())
+ throw new IllegalStateException();
+
+ if (servletName == null || "".equals(servletName.trim()))
+ throw new IllegalStateException("Missing servlet name");
+
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+ ServletHolder holder = handler.getServlet(servletName);
+ if (holder == null)
+ {
+ //new servlet
+ holder = handler.newServletHolder(Source.JAVAX_API);
+ holder.setName(servletName);
+ holder.setHeldClass(servletClass);
+ handler.addServlet(holder);
+ return dynamicHolderAdded(holder);
+ }
+
+ //complete a partial registration
+ if (holder.getClassName()==null && holder.getHeldClass()==null)
+ {
+ holder.setHeldClass(servletClass);
+ return holder.getRegistration();
+ }
+ else
+ return null; //existing completed registration for servlet name
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @since servlet-api-3.0
+ */
+ @Override
+ public ServletRegistration.Dynamic addServlet(String servletName, String className)
+ {
+ if (!isStarting())
+ throw new IllegalStateException();
+
+ if (servletName == null || "".equals(servletName.trim()))
+ throw new IllegalStateException("Missing servlet name");
+
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+
+ final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+ ServletHolder holder = handler.getServlet(servletName);
+ if (holder == null)
+ {
+ //new servlet
+ holder = handler.newServletHolder(Source.JAVAX_API);
+ holder.setName(servletName);
+ holder.setClassName(className);
+ handler.addServlet(holder);
+ return dynamicHolderAdded(holder);
+ }
+
+ //complete a partial registration
+ if (holder.getClassName()==null && holder.getHeldClass()==null)
+ {
+ holder.setClassName(className);
+ return holder.getRegistration();
+ }
+ else
+ return null; //existing completed registration for servlet name
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @since servlet-api-3.0
+ */
+ @Override
+ public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
+ {
+ if (!isStarting())
+ throw new IllegalStateException();
+
+ if (servletName == null || "".equals(servletName.trim()))
+ throw new IllegalStateException("Missing servlet name");
+
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+ ServletHolder holder = handler.getServlet(servletName);
+ if (holder == null)
+ {
+ holder = handler.newServletHolder(Source.JAVAX_API);
+ holder.setName(servletName);
+ holder.setServlet(servlet);
+ handler.addServlet(holder);
+ return dynamicHolderAdded(holder);
+ }
+
+ //complete a partial registration
+ if (holder.getClassName()==null && holder.getHeldClass()==null)
+ {
+ holder.setServlet(servlet);
+ return holder.getRegistration();
+ }
+ else
+ return null; //existing completed registration for servlet name
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean setInitParameter(String name, String value)
+ {
+ if (!isStarting())
+ throw new IllegalStateException();
+
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ return super.setInitParameter(name,value);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+ {
+ try
+ {
+ T f = createInstance(c);
+ for (int i=_decorators.size()-1; i>=0; i--)
+ {
+ Decorator decorator = _decorators.get(i);
+ f=decorator.decorate(f);
+ }
+ return f;
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+ {
+ try
+ {
+ T s = createInstance(c);
+ for (int i=_decorators.size()-1; i>=0; i--)
+ {
+ Decorator decorator = _decorators.get(i);
+ s=decorator.decorate(s);
+ }
+ return s;
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+
+ @Override
+ public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+ {
+ if (_sessionHandler!=null)
+ return _sessionHandler.getSessionManager().getDefaultSessionTrackingModes();
+ return null;
+ }
+
+ @Override
+ public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+ {
+ if (_sessionHandler!=null)
+ return _sessionHandler.getSessionManager().getEffectiveSessionTrackingModes();
+ return null;
+ }
+
+ @Override
+ public FilterRegistration getFilterRegistration(String filterName)
+ {
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ final FilterHolder holder=ServletContextHandler.this.getServletHandler().getFilter(filterName);
+ return (holder==null)?null:holder.getRegistration();
+ }
+
+ @Override
+ public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+ {
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ HashMap<String, FilterRegistration> registrations = new HashMap<String, FilterRegistration>();
+ ServletHandler handler=ServletContextHandler.this.getServletHandler();
+ FilterHolder[] holders=handler.getFilters();
+ if (holders!=null)
+ {
+ for (FilterHolder holder : holders)
+ registrations.put(holder.getName(),holder.getRegistration());
+ }
+ return registrations;
+ }
+
+ @Override
+ public ServletRegistration getServletRegistration(String servletName)
+ {
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ final ServletHolder holder=ServletContextHandler.this.getServletHandler().getServlet(servletName);
+ return (holder==null)?null:holder.getRegistration();
+ }
+
+ @Override
+ public Map<String, ? extends ServletRegistration> getServletRegistrations()
+ {
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ HashMap<String, ServletRegistration> registrations = new HashMap<String, ServletRegistration>();
+ ServletHandler handler=ServletContextHandler.this.getServletHandler();
+ ServletHolder[] holders=handler.getServlets();
+ if (holders!=null)
+ {
+ for (ServletHolder holder : holders)
+ registrations.put(holder.getName(),holder.getRegistration());
+ }
+ return registrations;
+ }
+
+ @Override
+ public SessionCookieConfig getSessionCookieConfig()
+ {
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+ if (_sessionHandler!=null)
+ return _sessionHandler.getSessionManager().getSessionCookieConfig();
+ return null;
+ }
+
+ @Override
+ public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+ {
+ if (!isStarting())
+ throw new IllegalStateException();
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+
+
+ if (_sessionHandler!=null)
+ _sessionHandler.getSessionManager().setSessionTrackingModes(sessionTrackingModes);
+ }
+
+ @Override
+ public void addListener(String className)
+ {
+ if (!isStarting())
+ throw new IllegalStateException();
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+ super.addListener(className);
+ }
+
+ @Override
+ public <T extends EventListener> void addListener(T t)
+ {
+ if (!isStarting())
+ throw new IllegalStateException();
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+ super.addListener(t);
+ ListenerHolder holder = getServletHandler().newListenerHolder(Source.JAVAX_API);
+ holder.setListener(t);
+ getServletHandler().addListener(holder);
+ }
+
+ @Override
+ public void addListener(Class<? extends EventListener> listenerClass)
+ {
+ if (!isStarting())
+ throw new IllegalStateException();
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+ super.addListener(listenerClass);
+ }
+
+ @Override
+ public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+ {
+ try
+ {
+ T l = createInstance(clazz);
+ for (int i=_decorators.size()-1; i>=0; i--)
+ {
+ Decorator decorator = _decorators.get(i);
+ l=decorator.decorate(l);
+ }
+ return l;
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+
+
+ @Override
+ public JspConfigDescriptor getJspConfigDescriptor()
+ {
+ return _jspConfig;
+ }
+
+ @Override
+ public void setJspConfigDescriptor(JspConfigDescriptor d)
+ {
+ _jspConfig = d;
+ }
+
+
+ @Override
+ public void declareRoles(String... roleNames)
+ {
+ if (!isStarting())
+ throw new IllegalStateException();
+ if (!_enabled)
+ throw new UnsupportedOperationException();
+ addRoles(roleNames);
+
+
+ }
+
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /** Interface to decorate loaded classes.
+ */
+ public interface Decorator
+ {
+ <T> T decorate (T o);
+ void destroy (Object o);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.QuietServletException;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.ServletRequestHttpWrapper;
+import org.eclipse.jetty.server.ServletResponseHttpWrapper;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ScopedHandler;
+import org.eclipse.jetty.servlet.BaseHolder.Source;
+import org.eclipse.jetty.util.ArrayUtil;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* --------------------------------------------------------------------- */
+/** Servlet HttpHandler.
+ * This handler maps requests to servlets that implement the
+ * javax.servlet.http.HttpServlet API.
+ * <P>
+ * This handler does not implement the full J2EE features and is intended to
+ * be used directly when a full web application is not required. If a Web application is required,
+ * then this handler should be used as part of a <code>org.eclipse.jetty.webapp.WebAppContext</code>.
+ * <p>
+ * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()}
+ * method must be called manually after start().
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@ManagedObject("Servlet Handler")
+public class ServletHandler extends ScopedHandler
+{
+ private static final Logger LOG = Log.getLogger(ServletHandler.class);
+
+ /* ------------------------------------------------------------ */
+ public static final String __DEFAULT_SERVLET="default";
+
+ /* ------------------------------------------------------------ */
+ private ServletContextHandler _contextHandler;
+ private ServletContext _servletContext;
+ private FilterHolder[] _filters=new FilterHolder[0];
+ private FilterMapping[] _filterMappings;
+ private int _matchBeforeIndex = -1; //index of last programmatic FilterMapping with isMatchAfter=false
+ private int _matchAfterIndex = -1; //index of 1st programmatic FilterMapping with isMatchAfter=true
+ private boolean _filterChainsCached=true;
+ private int _maxFilterChainsCacheSize=512;
+ private boolean _startWithUnavailable=false;
+ private boolean _ensureDefaultServlet=true;
+ private IdentityService _identityService;
+
+ private ServletHolder[] _servlets=new ServletHolder[0];
+ private ServletMapping[] _servletMappings;
+ private Map<String,ServletMapping> _servletPathMappings = new HashMap<String,ServletMapping>();
+
+ private final Map<String,FilterHolder> _filterNameMap= new HashMap<>();
+ private List<FilterMapping> _filterPathMappings;
+ private MultiMap<FilterMapping> _filterNameMappings;
+
+ private final Map<String,ServletHolder> _servletNameMap=new HashMap<>();
+ private PathMap<ServletHolder> _servletPathMap;
+
+ private ListenerHolder[] _listeners=new ListenerHolder[0];
+
+ protected final ConcurrentMap<?, ?> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
+ protected final Queue<?>[] _chainLRU = new Queue[FilterMapping.ALL];
+
+
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ */
+ public ServletHandler()
+ {
+ }
+
+ /* ----------------------------------------------------------------- */
+ @Override
+ protected synchronized void doStart()
+ throws Exception
+ {
+ ContextHandler.Context context=ContextHandler.getCurrentContext();
+ _servletContext=context==null?new ContextHandler.NoContext():context;
+ _contextHandler=(ServletContextHandler)(context==null?null:context.getContextHandler());
+
+ if (_contextHandler!=null)
+ {
+ SecurityHandler security_handler = _contextHandler.getChildHandlerByClass(SecurityHandler.class);
+ if (security_handler!=null)
+ _identityService=security_handler.getIdentityService();
+ }
+
+ updateNameMappings();
+ updateMappings();
+
+ if (getServletMapping("/")==null && _ensureDefaultServlet)
+ {
+ LOG.debug("Adding Default404Servlet to {}",this);
+ addServletWithMapping(Default404Servlet.class,"/");
+ updateMappings();
+ getServletMapping("/").setDefault(true);
+ }
+
+ if(_filterChainsCached)
+ {
+ _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap<String,FilterChain>();
+ _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap<String,FilterChain>();
+ _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap<String,FilterChain>();
+ _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap<String,FilterChain>();
+ _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap<String,FilterChain>();
+
+ _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue<String>();
+ _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue<String>();
+ _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue<String>();
+ _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue<String>();
+ _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue<String>();
+ }
+
+ if (_contextHandler==null)
+ initialize();
+
+ super.doStart();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
+ * default servlet is configured.
+ */
+ public boolean isEnsureDefaultServlet()
+ {
+ return _ensureDefaultServlet;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param ensureDefaultServlet true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other
+ * default servlet is configured.
+ */
+ public void setEnsureDefaultServlet(boolean ensureDefaultServlet)
+ {
+ _ensureDefaultServlet=ensureDefaultServlet;
+ }
+
+ /* ----------------------------------------------------------------- */
+ @Override
+ protected void start(LifeCycle l) throws Exception
+ {
+ //Don't start the whole object tree (ie all the servlet and filter Holders) when
+ //this handler starts. They have a slightly special lifecycle, and should only be
+ //started AFTER the handlers have all started (and the ContextHandler has called
+ //the context listeners).
+ if (!(l instanceof Holder))
+ super.start(l);
+ }
+
+ /* ----------------------------------------------------------------- */
+ @Override
+ protected synchronized void doStop()
+ throws Exception
+ {
+ super.doStop();
+
+ // Stop filters
+ List<FilterHolder> filterHolders = new ArrayList<FilterHolder>();
+ List<FilterMapping> filterMappings = ArrayUtil.asMutableList(_filterMappings);
+ if (_filters!=null)
+ {
+ for (int i=_filters.length; i-->0;)
+ {
+ try
+ {
+ _filters[i].stop();
+ }
+ catch(Exception e)
+ {
+ LOG.warn(Log.EXCEPTION,e);
+ }
+ if (_filters[i].getSource() != Source.EMBEDDED)
+ {
+ //remove all of the mappings that were for non-embedded filters
+ _filterNameMap.remove(_filters[i].getName());
+ //remove any mappings associated with this filter
+ ListIterator<FilterMapping> fmitor = filterMappings.listIterator();
+ while (fmitor.hasNext())
+ {
+ FilterMapping fm = fmitor.next();
+ if (fm.getFilterName().equals(_filters[i].getName()))
+ fmitor.remove();
+ }
+ }
+ else
+ filterHolders.add(_filters[i]); //only retain embedded
+ }
+ }
+
+ //Retain only filters and mappings that were added using jetty api (ie Source.EMBEDDED)
+ FilterHolder[] fhs = (FilterHolder[]) LazyList.toArray(filterHolders, FilterHolder.class);
+ updateBeans(_filters, fhs);
+ _filters = fhs;
+ FilterMapping[] fms = (FilterMapping[]) LazyList.toArray(filterMappings, FilterMapping.class);
+ updateBeans(_filterMappings, fms);
+ _filterMappings = fms;
+
+ _matchAfterIndex = (_filterMappings == null || _filterMappings.length == 0 ? -1 : _filterMappings.length-1);
+ _matchBeforeIndex = -1;
+
+ // Stop servlets
+ List<ServletHolder> servletHolders = new ArrayList<ServletHolder>(); //will be remaining servlets
+ List<ServletMapping> servletMappings = ArrayUtil.asMutableList(_servletMappings); //will be remaining mappings
+ if (_servlets!=null)
+ {
+ for (int i=_servlets.length; i-->0;)
+ {
+ try
+ {
+ _servlets[i].stop();
+ }
+ catch(Exception e)
+ {
+ LOG.warn(Log.EXCEPTION,e);
+ }
+
+ if (_servlets[i].getSource() != Source.EMBEDDED)
+ {
+ //remove from servlet name map
+ _servletNameMap.remove(_servlets[i].getName());
+ //remove any mappings associated with this servlet
+ ListIterator<ServletMapping> smitor = servletMappings.listIterator();
+ while (smitor.hasNext())
+ {
+ ServletMapping sm = smitor.next();
+ if (sm.getServletName().equals(_servlets[i].getName()))
+ smitor.remove();
+ }
+ }
+ else
+ servletHolders.add(_servlets[i]); //only retain embedded
+ }
+ }
+
+ //Retain only Servlets and mappings added via jetty apis (ie Source.EMBEDDED)
+ ServletHolder[] shs = (ServletHolder[]) LazyList.toArray(servletHolders, ServletHolder.class);
+ updateBeans(_servlets, shs);
+ _servlets = shs;
+ ServletMapping[] sms = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class);
+ updateBeans(_servletMappings, sms);
+ _servletMappings = sms;
+
+ //Retain only Listeners added via jetty apis (is Source.EMBEDDED)
+ List<ListenerHolder> listenerHolders = new ArrayList<ListenerHolder>();
+ if (_listeners != null)
+ {
+ for (int i=_listeners.length; i-->0;)
+ {
+ try
+ {
+ _listeners[i].stop();
+ }
+ catch(Exception e)
+ {
+ LOG.warn(Log.EXCEPTION,e);
+ }
+ if (_listeners[i].getSource() == Source.EMBEDDED)
+ listenerHolders.add(_listeners[i]);
+ }
+ }
+ ListenerHolder[] listeners = (ListenerHolder[])LazyList.toArray(listenerHolders, ListenerHolder.class);
+ updateBeans(_listeners, listeners);
+ _listeners = listeners;
+
+ //will be regenerated on next start
+ _filterPathMappings=null;
+ _filterNameMappings=null;
+ _servletPathMap=null;
+ _servletPathMappings=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected IdentityService getIdentityService()
+ {
+ return _identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the contextLog.
+ */
+ public Object getContextLog()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the filterMappings.
+ */
+ @ManagedAttribute(value="filters", readonly=true)
+ public FilterMapping[] getFilterMappings()
+ {
+ return _filterMappings;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get Filters.
+ * @return Array of defined servlets
+ */
+ @ManagedAttribute(value="filters", readonly=true)
+ public FilterHolder[] getFilters()
+ {
+ return _filters;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** ServletHolder matching path.
+ * @param pathInContext Path within _context.
+ * @return PathMap Entries pathspec to ServletHolder
+ */
+ public PathMap.MappedEntry<ServletHolder> getHolderEntry(String pathInContext)
+ {
+ if (_servletPathMap==null)
+ return null;
+ return _servletPathMap.getMatch(pathInContext);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletContext getServletContext()
+ {
+ return _servletContext;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the servletMappings.
+ */
+ @ManagedAttribute(value="mappings of servlets", readonly=true)
+ public ServletMapping[] getServletMappings()
+ {
+ return _servletMappings;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get the ServletMapping matching the path
+ *
+ * @param pathSpec
+ * @return
+ */
+ public ServletMapping getServletMapping(String pathSpec)
+ {
+ if (pathSpec == null || _servletPathMappings == null)
+ return null;
+
+ return _servletPathMappings.get(pathSpec);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get Servlets.
+ * @return Array of defined servlets
+ */
+ @ManagedAttribute(value="servlets", readonly=true)
+ public ServletHolder[] getServlets()
+ {
+ return _servlets;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ServletHolder getServlet(String name)
+ {
+ return _servletNameMap.get(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ // Get the base requests
+ final String old_servlet_path=baseRequest.getServletPath();
+ final String old_path_info=baseRequest.getPathInfo();
+
+ DispatcherType type = baseRequest.getDispatcherType();
+
+ ServletHolder servlet_holder=null;
+ UserIdentity.Scope old_scope=null;
+
+ // find the servlet
+ if (target.startsWith("/"))
+ {
+ // Look for the servlet by path
+ PathMap.MappedEntry<ServletHolder> entry=getHolderEntry(target);
+ if (entry!=null)
+ {
+ servlet_holder=entry.getValue();
+
+ String servlet_path_spec= entry.getKey();
+ String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
+ String path_info=PathMap.pathInfo(servlet_path_spec,target);
+
+ if (DispatcherType.INCLUDE.equals(type))
+ {
+ baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,servlet_path);
+ baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, path_info);
+ }
+ else
+ {
+ baseRequest.setServletPath(servlet_path);
+ baseRequest.setPathInfo(path_info);
+ }
+ }
+ }
+ else
+ {
+ // look for a servlet by name!
+ servlet_holder= _servletNameMap.get(target);
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder);
+
+ try
+ {
+ // Do the filter/handling thang
+ old_scope=baseRequest.getUserIdentityScope();
+ baseRequest.setUserIdentityScope(servlet_holder);
+
+ // start manual inline of nextScope(target,baseRequest,request,response);
+ if (never())
+ nextScope(target,baseRequest,request,response);
+ else if (_nextScope!=null)
+ _nextScope.doScope(target,baseRequest,request, response);
+ else if (_outerScope!=null)
+ _outerScope.doHandle(target,baseRequest,request, response);
+ else
+ doHandle(target,baseRequest,request, response);
+ // end manual inline (pathentic attempt to reduce stack depth)
+ }
+ finally
+ {
+ if (old_scope!=null)
+ baseRequest.setUserIdentityScope(old_scope);
+
+ if (!(DispatcherType.INCLUDE.equals(type)))
+ {
+ baseRequest.setServletPath(old_servlet_path);
+ baseRequest.setPathInfo(old_path_info);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+ */
+ @Override
+ public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ DispatcherType type = baseRequest.getDispatcherType();
+
+ ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope();
+ FilterChain chain=null;
+
+ // find the servlet
+ if (target.startsWith("/"))
+ {
+ if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
+ chain=getFilterChain(baseRequest, target, servlet_holder);
+ }
+ else
+ {
+ if (servlet_holder!=null)
+ {
+ if (_filterMappings!=null && _filterMappings.length>0)
+ {
+ chain=getFilterChain(baseRequest, null,servlet_holder);
+ }
+ }
+ }
+
+ LOG.debug("chain={}",chain);
+ Throwable th=null;
+ try
+ {
+ if (servlet_holder==null)
+ notFound(baseRequest,request, response);
+ else
+ {
+ // unwrap any tunnelling of base Servlet request/responses
+ ServletRequest req = request;
+ if (req instanceof ServletRequestHttpWrapper)
+ req = ((ServletRequestHttpWrapper)req).getRequest();
+ ServletResponse res = response;
+ if (res instanceof ServletResponseHttpWrapper)
+ res = ((ServletResponseHttpWrapper)res).getResponse();
+
+ // Do the filter/handling thang
+ if (chain!=null)
+ chain.doFilter(req, res);
+ else
+ servlet_holder.handle(baseRequest,req,res);
+ }
+ }
+ catch(EofException e)
+ {
+ throw e;
+ }
+ catch(RuntimeIOException e)
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+ {
+ if (e instanceof IOException)
+ throw (IOException)e;
+ if (e instanceof RuntimeException)
+ throw (RuntimeException)e;
+ if (e instanceof ServletException)
+ throw (ServletException)e;
+ }
+
+ // unwrap cause
+ th=e;
+ if (th instanceof ServletException)
+ {
+ if (th instanceof QuietServletException)
+ {
+ LOG.warn(th.toString());
+ LOG.debug(th);
+ }
+ else
+ LOG.warn(th);
+ }
+ else if (th instanceof EofException)
+ {
+ throw (EofException)th;
+ }
+ else
+ {
+ LOG.warn(request.getRequestURI(),th);
+ if (LOG.isDebugEnabled())
+ LOG.debug(request.toString());
+ }
+
+ request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
+ request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th);
+ if (!response.isCommitted())
+ {
+ baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+ if (th instanceof UnavailableException)
+ {
+ UnavailableException ue = (UnavailableException)th;
+ if (ue.isPermanent())
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ else
+ response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ }
+ else
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ else
+ {
+ if (th instanceof IOException)
+ throw (IOException)th;
+ if (th instanceof RuntimeException)
+ throw (RuntimeException)th;
+ if (th instanceof ServletException)
+ throw (ServletException)th;
+ throw new IllegalStateException("response already committed",th);
+ }
+ }
+ catch(Error e)
+ {
+ if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
+ throw e;
+ th=e;
+ if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+ throw e;
+ LOG.warn("Error for "+request.getRequestURI(),e);
+ if(LOG.isDebugEnabled())
+ LOG.debug(request.toString());
+
+ request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
+ request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+ if (!response.isCommitted())
+ {
+ baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+ else
+ LOG.debug("Response already committed for handling ",e);
+ }
+ finally
+ {
+ // Complete async errored requests
+ if (th!=null && request.isAsyncStarted())
+ baseRequest.getHttpChannelState().errorComplete();
+
+ if (servlet_holder!=null)
+ baseRequest.setHandled(true);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
+ {
+ String key=pathInContext==null?servletHolder.getName():pathInContext;
+ int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
+
+ if (_filterChainsCached && _chainCache!=null)
+ {
+ FilterChain chain = (FilterChain)_chainCache[dispatch].get(key);
+ if (chain!=null)
+ return chain;
+ }
+
+ // Build list of filters (list of FilterHolder objects)
+ List<FilterHolder> filters = new ArrayList<>();
+
+ // Path filters
+ if (pathInContext!=null && _filterPathMappings!=null)
+ {
+ for (FilterMapping filterPathMapping : _filterPathMappings)
+ {
+ if (filterPathMapping.appliesTo(pathInContext, dispatch))
+ filters.add(filterPathMapping.getFilterHolder());
+ }
+ }
+
+ // Servlet name filters
+ if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0)
+ {
+ // Servlet name filters
+ if (_filterNameMappings.size() > 0)
+ {
+ Object o= _filterNameMappings.get(servletHolder.getName());
+
+ for (int i=0; i<LazyList.size(o);i++)
+ {
+ FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
+ if (mapping.appliesTo(dispatch))
+ filters.add(mapping.getFilterHolder());
+ }
+
+ o= _filterNameMappings.get("*");
+ for (int i=0; i<LazyList.size(o);i++)
+ {
+ FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
+ if (mapping.appliesTo(dispatch))
+ filters.add(mapping.getFilterHolder());
+ }
+ }
+ }
+
+ if (filters.isEmpty())
+ return null;
+
+
+ FilterChain chain = null;
+ if (_filterChainsCached)
+ {
+ if (filters.size() > 0)
+ chain= new CachedChain(filters, servletHolder);
+
+ final Map<String,FilterChain> cache=(Map<String, FilterChain>)_chainCache[dispatch];
+ final Queue<String> lru=(Queue<String>)_chainLRU[dispatch];
+
+ // Do we have too many cached chains?
+ while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
+ {
+ // The LRU list is not atomic with the cache map, so be prepared to invalidate if
+ // a key is not found to delete.
+ // Delete by LRU (where U==created)
+ String k=lru.poll();
+ if (k==null)
+ {
+ cache.clear();
+ break;
+ }
+ cache.remove(k);
+ }
+
+ cache.put(key,chain);
+ lru.add(key);
+ }
+ else if (filters.size() > 0)
+ chain = new Chain(baseRequest,filters, servletHolder);
+
+ return chain;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void invalidateChainsCache()
+ {
+ if (_chainLRU[FilterMapping.REQUEST]!=null)
+ {
+ _chainLRU[FilterMapping.REQUEST].clear();
+ _chainLRU[FilterMapping.FORWARD].clear();
+ _chainLRU[FilterMapping.INCLUDE].clear();
+ _chainLRU[FilterMapping.ERROR].clear();
+ _chainLRU[FilterMapping.ASYNC].clear();
+
+ _chainCache[FilterMapping.REQUEST].clear();
+ _chainCache[FilterMapping.FORWARD].clear();
+ _chainCache[FilterMapping.INCLUDE].clear();
+ _chainCache[FilterMapping.ERROR].clear();
+ _chainCache[FilterMapping.ASYNC].clear();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the handler is started and there are no unavailable servlets
+ */
+ public boolean isAvailable()
+ {
+ if (!isStarted())
+ return false;
+ ServletHolder[] holders = getServlets();
+ for (ServletHolder holder : holders)
+ {
+ if (holder != null && !holder.isAvailable())
+ return false;
+ }
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param start True if this handler will start with unavailable servlets
+ */
+ public void setStartWithUnavailable(boolean start)
+ {
+ _startWithUnavailable=start;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if this handler will start with unavailable servlets
+ */
+ public boolean isStartWithUnavailable()
+ {
+ return _startWithUnavailable;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /** Initialize filters and load-on-startup servlets.
+ */
+ public void initialize()
+ throws Exception
+ {
+ MultiException mx = new MultiException();
+
+ //start filter holders now
+ if (_filters != null)
+ {
+ for (FilterHolder f: _filters)
+ {
+ try
+ {
+ f.start();
+ f.initialize();
+ }
+ catch (Exception e)
+ {
+ mx.add(e);
+ }
+ }
+ }
+
+ // Sort and Initialize servlets
+ if (_servlets!=null)
+ {
+ ServletHolder[] servlets = _servlets.clone();
+ Arrays.sort(servlets);
+ for (ServletHolder servlet : servlets)
+ {
+ try
+ {
+ /* if (servlet.getClassName() == null && servlet.getForcedPath() != null)
+ {
+ ServletHolder forced_holder = _servletPathMap.match(servlet.getForcedPath());
+ if (forced_holder == null || forced_holder.getClassName() == null)
+ {
+ mx.add(new IllegalStateException("No forced path servlet for " + servlet.getForcedPath()));
+ continue;
+ }
+ System.err.println("ServletHandler setting forced path classname to "+forced_holder.getClassName()+ " for "+servlet.getForcedPath());
+ servlet.setClassName(forced_holder.getClassName());
+ }*/
+
+ servlet.start();
+ servlet.initialize();
+ }
+ catch (Throwable e)
+ {
+ LOG.debug(Log.EXCEPTION, e);
+ mx.add(e);
+ }
+ }
+ }
+
+ //any other beans
+ for (Holder<?> h: getBeans(Holder.class))
+ {
+ try
+ {
+ if (!h.isStarted())
+ {
+ h.start();
+ h.initialize();
+ }
+ }
+ catch (Exception e)
+ {
+ mx.add(e);
+ }
+ }
+
+ mx.ifExceptionThrow();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the filterChainsCached.
+ */
+ public boolean isFilterChainsCached()
+ {
+ return _filterChainsCached;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add a holder for a listener
+ * @param filter
+ */
+ public void addListener (ListenerHolder listener)
+ {
+ if (listener != null)
+ setListeners(ArrayUtil.addToArray(getListeners(), listener, ListenerHolder.class));
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public ListenerHolder[] getListeners()
+ {
+ return _listeners;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setListeners(ListenerHolder[] listeners)
+ {
+ if (listeners!=null)
+ for (ListenerHolder holder:listeners)
+ holder.setServletHandler(this);
+
+ updateBeans(_listeners,listeners);
+ _listeners = listeners;
+ }
+
+ /* ------------------------------------------------------------ */
+ public ListenerHolder newListenerHolder(Holder.Source source)
+ {
+ return new ListenerHolder(source);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * see also newServletHolder(Class)
+ */
+ public ServletHolder newServletHolder(Holder.Source source)
+ {
+ return new ServletHolder(source);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a servlet.
+ * @return The servlet holder.
+ */
+ public ServletHolder addServletWithMapping (String className,String pathSpec)
+ {
+ ServletHolder holder = newServletHolder(Source.EMBEDDED);
+ holder.setClassName(className);
+ addServletWithMapping(holder,pathSpec);
+ return holder;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** conveniance method to add a servlet.
+ * @return The servlet holder.
+ */
+ public ServletHolder addServletWithMapping (Class<? extends Servlet> servlet,String pathSpec)
+ {
+ ServletHolder holder = newServletHolder(Source.EMBEDDED);
+ holder.setHeldClass(servlet);
+ addServletWithMapping(holder,pathSpec);
+
+ return holder;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** conveniance method to add a servlet.
+ * @param servlet servlet holder to add
+ * @param pathSpec servlet mappings for the servletHolder
+ */
+ public void addServletWithMapping (ServletHolder servlet,String pathSpec)
+ {
+ ServletHolder[] holders=getServlets();
+ if (holders!=null)
+ holders = holders.clone();
+
+ try
+ {
+ setServlets(ArrayUtil.addToArray(holders, servlet, ServletHolder.class));
+
+ ServletMapping mapping = new ServletMapping();
+ mapping.setServletName(servlet.getName());
+ mapping.setPathSpec(pathSpec);
+ setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
+ }
+ catch (Exception e)
+ {
+ setServlets(holders);
+ if (e instanceof RuntimeException)
+ throw (RuntimeException)e;
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**Convenience method to add a pre-constructed ServletHolder.
+ * @param holder
+ */
+ public void addServlet(ServletHolder holder)
+ {
+ setServlets(ArrayUtil.addToArray(getServlets(), holder, ServletHolder.class));
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a pre-constructed ServletMapping.
+ * @param mapping
+ */
+ public void addServletMapping (ServletMapping mapping)
+ {
+ setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
+ }
+
+ /* ------------------------------------------------------------ */
+ public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement)
+ {
+ if (_contextHandler != null)
+ {
+ return _contextHandler.setServletSecurity(registration, servletSecurityElement);
+ }
+ return Collections.emptySet();
+ }
+
+ /* ------------------------------------------------------------ */
+ public FilterHolder newFilterHolder(Holder.Source source)
+ {
+ return new FilterHolder(source);
+ }
+
+ /* ------------------------------------------------------------ */
+ public FilterHolder getFilter(String name)
+ {
+ return _filterNameMap.get(name);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a filter.
+ * @param filter class of filter to create
+ * @param pathSpec filter mappings for filter
+ * @param dispatches see {@link FilterMapping#setDispatches(int)}
+ * @return The filter holder.
+ */
+ public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,EnumSet<DispatcherType> dispatches)
+ {
+ FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+ holder.setHeldClass(filter);
+ addFilterWithMapping(holder,pathSpec,dispatches);
+
+ return holder;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a filter.
+ * @param className of filter
+ * @param pathSpec filter mappings for filter
+ * @param dispatches see {@link FilterMapping#setDispatches(int)}
+ * @return The filter holder.
+ */
+ public FilterHolder addFilterWithMapping (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
+ {
+ FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+ holder.setClassName(className);
+
+ addFilterWithMapping(holder,pathSpec,dispatches);
+ return holder;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a filter.
+ * @param holder filter holder to add
+ * @param pathSpec filter mappings for filter
+ * @param dispatches see {@link FilterMapping#setDispatches(int)}
+ */
+ public void addFilterWithMapping (FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
+ {
+ FilterHolder[] holders = getFilters();
+ if (holders!=null)
+ holders = holders.clone();
+
+ try
+ {
+ setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
+
+ FilterMapping mapping = new FilterMapping();
+ mapping.setFilterName(holder.getName());
+ mapping.setPathSpec(pathSpec);
+ mapping.setDispatcherTypes(dispatches);
+ addFilterMapping(mapping);
+
+ }
+ catch (RuntimeException e)
+ {
+ setFilters(holders);
+ throw e;
+ }
+ catch (Error e)
+ {
+ setFilters(holders);
+ throw e;
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a filter.
+ * @param filter class of filter to create
+ * @param pathSpec filter mappings for filter
+ * @param dispatches see {@link FilterMapping#setDispatches(int)}
+ * @return The filter holder.
+ */
+ public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,int dispatches)
+ {
+ FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+ holder.setHeldClass(filter);
+ addFilterWithMapping(holder,pathSpec,dispatches);
+
+ return holder;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a filter.
+ * @param className of filter
+ * @param pathSpec filter mappings for filter
+ * @param dispatches see {@link FilterMapping#setDispatches(int)}
+ * @return The filter holder.
+ */
+ public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches)
+ {
+ FilterHolder holder = newFilterHolder(Source.EMBEDDED);
+ holder.setClassName(className);
+
+ addFilterWithMapping(holder,pathSpec,dispatches);
+ return holder;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a filter.
+ * @param holder filter holder to add
+ * @param pathSpec filter mappings for filter
+ * @param dispatches see {@link FilterMapping#setDispatches(int)}
+ */
+ public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches)
+ {
+ FilterHolder[] holders = getFilters();
+ if (holders!=null)
+ holders = holders.clone();
+
+ try
+ {
+ setFilters(ArrayUtil.addToArray(holders, holder, FilterHolder.class));
+
+ FilterMapping mapping = new FilterMapping();
+ mapping.setFilterName(holder.getName());
+ mapping.setPathSpec(pathSpec);
+ mapping.setDispatches(dispatches);
+ addFilterMapping(mapping);
+ }
+ catch (RuntimeException e)
+ {
+ setFilters(holders);
+ throw e;
+ }
+ catch (Error e)
+ {
+ setFilters(holders);
+ throw e;
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a filter with a mapping
+ * @param className
+ * @param pathSpec
+ * @param dispatches
+ * @return the filter holder created
+ * @deprecated use {@link #addFilterWithMapping(Class, String, EnumSet)} instead
+ */
+ public FilterHolder addFilter (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
+ {
+ return addFilterWithMapping(className, pathSpec, dispatches);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * convenience method to add a filter and mapping
+ * @param filter
+ * @param filterMapping
+ */
+ public void addFilter (FilterHolder filter, FilterMapping filterMapping)
+ {
+ if (filter != null)
+ setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
+ if (filterMapping != null)
+ addFilterMapping(filterMapping);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a preconstructed FilterHolder
+ * @param filter
+ */
+ public void addFilter (FilterHolder filter)
+ {
+ if (filter != null)
+ setFilters(ArrayUtil.addToArray(getFilters(), filter, FilterHolder.class));
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a preconstructed FilterMapping
+ * @param mapping
+ */
+ public void addFilterMapping (FilterMapping mapping)
+ {
+ if (mapping != null)
+ {
+ Source source = (mapping.getFilterHolder()==null?null:mapping.getFilterHolder().getSource());
+ FilterMapping[] mappings =getFilterMappings();
+ if (mappings==null || mappings.length==0)
+ {
+ setFilterMappings(insertFilterMapping(mapping,0,false));
+ if (source != null && source == Source.JAVAX_API)
+ _matchAfterIndex = 0;
+ }
+ else
+ {
+ //there are existing entries. If this is a programmatic filtermapping, it is added at the end of the list.
+ //If this is a normal filtermapping, it is inserted after all the other filtermappings (matchBefores and normals),
+ //but before the first matchAfter filtermapping.
+ if (source != null && Source.JAVAX_API == source)
+ {
+ setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false));
+ if (_matchAfterIndex < 0)
+ _matchAfterIndex = getFilterMappings().length-1;
+ }
+ else
+ {
+ //insert non-programmatic filter mappings before any matchAfters, if any
+ if (_matchAfterIndex < 0)
+ setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false));
+ else
+ {
+ FilterMapping[] new_mappings = insertFilterMapping(mapping, _matchAfterIndex, true);
+ ++_matchAfterIndex;
+ setFilterMappings(new_mappings);
+ }
+ }
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Convenience method to add a preconstructed FilterMapping
+ * @param mapping
+ */
+ public void prependFilterMapping (FilterMapping mapping)
+ {
+ if (mapping != null)
+ {
+ Source source = mapping.getFilterHolder().getSource();
+
+ FilterMapping[] mappings = getFilterMappings();
+ if (mappings==null || mappings.length==0)
+ {
+ setFilterMappings(insertFilterMapping(mapping, 0, false));
+ if (source != null && Source.JAVAX_API == source)
+ _matchBeforeIndex = 0;
+ }
+ else
+ {
+ if (source != null && Source.JAVAX_API == source)
+ {
+ //programmatically defined filter mappings are prepended to mapping list in the order
+ //in which they were defined. In other words, insert this mapping at the tail of the
+ //programmatically prepended filter mappings, BEFORE the first web.xml defined filter mapping.
+
+ if (_matchBeforeIndex < 0)
+ {
+ //no programmatically defined prepended filter mappings yet, prepend this one
+ _matchBeforeIndex = 0;
+ FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true);
+ setFilterMappings(new_mappings);
+ }
+ else
+ {
+ FilterMapping[] new_mappings = insertFilterMapping(mapping,_matchBeforeIndex, false);
+ ++_matchBeforeIndex;
+ setFilterMappings(new_mappings);
+ }
+ }
+ else
+ {
+ //non programmatically defined, just prepend to list
+ FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true);
+ setFilterMappings(new_mappings);
+ }
+
+ //adjust matchAfterIndex ptr to take account of the mapping we just prepended
+ if (_matchAfterIndex >= 0)
+ ++_matchAfterIndex;
+ }
+ }
+ }
+
+
+
+ /**
+ * Insert a filtermapping in the list
+ * @param mapping the FilterMapping to add
+ * @param pos the position in the existing arry at which to add it
+ * @param before if true, insert before pos, if false insert after it
+ * @return
+ */
+ protected FilterMapping[] insertFilterMapping (FilterMapping mapping, int pos, boolean before)
+ {
+ if (pos < 0)
+ throw new IllegalArgumentException("FilterMapping insertion pos < 0");
+ FilterMapping[] mappings = getFilterMappings();
+
+ if (mappings==null || mappings.length==0)
+ {
+ return new FilterMapping[] {mapping};
+ }
+ FilterMapping[] new_mappings = new FilterMapping[mappings.length+1];
+
+
+ if (before)
+ {
+ //copy existing filter mappings up to but not including the pos
+ System.arraycopy(mappings,0,new_mappings,0,pos);
+
+ //add in the new mapping
+ new_mappings[pos] = mapping;
+
+ //copy the old pos mapping and any remaining existing mappings
+ System.arraycopy(mappings,pos,new_mappings,pos+1, mappings.length-pos);
+
+ }
+ else
+ {
+ //copy existing filter mappings up to and including the pos
+ System.arraycopy(mappings,0,new_mappings,0,pos+1);
+ //add in the new mapping after the pos
+ new_mappings[pos+1] = mapping;
+
+ //copy the remaining existing mappings
+ if (mappings.length > pos+1)
+ System.arraycopy(mappings,pos+1,new_mappings,pos+2, mappings.length-(pos+1));
+ }
+ return new_mappings;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ protected synchronized void updateNameMappings()
+ {
+ // update filter name map
+ _filterNameMap.clear();
+ if (_filters!=null)
+ {
+ for (FilterHolder filter : _filters)
+ {
+ _filterNameMap.put(filter.getName(), filter);
+ filter.setServletHandler(this);
+ }
+ }
+
+ // Map servlet names to holders
+ _servletNameMap.clear();
+ if (_servlets!=null)
+ {
+ // update the maps
+ for (ServletHolder servlet : _servlets)
+ {
+ _servletNameMap.put(servlet.getName(), servlet);
+ servlet.setServletHandler(this);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected synchronized void updateMappings()
+ {
+ // update filter mappings
+ if (_filterMappings==null)
+ {
+ _filterPathMappings=null;
+ _filterNameMappings=null;
+ }
+ else
+ {
+ _filterPathMappings=new ArrayList<>();
+ _filterNameMappings=new MultiMap<FilterMapping>();
+ for (FilterMapping filtermapping : _filterMappings)
+ {
+ FilterHolder filter_holder = _filterNameMap.get(filtermapping.getFilterName());
+ if (filter_holder == null)
+ throw new IllegalStateException("No filter named " + filtermapping.getFilterName());
+ filtermapping.setFilterHolder(filter_holder);
+ if (filtermapping.getPathSpecs() != null)
+ _filterPathMappings.add(filtermapping);
+
+ if (filtermapping.getServletNames() != null)
+ {
+ String[] names = filtermapping.getServletNames();
+ for (String name : names)
+ {
+ if (name != null)
+ _filterNameMappings.add(name, filtermapping);
+ }
+ }
+ }
+ }
+
+ // Map servlet paths to holders
+ if (_servletMappings==null || _servletNameMap==null)
+ {
+ _servletPathMap=null;
+ }
+ else
+ {
+ PathMap<ServletHolder> pm = new PathMap<>();
+ Map<String,ServletMapping> servletPathMappings = new HashMap<String,ServletMapping>();
+
+ //create a map of paths to set of ServletMappings that define that mapping
+ HashMap<String, Set<ServletMapping>> sms = new HashMap<String, Set<ServletMapping>>();
+ for (ServletMapping servletMapping : _servletMappings)
+ {
+ String[] pathSpecs = servletMapping.getPathSpecs();
+ if (pathSpecs != null)
+ {
+ for (String pathSpec : pathSpecs)
+ {
+ Set<ServletMapping> mappings = sms.get(pathSpec);
+ if (mappings == null)
+ {
+ mappings = new HashSet<ServletMapping>();
+ sms.put(pathSpec, mappings);
+ }
+ mappings.add(servletMapping);
+ }
+ }
+ }
+
+ //evaluate path to servlet map based on servlet mappings
+ for (String pathSpec : sms.keySet())
+ {
+ //for each path, look at the mappings where it is referenced
+ //if a mapping is for a servlet that is not enabled, skip it
+ Set<ServletMapping> mappings = sms.get(pathSpec);
+
+
+
+ ServletMapping finalMapping = null;
+ for (ServletMapping mapping : mappings)
+ {
+ //Get servlet associated with the mapping and check it is enabled
+ ServletHolder servlet_holder = _servletNameMap.get(mapping.getServletName());
+ if (servlet_holder == null)
+ throw new IllegalStateException("No such servlet: " + mapping.getServletName());
+ //if the servlet related to the mapping is not enabled, skip it from consideration
+ if (!servlet_holder.isEnabled())
+ continue;
+
+ //only accept a default mapping if we don't have any other
+ if (finalMapping == null)
+ finalMapping = mapping;
+ else
+ {
+ //already have a candidate - only accept another one if the candidate is a default
+ if (finalMapping.isDefault())
+ finalMapping = mapping;
+ else
+ {
+ //existing candidate isn't a default, if the one we're looking at isn't a default either, then its an error
+ if (!mapping.isDefault())
+ throw new IllegalStateException("Multiple servlets map to path: "+pathSpec+": "+finalMapping.getServletName()+","+mapping.getServletName());
+ }
+ }
+ }
+ if (finalMapping == null)
+ throw new IllegalStateException ("No acceptable servlet mappings for "+pathSpec);
+
+ if (LOG.isDebugEnabled()) LOG.debug("Chose path={} mapped to servlet={} from default={}", pathSpec, finalMapping.getServletName(), finalMapping.isDefault());
+
+ servletPathMappings.put(pathSpec, finalMapping);
+ pm.put(pathSpec,_servletNameMap.get(finalMapping.getServletName()));
+ }
+
+ _servletPathMap=pm;
+ _servletPathMappings=servletPathMappings;
+ }
+
+ // flush filter chain cache
+ if (_chainCache!=null)
+ {
+ for (int i=_chainCache.length;i-->0;)
+ {
+ if (_chainCache[i]!=null)
+ _chainCache[i].clear();
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("filterNameMap="+_filterNameMap);
+ LOG.debug("pathFilters="+_filterPathMappings);
+ LOG.debug("servletFilterMap="+_filterNameMappings);
+ LOG.debug("servletPathMap="+_servletPathMap);
+ LOG.debug("servletNameMap="+_servletNameMap);
+ }
+
+ try
+ {
+ if (_contextHandler!=null && _contextHandler.isStarted() || _contextHandler==null && isStarted())
+ initialize();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void notFound(Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ LOG.debug("Not Found {}",request.getRequestURI());
+ if (getHandler()!=null)
+ nextHandle(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()),baseRequest,request,response);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filterChainsCached The filterChainsCached to set.
+ */
+ public void setFilterChainsCached(boolean filterChainsCached)
+ {
+ _filterChainsCached = filterChainsCached;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filterMappings The filterMappings to set.
+ */
+ public void setFilterMappings(FilterMapping[] filterMappings)
+ {
+ updateBeans(_filterMappings,filterMappings);
+ _filterMappings = filterMappings;
+ updateMappings();
+ invalidateChainsCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void setFilters(FilterHolder[] holders)
+ {
+ if (holders!=null)
+ for (FilterHolder holder:holders)
+ holder.setServletHandler(this);
+
+ updateBeans(_filters,holders);
+ _filters=holders;
+ updateNameMappings();
+ invalidateChainsCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletMappings The servletMappings to set.
+ */
+ public void setServletMappings(ServletMapping[] servletMappings)
+ {
+ updateBeans(_servletMappings,servletMappings);
+ _servletMappings = servletMappings;
+ updateMappings();
+ invalidateChainsCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set Servlets.
+ * @param holders Array of servlets to define
+ */
+ public synchronized void setServlets(ServletHolder[] holders)
+ {
+ if (holders!=null)
+ for (ServletHolder holder:holders)
+ holder.setServletHandler(this);
+
+ updateBeans(_servlets,holders);
+ _servlets=holders;
+ updateNameMappings();
+ invalidateChainsCache();
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private class CachedChain implements FilterChain
+ {
+ FilterHolder _filterHolder;
+ CachedChain _next;
+ ServletHolder _servletHolder;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filters list of {@link FilterHolder} objects
+ * @param servletHolder
+ */
+ CachedChain(List<FilterHolder> filters, ServletHolder servletHolder)
+ {
+ if (filters.size()>0)
+ {
+ _filterHolder=filters.get(0);
+ filters.remove(0);
+ _next=new CachedChain(filters,servletHolder);
+ }
+ else
+ _servletHolder=servletHolder;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response)
+ throws IOException, ServletException
+ {
+ final Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest();
+
+ // pass to next filter
+ if (_filterHolder!=null)
+ {
+ LOG.debug("call filter {}", _filterHolder);
+ Filter filter= _filterHolder.getFilter();
+ if (_filterHolder.isAsyncSupported())
+ filter.doFilter(request, response, _next);
+ else
+ {
+ final boolean suspendable=baseRequest.isAsyncSupported();
+ if (suspendable)
+ {
+ try
+ {
+ baseRequest.setAsyncSupported(false);
+ filter.doFilter(request, response, _next);
+ }
+ finally
+ {
+ baseRequest.setAsyncSupported(true);
+ }
+ }
+ else
+ filter.doFilter(request, response, _next);
+ }
+ return;
+ }
+
+ // Call servlet
+ HttpServletRequest srequest = (HttpServletRequest)request;
+ if (_servletHolder == null)
+ notFound(baseRequest, srequest, (HttpServletResponse)response);
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("call servlet " + _servletHolder);
+ _servletHolder.handle(baseRequest,request, response);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ if (_filterHolder!=null)
+ return _filterHolder+"->"+_next.toString();
+ if (_servletHolder!=null)
+ return _servletHolder.toString();
+ return "null";
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private class Chain implements FilterChain
+ {
+ final Request _baseRequest;
+ final List<FilterHolder> _chain;
+ final ServletHolder _servletHolder;
+ int _filter= 0;
+
+ /* ------------------------------------------------------------ */
+ Chain(Request baseRequest, List<FilterHolder> filters, ServletHolder servletHolder)
+ {
+ _baseRequest=baseRequest;
+ _chain= filters;
+ _servletHolder= servletHolder;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response)
+ throws IOException, ServletException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("doFilter " + _filter);
+
+ // pass to next filter
+ if (_filter < _chain.size())
+ {
+ FilterHolder holder= _chain.get(_filter++);
+ if (LOG.isDebugEnabled())
+ LOG.debug("call filter " + holder);
+ Filter filter= holder.getFilter();
+
+ if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported())
+ {
+ filter.doFilter(request, response, this);
+ }
+ else
+ {
+ try
+ {
+ _baseRequest.setAsyncSupported(false);
+ filter.doFilter(request, response, this);
+ }
+ finally
+ {
+ _baseRequest.setAsyncSupported(true);
+ }
+ }
+
+ return;
+ }
+
+ // Call servlet
+ HttpServletRequest srequest = (HttpServletRequest)request;
+ if (_servletHolder == null)
+ notFound((request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest(), srequest, (HttpServletResponse)response);
+ else
+ {
+ LOG.debug("call servlet {}", _servletHolder);
+ _servletHolder.handle(_baseRequest,request, response);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ StringBuilder b = new StringBuilder();
+ for(FilterHolder f: _chain)
+ {
+ b.append(f.toString());
+ b.append("->");
+ }
+ b.append(_servletHolder);
+ return b.toString();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The maximum entries in a filter chain cache.
+ */
+ public int getMaxFilterChainsCacheSize()
+ {
+ return _maxFilterChainsCacheSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the maximum filter chain cache size.
+ * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
+ * is greater than zero, then the cache is flushed whenever it grows to be this size.
+ *
+ * @param maxFilterChainsCacheSize the maximum number of entries in a filter chain cache.
+ */
+ public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
+ {
+ _maxFilterChainsCacheSize = maxFilterChainsCacheSize;
+ }
+
+ /* ------------------------------------------------------------ */
+ void destroyServlet(Servlet servlet)
+ {
+ if (_contextHandler!=null)
+ _contextHandler.destroyServlet(servlet);
+ }
+
+ /* ------------------------------------------------------------ */
+ void destroyFilter(Filter filter)
+ {
+ if (_contextHandler!=null)
+ _contextHandler.destroyFilter(filter);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class Default404Servlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException
+ {
+ resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SingleThreadModel;
+import javax.servlet.UnavailableException;
+
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.RunAsToken;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+
+/* --------------------------------------------------------------------- */
+/** Servlet Instance and Context Holder.
+ * Holds the name, params and some state of a javax.servlet.Servlet
+ * instance. It implements the ServletConfig interface.
+ * This class will organise the loading of the servlet when needed or
+ * requested.
+ *
+ *
+ */
+@ManagedObject("Servlet Holder")
+public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope, Comparable<ServletHolder>
+{
+ private static final Logger LOG = Log.getLogger(ServletHolder.class);
+
+ /* ---------------------------------------------------------------- */
+ private int _initOrder = -1;
+ private boolean _initOnStartup=false;
+ private Map<String, String> _roleMap;
+ private String _forcedPath;
+ private String _runAsRole;
+ private RunAsToken _runAsToken;
+ private IdentityService _identityService;
+ private ServletRegistration.Dynamic _registration;
+
+
+ private transient Servlet _servlet;
+ private transient Config _config;
+ private transient long _unavailable;
+ private transient boolean _enabled = true;
+ private transient UnavailableException _unavailableEx;
+
+ public static final String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix";
+ public static final Map<String,String> NO_MAPPED_ROLES = Collections.emptyMap();
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor .
+ */
+ public ServletHolder()
+ {
+ this(Source.EMBEDDED);
+ }
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor .
+ */
+ public ServletHolder(Holder.Source creator)
+ {
+ super(creator);
+ }
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor for existing servlet.
+ */
+ public ServletHolder(Servlet servlet)
+ {
+ this(Source.EMBEDDED);
+ setServlet(servlet);
+ }
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor for servlet class.
+ */
+ public ServletHolder(String name, Class<? extends Servlet> servlet)
+ {
+ this(Source.EMBEDDED);
+ setName(name);
+ setHeldClass(servlet);
+ }
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor for servlet class.
+ */
+ public ServletHolder(String name, Servlet servlet)
+ {
+ this(Source.EMBEDDED);
+ setName(name);
+ setServlet(servlet);
+ }
+
+ /* ---------------------------------------------------------------- */
+ /** Constructor for servlet class.
+ */
+ public ServletHolder(Class<? extends Servlet> servlet)
+ {
+ this(Source.EMBEDDED);
+ setHeldClass(servlet);
+ }
+
+ /* ---------------------------------------------------------------- */
+ /**
+ * @return The unavailable exception or null if not unavailable
+ */
+ public UnavailableException getUnavailableException()
+ {
+ return _unavailableEx;
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized void setServlet(Servlet servlet)
+ {
+ if (servlet==null || servlet instanceof SingleThreadModel)
+ throw new IllegalArgumentException();
+
+ _extInstance=true;
+ _servlet=servlet;
+ setHeldClass(servlet.getClass());
+ if (getName()==null)
+ setName(servlet.getClass().getName()+"-"+super.hashCode());
+ }
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute(value="initialization order", readonly=true)
+ public int getInitOrder()
+ {
+ return _initOrder;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the initialize order.
+ * Holders with order<0, are initialized on use. Those with
+ * order>=0 are initialized in increasing order when the handler
+ * is started.
+ */
+ public void setInitOrder(int order)
+ {
+ _initOnStartup=order>=0;
+ _initOrder = order;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Comparitor by init order.
+ */
+ @Override
+ public int compareTo(ServletHolder sh)
+ {
+ if (sh==this)
+ return 0;
+ if (sh._initOrder<_initOrder)
+ return 1;
+ if (sh._initOrder>_initOrder)
+ return -1;
+
+ int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
+ if (c==0)
+ c=_name.compareTo(sh._name);
+ return c;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean equals(Object o)
+ {
+ return o instanceof ServletHolder && compareTo((ServletHolder)o)==0;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int hashCode()
+ {
+ return _name==null?System.identityHashCode(this):_name.hashCode();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Link a user role.
+ * Translate the role name used by a servlet, to the link name
+ * used by the container.
+ * @param name The role name as used by the servlet
+ * @param link The role name as used by the container.
+ */
+ public synchronized void setUserRoleLink(String name,String link)
+ {
+ if (_roleMap==null)
+ _roleMap=new HashMap<String, String>();
+ _roleMap.put(name,link);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** get a user role link.
+ * @param name The name of the role
+ * @return The name as translated by the link. If no link exists,
+ * the name is returned.
+ */
+ public String getUserRoleLink(String name)
+ {
+ if (_roleMap==null)
+ return name;
+ String link= _roleMap.get(name);
+ return (link==null)?name:link;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the forcedPath.
+ */
+ @ManagedAttribute(value="forced servlet path", readonly=true)
+ public String getForcedPath()
+ {
+ return _forcedPath;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param forcedPath The forcedPath to set.
+ */
+ public void setForcedPath(String forcedPath)
+ {
+ _forcedPath = forcedPath;
+ }
+
+ public boolean isEnabled()
+ {
+ return _enabled;
+ }
+
+
+ public void setEnabled(boolean enabled)
+ {
+ _enabled = enabled;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void doStart()
+ throws Exception
+ {
+ _unavailable=0;
+ if (!_enabled)
+ return;
+
+ // Handle JSP file forced paths
+ if (_forcedPath != null)
+ {
+ // Look for a precompiled JSP Servlet
+ String precompiled=getClassNameForJsp(_forcedPath);
+ LOG.debug("Checking for precompiled servlet {} for jsp {}", precompiled, _forcedPath);
+ ServletHolder jsp=getServletHandler().getServlet(precompiled);
+ if (jsp!=null)
+ {
+ LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
+ // set the className for this servlet to the precompiled one
+ setClassName(jsp.getClassName());
+ }
+ else
+ {
+ if (getClassName() == null)
+ {
+ // Look for normal JSP servlet
+ jsp=getServletHandler().getServlet("jsp");
+ if (jsp!=null)
+ {
+ LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
+ setClassName(jsp.getClassName());
+ //copy jsp init params that don't exist for this servlet
+ for (Map.Entry<String, String> entry:jsp.getInitParameters().entrySet())
+ {
+ if (!_initParams.containsKey(entry.getKey()))
+ setInitParameter(entry.getKey(), entry.getValue());
+ }
+ //jsp specific: set up the jsp-file on the JspServlet so it can precompile iff load-on-startup is >=0
+ if (_initOnStartup)
+ setInitParameter("jspFile", _forcedPath);
+ }
+ }
+ }
+ }
+
+
+ //check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
+ try
+ {
+ super.doStart();
+ }
+ catch (UnavailableException ue)
+ {
+ makeUnavailable(ue);
+ if (_servletHandler.isStartWithUnavailable())
+ {
+ LOG.ignore(ue);
+ return;
+ }
+ else
+ throw ue;
+ }
+
+
+ //servlet is not an instance of javax.servlet.Servlet
+ try
+ {
+ checkServletType();
+ }
+ catch (UnavailableException ue)
+ {
+ makeUnavailable(ue);
+ if (_servletHandler.isStartWithUnavailable())
+ {
+ LOG.ignore(ue);
+ return;
+ }
+ else
+ throw ue;
+ }
+
+ //check if we need to forcibly set load-on-startup
+ checkInitOnStartup();
+
+ _identityService = _servletHandler.getIdentityService();
+ if (_identityService!=null && _runAsRole!=null)
+ _runAsToken=_identityService.newRunAsToken(_runAsRole);
+
+ _config=new Config();
+
+ if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
+ _servlet = new SingleThreadedWrapper();
+
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void initialize ()
+ throws Exception
+ {
+ super.initialize();
+ if (_extInstance || _initOnStartup)
+ {
+ try
+ {
+ initServlet();
+ }
+ catch(Exception e)
+ {
+ if (_servletHandler.isStartWithUnavailable())
+ LOG.ignore(e);
+ else
+ throw e;
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void doStop()
+ throws Exception
+ {
+ Object old_run_as = null;
+ if (_servlet!=null)
+ {
+ try
+ {
+ if (_identityService!=null)
+ old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
+
+ destroyInstance(_servlet);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ finally
+ {
+ if (_identityService!=null)
+ _identityService.unsetRunAs(old_run_as);
+ }
+ }
+
+ if (!_extInstance)
+ _servlet=null;
+
+ _config=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void destroyInstance (Object o)
+ throws Exception
+ {
+ if (o==null)
+ return;
+ Servlet servlet = ((Servlet)o);
+ getServletHandler().destroyServlet(servlet);
+ servlet.destroy();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the servlet.
+ * @return The servlet
+ */
+ public synchronized Servlet getServlet()
+ throws ServletException
+ {
+ // Handle previous unavailability
+ if (_unavailable!=0)
+ {
+ if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable)
+ throw _unavailableEx;
+ _unavailable=0;
+ _unavailableEx=null;
+ }
+
+ if (_servlet==null)
+ initServlet();
+ return _servlet;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the servlet instance (no initialization done).
+ * @return The servlet or null
+ */
+ public Servlet getServletInstance()
+ {
+ return _servlet;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Check to ensure class of servlet is acceptable.
+ * @throws UnavailableException
+ */
+ public void checkServletType ()
+ throws UnavailableException
+ {
+ if (_class==null || !javax.servlet.Servlet.class.isAssignableFrom(_class))
+ {
+ throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet");
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the holder is started and is not unavailable
+ */
+ public boolean isAvailable()
+ {
+ if (isStarted()&& _unavailable==0)
+ return true;
+ try
+ {
+ getServlet();
+ }
+ catch(Exception e)
+ {
+ LOG.ignore(e);
+ }
+
+ return isStarted()&& _unavailable==0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Check if there is a javax.servlet.annotation.ServletSecurity
+ * annotation on the servlet class. If there is, then we force
+ * it to be loaded on startup, because all of the security
+ * constraints must be calculated as the container starts.
+ *
+ */
+ private void checkInitOnStartup()
+ {
+ if (_class==null)
+ return;
+
+ if ((_class.getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup)
+ setInitOrder(Integer.MAX_VALUE);
+ }
+
+ /* ------------------------------------------------------------ */
+ private void makeUnavailable(UnavailableException e)
+ {
+ if (_unavailableEx==e && _unavailable!=0)
+ return;
+
+ _servletHandler.getServletContext().log("unavailable",e);
+
+ _unavailableEx=e;
+ _unavailable=-1;
+ if (e.isPermanent())
+ _unavailable=-1;
+ else
+ {
+ if (_unavailableEx.getUnavailableSeconds()>0)
+ _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds();
+ else
+ _unavailable=System.currentTimeMillis()+5000; // TODO configure
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+
+ private void makeUnavailable(final Throwable e)
+ {
+ if (e instanceof UnavailableException)
+ makeUnavailable((UnavailableException)e);
+ else
+ {
+ ServletContext ctx = _servletHandler.getServletContext();
+ if (ctx==null)
+ LOG.info("unavailable",e);
+ else
+ ctx.log("unavailable",e);
+ _unavailableEx=new UnavailableException(String.valueOf(e),-1)
+ {
+ {
+ initCause(e);
+ }
+ };
+ _unavailable=-1;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void initServlet()
+ throws ServletException
+ {
+ Object old_run_as = null;
+ try
+ {
+ if (_servlet==null)
+ _servlet=newInstance();
+ if (_config==null)
+ _config=new Config();
+
+
+
+ // Handle run as
+ if (_identityService!=null)
+ {
+ old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
+ }
+
+ // Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
+ if (isJspServlet())
+ {
+ initJspServlet();
+ }
+
+ initMultiPart();
+
+ LOG.debug("Filter.init {}",_servlet);
+ _servlet.init(_config);
+ }
+ catch (UnavailableException e)
+ {
+ makeUnavailable(e);
+ _servlet=null;
+ _config=null;
+ throw e;
+ }
+ catch (ServletException e)
+ {
+ makeUnavailable(e.getCause()==null?e:e.getCause());
+ _servlet=null;
+ _config=null;
+ throw e;
+ }
+ catch (Exception e)
+ {
+ makeUnavailable(e);
+ _servlet=null;
+ _config=null;
+ throw new ServletException(this.toString(),e);
+ }
+ finally
+ {
+ // pop run-as role
+ if (_identityService!=null)
+ _identityService.unsetRunAs(old_run_as);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @throws Exception
+ */
+ protected void initJspServlet () throws Exception
+ {
+ ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
+
+ /* Set the webapp's classpath for Jasper */
+ ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
+
+ /* Set the system classpath for Jasper */
+ setInitParameter("com.sun.appserv.jsp.classpath", Loader.getClassPath(ch.getClassLoader().getParent()));
+
+ /* Set up other classpath attribute */
+ if ("?".equals(getInitParameter("classpath")))
+ {
+ String classpath = ch.getClassPath();
+ LOG.debug("classpath=" + classpath);
+ if (classpath != null)
+ setInitParameter("classpath", classpath);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Register a ServletRequestListener that will ensure tmp multipart
+ * files are deleted when the request goes out of scope.
+ *
+ * @throws Exception
+ */
+ protected void initMultiPart () throws Exception
+ {
+ //if this servlet can handle multipart requests, ensure tmp files will be
+ //cleaned up correctly
+ if (((Registration)getRegistration()).getMultipartConfig() != null)
+ {
+ //Register a listener to delete tmp files that are created as a result of this
+ //servlet calling Request.getPart() or Request.getParts()
+
+ ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
+ ch.addEventListener(new Request.MultiPartCleanerListener());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath()
+ */
+ @Override
+ public String getContextPath()
+ {
+ return _config.getServletContext().getContextPath();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap()
+ */
+ @Override
+ public Map<String, String> getRoleRefMap()
+ {
+ return _roleMap;
+ }
+
+ /* ------------------------------------------------------------ */
+ @ManagedAttribute(value="role to run servlet as", readonly=true)
+ public String getRunAsRole()
+ {
+ return _runAsRole;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRunAsRole(String role)
+ {
+ _runAsRole = role;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Service a request with this servlet.
+ */
+ public void handle(Request baseRequest,
+ ServletRequest request,
+ ServletResponse response)
+ throws ServletException,
+ UnavailableException,
+ IOException
+ {
+ if (_class==null)
+ throw new UnavailableException("Servlet Not Initialized");
+
+ Servlet servlet=_servlet;
+ synchronized(this)
+ {
+ if (!isStarted())
+ throw new UnavailableException("Servlet not initialized", -1);
+ if (_unavailable!=0 || (!_initOnStartup && servlet==null))
+ servlet=getServlet();
+ if (servlet==null)
+ throw new UnavailableException("Could not instantiate "+_class);
+ }
+
+ // Service the request
+ boolean servlet_error=true;
+ Object old_run_as = null;
+ boolean suspendable = baseRequest.isAsyncSupported();
+ try
+ {
+ // Handle aliased path
+ if (_forcedPath!=null)
+ // TODO complain about poor naming to the Jasper folks
+ request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
+
+ // Handle run as
+ if (_identityService!=null)
+ old_run_as=_identityService.setRunAs(baseRequest.getResolvedUserIdentity(),_runAsToken);
+
+ if (!isAsyncSupported())
+ baseRequest.setAsyncSupported(false);
+
+ MultipartConfigElement mpce = ((Registration)getRegistration()).getMultipartConfig();
+ if (mpce != null)
+ request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
+
+ servlet.service(request,response);
+ servlet_error=false;
+ }
+ catch(UnavailableException e)
+ {
+ makeUnavailable(e);
+ throw _unavailableEx;
+ }
+ finally
+ {
+ baseRequest.setAsyncSupported(suspendable);
+
+ // pop run-as role
+ if (_identityService!=null)
+ _identityService.unsetRunAs(old_run_as);
+
+ // Handle error params.
+ if (servlet_error)
+ request.setAttribute("javax.servlet.error.servlet_name",getName());
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private boolean isJspServlet ()
+ {
+ if (_servlet == null)
+ return false;
+
+ Class c = _servlet.getClass();
+
+ boolean result = false;
+ while (c != null && !result)
+ {
+ result = isJspServlet(c.getName());
+ c = c.getSuperclass();
+ }
+
+ return result;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private boolean isJspServlet (String classname)
+ {
+ if (classname == null)
+ return false;
+ return ("org.apache.jasper.servlet.JspServlet".equals(classname));
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private String getNameOfJspClass (String jsp)
+ {
+ if (jsp == null)
+ return "";
+
+ int i = jsp.lastIndexOf('/') + 1;
+ jsp = jsp.substring(i);
+ try
+ {
+ Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+ Method makeJavaIdentifier = jspUtil.getMethod("makeJavaIdentifier", String.class);
+ return (String)makeJavaIdentifier.invoke(null, jsp);
+ }
+ catch (Exception e)
+ {
+ String tmp = jsp.replace('.','_');
+ LOG.warn("Unable to make identifier for jsp "+jsp +" trying "+tmp+" instead");
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ return tmp;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private String getPackageOfJspClass (String jsp)
+ {
+ if (jsp == null)
+ return "";
+
+ int i = jsp.lastIndexOf('/');
+ if (i <= 0)
+ return "";
+ try
+ {
+ Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+ Method makeJavaPackage = jspUtil.getMethod("makeJavaPackage", String.class);
+ return (String)makeJavaPackage.invoke(null, jsp.substring(0,i));
+ }
+ catch (Exception e)
+ {
+ String tmp = jsp.substring(1).replace('/','.');
+ LOG.warn("Unable to make package for jsp "+jsp +" trying "+tmp+" instead");
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ return tmp;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private String getJspPackagePrefix ()
+ {
+ String jspPackageName = (String)getServletHandler().getServletContext().getInitParameter(JSP_GENERATED_PACKAGE_NAME );
+ if (jspPackageName == null)
+ jspPackageName = "org.apache.jsp";
+
+ return jspPackageName;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private String getClassNameForJsp (String jsp)
+ {
+ if (jsp == null)
+ return null;
+
+ return getJspPackagePrefix() + "." +getPackageOfJspClass(jsp) + "." + getNameOfJspClass(jsp);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ protected class Config extends HolderConfig implements ServletConfig
+ {
+ /* -------------------------------------------------------- */
+ @Override
+ public String getServletName()
+ {
+ return getName();
+ }
+
+ }
+
+ /* -------------------------------------------------------- */
+ /* -------------------------------------------------------- */
+ /* -------------------------------------------------------- */
+ public class Registration extends HolderRegistration implements ServletRegistration.Dynamic
+ {
+ protected MultipartConfigElement _multipartConfig;
+
+ @Override
+ public Set<String> addMapping(String... urlPatterns)
+ {
+ illegalStateIfContextStarted();
+ Set<String> clash=null;
+ for (String pattern : urlPatterns)
+ {
+ ServletMapping mapping = _servletHandler.getServletMapping(pattern);
+ if (mapping!=null)
+ {
+ //if the servlet mapping was from a default descriptor, then allow it to be overridden
+ if (!mapping.isDefault())
+ {
+ if (clash==null)
+ clash=new HashSet<String>();
+ clash.add(pattern);
+ }
+ }
+ }
+
+ //if there were any clashes amongst the urls, return them
+ if (clash!=null)
+ return clash;
+
+ //otherwise apply all of them
+ ServletMapping mapping = new ServletMapping();
+ mapping.setServletName(ServletHolder.this.getName());
+ mapping.setPathSpecs(urlPatterns);
+ _servletHandler.addServletMapping(mapping);
+
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Collection<String> getMappings()
+ {
+ ServletMapping[] mappings =_servletHandler.getServletMappings();
+ List<String> patterns=new ArrayList<String>();
+ if (mappings!=null)
+ {
+ for (ServletMapping mapping : mappings)
+ {
+ if (!mapping.getServletName().equals(getName()))
+ continue;
+ String[] specs=mapping.getPathSpecs();
+ if (specs!=null && specs.length>0)
+ patterns.addAll(Arrays.asList(specs));
+ }
+ }
+ return patterns;
+ }
+
+ @Override
+ public String getRunAsRole()
+ {
+ return _runAsRole;
+ }
+
+ @Override
+ public void setLoadOnStartup(int loadOnStartup)
+ {
+ illegalStateIfContextStarted();
+ ServletHolder.this.setInitOrder(loadOnStartup);
+ }
+
+ public int getInitOrder()
+ {
+ return ServletHolder.this.getInitOrder();
+ }
+
+ @Override
+ public void setMultipartConfig(MultipartConfigElement element)
+ {
+ _multipartConfig = element;
+ }
+
+ public MultipartConfigElement getMultipartConfig()
+ {
+ return _multipartConfig;
+ }
+
+ @Override
+ public void setRunAsRole(String role)
+ {
+ _runAsRole = role;
+ }
+
+ @Override
+ public Set<String> setServletSecurity(ServletSecurityElement securityElement)
+ {
+ return _servletHandler.setServletSecurity(this, securityElement);
+ }
+ }
+
+ public ServletRegistration.Dynamic getRegistration()
+ {
+ if (_registration == null)
+ _registration = new Registration();
+ return _registration;
+ }
+
+ /* -------------------------------------------------------- */
+ /* -------------------------------------------------------- */
+ /* -------------------------------------------------------- */
+ private class SingleThreadedWrapper implements Servlet
+ {
+ Stack<Servlet> _stack=new Stack<Servlet>();
+
+ @Override
+ public void destroy()
+ {
+ synchronized(this)
+ {
+ while(_stack.size()>0)
+ try { (_stack.pop()).destroy(); } catch (Exception e) { LOG.warn(e); }
+ }
+ }
+
+ @Override
+ public ServletConfig getServletConfig()
+ {
+ return _config;
+ }
+
+ @Override
+ public String getServletInfo()
+ {
+ return null;
+ }
+
+ @Override
+ public void init(ServletConfig config) throws ServletException
+ {
+ synchronized(this)
+ {
+ if(_stack.size()==0)
+ {
+ try
+ {
+ Servlet s = newInstance();
+ s.init(config);
+ _stack.push(s);
+ }
+ catch (ServletException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+ {
+ Servlet s;
+ synchronized(this)
+ {
+ if(_stack.size()>0)
+ s=(Servlet)_stack.pop();
+ else
+ {
+ try
+ {
+ s = newInstance();
+ s.init(_config);
+ }
+ catch (ServletException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new ServletException(e);
+ }
+ }
+ }
+
+ try
+ {
+ s.service(req,res);
+ }
+ finally
+ {
+ synchronized(this)
+ {
+ _stack.push(s);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the newly created Servlet instance
+ * @throws ServletException
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ protected Servlet newInstance() throws ServletException, IllegalAccessException, InstantiationException
+ {
+ try
+ {
+ ServletContext ctx = getServletHandler().getServletContext();
+ if (ctx instanceof ServletContextHandler.Context)
+ return ((ServletContextHandler.Context)ctx).createServlet(getHeldClass());
+ return getHeldClass().newInstance();
+ }
+ catch (ServletException se)
+ {
+ Throwable cause = se.getRootCause();
+ if (cause instanceof InstantiationException)
+ throw (InstantiationException)cause;
+ if (cause instanceof IllegalAccessException)
+ throw (IllegalAccessException)cause;
+ throw se;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x==%s,%d,%b",_name,hashCode(),_className,_initOrder,_servlet!=null);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+@ManagedObject("Servlet Mapping")
+public class ServletMapping
+{
+ private String[] _pathSpecs;
+ private String _servletName;
+ private boolean _default;
+
+
+ /* ------------------------------------------------------------ */
+ public ServletMapping()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the pathSpecs.
+ */
+ @ManagedAttribute(value="url patterns", readonly=true)
+ public String[] getPathSpecs()
+ {
+ return _pathSpecs;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the servletName.
+ */
+ @ManagedAttribute(value="servlet name", readonly=true)
+ public String getServletName()
+ {
+ return _servletName;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathSpecs The pathSpecs to set.
+ */
+ public void setPathSpecs(String[] pathSpecs)
+ {
+ _pathSpecs = pathSpecs;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param pathSpec The pathSpec to set.
+ */
+ public void setPathSpec(String pathSpec)
+ {
+ _pathSpecs = new String[]{pathSpec};
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param servletName The servletName to set.
+ */
+ public void setServletName(String servletName)
+ {
+ _servletName = servletName;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return
+ */
+ @ManagedAttribute(value="default", readonly=true)
+ public boolean isDefault()
+ {
+ return _default;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param fromDefault
+ */
+ public void setDefault(boolean fromDefault)
+ {
+ _default = fromDefault;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toString()
+ {
+ return (_pathSpecs==null?"[]":Arrays.asList(_pathSpecs).toString())+"=>"+_servletName;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ out.append(String.valueOf(this)).append("\n");
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.Map;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.resource.Resource;
+
+public class ServletTester extends ContainerLifeCycle
+{
+ private final Server _server=new Server();
+ private final LocalConnector _connector=new LocalConnector(_server);
+ private final ServletContextHandler _context;
+
+ public Server getServer()
+ {
+ return _server;
+ }
+
+ public LocalConnector getConnector()
+ {
+ return _connector;
+ }
+
+ public void setVirtualHosts(String[] vhosts)
+ {
+ _context.setVirtualHosts(vhosts);
+ }
+
+ public void addVirtualHosts(String[] virtualHosts)
+ {
+ _context.addVirtualHosts(virtualHosts);
+ }
+
+ public ServletHolder addServlet(String className, String pathSpec)
+ {
+ return _context.addServlet(className,pathSpec);
+ }
+
+ public ServletHolder addServlet(Class<? extends Servlet> servlet, String pathSpec)
+ {
+ return _context.addServlet(servlet,pathSpec);
+ }
+
+ public void addServlet(ServletHolder servlet, String pathSpec)
+ {
+ _context.addServlet(servlet,pathSpec);
+ }
+
+ public void addFilter(FilterHolder holder, String pathSpec, EnumSet<DispatcherType> dispatches)
+ {
+ _context.addFilter(holder,pathSpec,dispatches);
+ }
+
+ public FilterHolder addFilter(Class<? extends Filter> filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
+ {
+ return _context.addFilter(filterClass,pathSpec,dispatches);
+ }
+
+ public FilterHolder addFilter(String filterClass, String pathSpec, EnumSet<DispatcherType> dispatches)
+ {
+ return _context.addFilter(filterClass,pathSpec,dispatches);
+ }
+
+ public Object getAttribute(String name)
+ {
+ return _context.getAttribute(name);
+ }
+
+ public Enumeration getAttributeNames()
+ {
+ return _context.getAttributeNames();
+ }
+
+ public Attributes getAttributes()
+ {
+ return _context.getAttributes();
+ }
+
+ public String getContextPath()
+ {
+ return _context.getContextPath();
+ }
+
+ public String getInitParameter(String name)
+ {
+ return _context.getInitParameter(name);
+ }
+
+ public String setInitParameter(String name, String value)
+ {
+ return _context.setInitParameter(name,value);
+ }
+
+ public Enumeration getInitParameterNames()
+ {
+ return _context.getInitParameterNames();
+ }
+
+ public Map<String, String> getInitParams()
+ {
+ return _context.getInitParams();
+ }
+
+ public void removeAttribute(String name)
+ {
+ _context.removeAttribute(name);
+ }
+
+ public void setAttribute(String name, Object value)
+ {
+ _context.setAttribute(name,value);
+ }
+
+ public void setContextPath(String contextPath)
+ {
+ _context.setContextPath(contextPath);
+ }
+
+ public Resource getBaseResource()
+ {
+ return _context.getBaseResource();
+ }
+
+ public String getResourceBase()
+ {
+ return _context.getResourceBase();
+ }
+
+ public void setResourceBase(String resourceBase)
+ {
+ _context.setResourceBase(resourceBase);
+ }
+
+ private final ServletHandler _handler;
+
+ public ServletTester()
+ {
+ this("/",ServletContextHandler.SECURITY|ServletContextHandler.SESSIONS);
+ }
+
+ public ServletTester(String ctxPath)
+ {
+ this(ctxPath,ServletContextHandler.SECURITY|ServletContextHandler.SESSIONS);
+ }
+
+ public ServletTester(String contextPath,int options)
+ {
+ _context=new ServletContextHandler(_server,contextPath,options);
+ _handler=_context.getServletHandler();
+ _server.setConnectors(new Connector[]{_connector});
+ addBean(_server);
+ }
+
+ public ServletContextHandler getContext()
+ {
+ return _context;
+ }
+
+ public String getResponses(String request) throws Exception
+ {
+ return _connector.getResponses(request);
+ }
+
+ public ByteBuffer getResponses(ByteBuffer request) throws Exception
+ {
+ return _connector.getResponses(request);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Create a port based connector.
+ * This methods adds a port connector to the server
+ * @return A URL to access the server via the connector.
+ * @throws Exception
+ */
+ public String createConnector(boolean localhost) throws Exception
+ {
+ ServerConnector connector = new ServerConnector(_server);
+ if (localhost)
+ connector.setHost("127.0.0.1");
+ _server.addConnector(connector);
+ if (_server.isStarted())
+ connector.start();
+ else
+ connector.open();
+
+ return "http://"+(localhost?"127.0.0.1":
+ InetAddress.getLocalHost().getHostAddress()
+ )+":"+connector.getLocalPort();
+ }
+
+ public LocalConnector createLocalConnector()
+ {
+ LocalConnector connector = new LocalConnector(_server);
+ _server.addConnector(connector);
+ return connector;
+ }
+
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.ConnectorStatistics;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * StatisticsServlet
+ *
+ *
+ */
+public class StatisticsServlet extends HttpServlet
+{
+ private static final Logger LOG = Log.getLogger(StatisticsServlet.class);
+
+ boolean _restrictToLocalhost = true; // defaults to true
+ private StatisticsHandler _statsHandler;
+ private MemoryMXBean _memoryBean;
+ private Connector[] _connectors;
+
+
+
+ /**
+ * @see javax.servlet.GenericServlet#init()
+ */
+ public void init() throws ServletException
+ {
+ ServletContext context = getServletContext();
+ ContextHandler.Context scontext = (ContextHandler.Context) context;
+ Server _server = scontext.getContextHandler().getServer();
+
+ Handler handler = _server.getChildHandlerByClass(StatisticsHandler.class);
+
+ if (handler != null)
+ {
+ _statsHandler = (StatisticsHandler) handler;
+ }
+ else
+ {
+ LOG.warn("Statistics Handler not installed!");
+ return;
+ }
+
+ _memoryBean = ManagementFactory.getMemoryMXBean();
+ _connectors = _server.getConnectors();
+
+ if (getInitParameter("restrictToLocalhost") != null)
+ {
+ _restrictToLocalhost = "true".equals(getInitParameter("restrictToLocalhost"));
+ }
+ }
+
+
+
+ /**
+ * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
+ {
+ doGet(sreq, sres);
+ }
+
+
+
+ /**
+ * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ if (_statsHandler == null)
+ {
+ LOG.warn("Statistics Handler not installed!");
+ resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ return;
+ }
+ if (_restrictToLocalhost)
+ {
+ if (!isLoopbackAddress(req.getRemoteAddr()))
+ {
+ resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ return;
+ }
+ }
+
+ String wantXml = req.getParameter("xml");
+ if (wantXml == null)
+ wantXml = req.getParameter("XML");
+
+ if (wantXml != null && "true".equalsIgnoreCase(wantXml))
+ {
+ sendXmlResponse(resp);
+ }
+ else
+ {
+ sendTextResponse(resp);
+ }
+
+ }
+
+ private boolean isLoopbackAddress(String address)
+ {
+ try
+ {
+ InetAddress addr = InetAddress.getByName(address);
+ return addr.isLoopbackAddress();
+ }
+ catch (UnknownHostException e )
+ {
+ LOG.warn("Warning: attempt to access statistics servlet from " + address, e);
+ return false;
+ }
+ }
+
+ private void sendXmlResponse(HttpServletResponse response) throws IOException
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("<statistics>\n");
+
+ sb.append(" <requests>\n");
+ sb.append(" <statsOnMs>").append(_statsHandler.getStatsOnMs()).append("</statsOnMs>\n");
+
+ sb.append(" <requests>").append(_statsHandler.getRequests()).append("</requests>\n");
+ sb.append(" <requestsActive>").append(_statsHandler.getRequestsActive()).append("</requestsActive>\n");
+ sb.append(" <requestsActiveMax>").append(_statsHandler.getRequestsActiveMax()).append("</requestsActiveMax>\n");
+ sb.append(" <requestsTimeTotal>").append(_statsHandler.getRequestTimeTotal()).append("</requestsTimeTotal>\n");
+ sb.append(" <requestsTimeMean>").append(_statsHandler.getRequestTimeMean()).append("</requestsTimeMean>\n");
+ sb.append(" <requestsTimeMax>").append(_statsHandler.getRequestTimeMax()).append("</requestsTimeMax>\n");
+ sb.append(" <requestsTimeStdDev>").append(_statsHandler.getRequestTimeStdDev()).append("</requestsTimeStdDev>\n");
+
+ sb.append(" <dispatched>").append(_statsHandler.getDispatched()).append("</dispatched>\n");
+ sb.append(" <dispatchedActive>").append(_statsHandler.getDispatchedActive()).append("</dispatchedActive>\n");
+ sb.append(" <dispatchedActiveMax>").append(_statsHandler.getDispatchedActiveMax()).append("</dispatchedActiveMax>\n");
+ sb.append(" <dispatchedTimeTotalMs>").append(_statsHandler.getDispatchedTimeTotal()).append("</dispatchedTimeTotalMs>\n");
+ sb.append(" <dispatchedTimeMeanMs>").append(_statsHandler.getDispatchedTimeMean()).append("</dispatchedTimeMeanMs>\n");
+ sb.append(" <dispatchedTimeMaxMs>").append(_statsHandler.getDispatchedTimeMax()).append("</dispatchedTimeMaxMs>\n");
+ sb.append(" <dispatchedTimeStdDevMs>").append(_statsHandler.getDispatchedTimeStdDev()).append("</dispatchedTimeStdDevMs>\n");
+
+ sb.append(" <asyncRequests>").append(_statsHandler.getAsyncRequests()).append("</asyncRequests>\n");
+ sb.append(" <requestsSuspended>").append(_statsHandler.getAsyncRequestsWaiting()).append("</requestsSuspended>\n");
+ sb.append(" <requestsSuspendedMax>").append(_statsHandler.getAsyncRequestsWaitingMax()).append("</requestsSuspendedMax>\n");
+ sb.append(" <requestsResumed>").append(_statsHandler.getAsyncDispatches()).append("</requestsResumed>\n");
+ sb.append(" <requestsExpired>").append(_statsHandler.getExpires()).append("</requestsExpired>\n");
+ sb.append(" </requests>\n");
+
+ sb.append(" <responses>\n");
+ sb.append(" <responses1xx>").append(_statsHandler.getResponses1xx()).append("</responses1xx>\n");
+ sb.append(" <responses2xx>").append(_statsHandler.getResponses2xx()).append("</responses2xx>\n");
+ sb.append(" <responses3xx>").append(_statsHandler.getResponses3xx()).append("</responses3xx>\n");
+ sb.append(" <responses4xx>").append(_statsHandler.getResponses4xx()).append("</responses4xx>\n");
+ sb.append(" <responses5xx>").append(_statsHandler.getResponses5xx()).append("</responses5xx>\n");
+ sb.append(" <responsesBytesTotal>").append(_statsHandler.getResponsesBytesTotal()).append("</responsesBytesTotal>\n");
+ sb.append(" </responses>\n");
+
+ sb.append(" <connections>\n");
+ for (Connector connector : _connectors)
+ {
+ sb.append(" <connector>\n");
+ sb.append(" <name>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</name>\n");
+ sb.append(" <protocols>\n");
+ for (String protocol:connector.getProtocols())
+ sb.append(" <protocol>").append(protocol).append("</protocol>\n");
+ sb.append(" </protocols>\n");
+
+ ConnectorStatistics connectorStats = null;
+
+ if (connector instanceof AbstractConnector)
+ connectorStats = ((AbstractConnector)connector).getBean(ConnectorStatistics.class);
+ if (connectorStats == null)
+ sb.append(" <statsOn>false</statsOn>\n");
+ else
+ {
+ sb.append(" <statsOn>true</statsOn>\n");
+ sb.append(" <connections>").append(connectorStats.getConnections()).append("</connections>\n");
+ sb.append(" <connectionsOpen>").append(connectorStats.getConnectionsOpen()).append("</connectionsOpen>\n");
+ sb.append(" <connectionsOpenMax>").append(connectorStats.getConnectionsOpenMax()).append("</connectionsOpenMax>\n");
+ sb.append(" <connectionsDurationMean>").append(connectorStats.getConnectionDurationMean()).append("</connectionsDurationMean>\n");
+ sb.append(" <connectionsDurationMax>").append(connectorStats.getConnectionDurationMax()).append("</connectionsDurationMax>\n");
+ sb.append(" <connectionsDurationStdDev>").append(connectorStats.getConnectionDurationStdDev()).append("</connectionsDurationStdDev>\n");
+ sb.append(" <messagesIn>").append(connectorStats.getMessagesIn()).append("</messagesIn>\n");
+ sb.append(" <messagesOut>").append(connectorStats.getMessagesIn()).append("</messagesOut>\n");
+ sb.append(" <elapsedMs>").append(connectorStats.getStartedMillis()).append("</elapsedMs>\n");
+ }
+ sb.append(" </connector>\n");
+ }
+ sb.append(" </connections>\n");
+
+ sb.append(" <memory>\n");
+ sb.append(" <heapMemoryUsage>").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("</heapMemoryUsage>\n");
+ sb.append(" <nonHeapMemoryUsage>").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("</nonHeapMemoryUsage>\n");
+ sb.append(" </memory>\n");
+
+ sb.append("</statistics>\n");
+
+ response.setContentType("text/xml");
+ PrintWriter pout = response.getWriter();
+ pout.write(sb.toString());
+ }
+
+
+
+ /**
+ * @param response
+ * @throws IOException
+ */
+ private void sendTextResponse(HttpServletResponse response) throws IOException
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(_statsHandler.toStatsHTML());
+
+ sb.append("<h2>Connections:</h2>\n");
+ for (Connector connector : _connectors)
+ {
+ sb.append("<h3>").append(connector.getClass().getName()).append("@").append(connector.hashCode()).append("</h3>");
+ sb.append("Protocols:");
+ for (String protocol:connector.getProtocols())
+ sb.append(protocol).append(" ");
+ sb.append(" <br />\n");
+
+ ConnectorStatistics connectorStats = null;
+
+ if (connector instanceof AbstractConnector)
+ connectorStats = ((AbstractConnector)connector).getBean(ConnectorStatistics.class);
+
+ if (connectorStats != null)
+ {
+ sb.append("Statistics gathering started ").append(connectorStats.getStartedMillis()).append("ms ago").append("<br />\n");
+ sb.append("Total connections: ").append(connectorStats.getConnections()).append("<br />\n");
+ sb.append("Current connections open: ").append(connectorStats.getConnectionsOpen()).append("<br />\n");;
+ sb.append("Max concurrent connections open: ").append(connectorStats.getConnectionsOpenMax()).append("<br />\n");
+ sb.append("Mean connection duration: ").append(connectorStats.getConnectionDurationMean()).append("<br />\n");
+ sb.append("Max connection duration: ").append(connectorStats.getConnectionDurationMax()).append("<br />\n");
+ sb.append("Connection duration standard deviation: ").append(connectorStats.getConnectionDurationStdDev()).append("<br />\n");
+ sb.append("Total messages in: ").append(connectorStats.getMessagesIn()).append("<br />\n");
+ sb.append("Total messages out: ").append(connectorStats.getMessagesOut()).append("<br />\n");
+ }
+ else
+ {
+ sb.append("Statistics gathering off.\n");
+ }
+
+ }
+
+ sb.append("<h2>Memory:</h2>\n");
+ sb.append("Heap memory usage: ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\n");
+ sb.append("Non-heap memory usage: ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\n");
+
+ response.setContentType("text/html");
+ PrintWriter pout = response.getWriter();
+ pout.write(sb.toString());
+
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet.listener;
+
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * ELContextCleaner
+ *
+ * Clean up BeanELResolver when the context is going out
+ * of service:
+ *
+ * See http://java.net/jira/browse/GLASSFISH-1649
+ * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=353095
+ */
+public class ELContextCleaner implements ServletContextListener
+{
+ private static final Logger LOG = Log.getLogger(ELContextCleaner.class);
+
+
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ }
+
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ try
+ {
+ //Check that the BeanELResolver class is on the classpath
+ Class beanELResolver = Loader.loadClass(this.getClass(), "javax.el.BeanELResolver");
+
+ //Get a reference via reflection to the properties field which is holding class references
+ Field field = getField(beanELResolver);
+
+ //Get rid of references
+ purgeEntries(field);
+
+ LOG.debug("javax.el.BeanELResolver purged");
+ }
+
+ catch (ClassNotFoundException e)
+ {
+ //BeanELResolver not on classpath, ignore
+ }
+ catch (SecurityException e)
+ {
+ LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+ }
+ catch (IllegalArgumentException e)
+ {
+ LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+ }
+ catch (IllegalAccessException e)
+ {
+ LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+ }
+ catch (NoSuchFieldException e)
+ {
+ LOG.debug("Not cleaning cached beans: no such field javax.el.BeanELResolver.properties");
+ }
+
+ }
+
+
+ protected Field getField (Class beanELResolver)
+ throws SecurityException, NoSuchFieldException
+ {
+ if (beanELResolver == null)
+ return null;
+
+ return beanELResolver.getDeclaredField("properties");
+ }
+
+ protected void purgeEntries (Field properties)
+ throws IllegalArgumentException, IllegalAccessException
+ {
+ if (properties == null)
+ return;
+
+ if (!properties.isAccessible())
+ properties.setAccessible(true);
+
+ ConcurrentHashMap map = (ConcurrentHashMap) properties.get(null);
+ if (map == null)
+ return;
+
+ Iterator<Class> itor = map.keySet().iterator();
+ while (itor.hasNext())
+ {
+ Class clazz = itor.next();
+ LOG.debug("Clazz: "+clazz+" loaded by "+clazz.getClassLoader());
+ if (Thread.currentThread().getContextClassLoader().equals(clazz.getClassLoader()))
+ {
+ itor.remove();
+ LOG.debug("removed");
+ }
+ else
+ LOG.debug("not removed: "+"contextclassloader="+Thread.currentThread().getContextClassLoader()+"clazz's classloader="+clazz.getClassLoader());
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.servlet.listener;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * IntrospectorCleaner
+ *
+ * Cleans a static cache of Methods held by java.beans.Introspector
+ * class when a context is undeployed.
+ *
+ * @see java.beans.Introspector
+ */
+public class IntrospectorCleaner implements ServletContextListener
+{
+
+ public void contextInitialized(ServletContextEvent sce)
+ {
+
+ }
+
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ java.beans.Introspector.flushCaches();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Servlet : Useful Servlet Listeners
+ */
+package org.eclipse.jetty.servlet.listener;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Server : Modular Servlet Integration
+ */
+package org.eclipse.jetty.servlet;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Trie implementation.
+ * <p>Provides some common implementations, which may not be the most
+ * efficient. For byte operations, the assumption is made that the charset
+ * is ISO-8859-1</p>
+ * @param <V>
+ */
+public abstract class AbstractTrie<V> implements Trie<V>
+{
+ final boolean _caseInsensitive;
+
+ protected AbstractTrie(boolean insensitive)
+ {
+ _caseInsensitive=insensitive;
+ }
+
+ @Override
+ public boolean put(V v)
+ {
+ return put(v.toString(),v);
+ }
+
+ @Override
+ public V remove(String s)
+ {
+ V o=get(s);
+ put(s,null);
+ return o;
+ }
+
+ @Override
+ public V get(String s)
+ {
+ return get(s,0,s.length());
+ }
+
+ @Override
+ public V get(ByteBuffer b)
+ {
+ return get(b,0,b.remaining());
+ }
+
+ @Override
+ public V getBest(String s)
+ {
+ return getBest(s,0,s.length());
+ }
+
+ @Override
+ public V getBest(byte[] b, int offset, int len)
+ {
+ return getBest(new String(b,offset,len,StandardCharsets.ISO_8859_1));
+ }
+
+ @Override
+ public boolean isCaseInsensitive()
+ {
+ return _caseInsensitive;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.AbstractList;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+
+/* ------------------------------------------------------------ */
+/**
+ * Queue backed by circular array.
+ * <p/>
+ * This partial Queue implementation (also with {@link #remove()} for stack operation)
+ * is backed by a growable circular array.
+ *
+ * @param <E>
+ */
+public class ArrayQueue<E> extends AbstractList<E> implements Queue<E>
+{
+ public static final int DEFAULT_CAPACITY = 64;
+ public static final int DEFAULT_GROWTH = 32;
+
+ protected final Object _lock;
+ protected final int _growCapacity;
+ protected Object[] _elements;
+ protected int _nextE;
+ protected int _nextSlot;
+ protected int _size;
+
+ /* ------------------------------------------------------------ */
+ public ArrayQueue()
+ {
+ this(DEFAULT_CAPACITY, -1);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ArrayQueue(Object lock)
+ {
+ this(DEFAULT_CAPACITY, -1,lock);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ArrayQueue(int capacity)
+ {
+ this(capacity, -1);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ArrayQueue(int initCapacity, int growBy)
+ {
+ this(initCapacity, growBy, null);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ArrayQueue(int initCapacity, int growBy, Object lock)
+ {
+ _lock = lock == null ? this : lock;
+ _growCapacity = growBy;
+ _elements = new Object[initCapacity];
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object lock()
+ {
+ return _lock;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getCapacity()
+ {
+ synchronized (_lock)
+ {
+ return _elements.length;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean add(E e)
+ {
+ if (!offer(e))
+ throw new IllegalStateException("Full");
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean offer(E e)
+ {
+ synchronized (_lock)
+ {
+ return enqueue(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private boolean enqueue(E e)
+ {
+ if (_size == _elements.length && !grow())
+ return false;
+
+ _size++;
+ _elements[_nextSlot++] = e;
+ if (_nextSlot == _elements.length)
+ _nextSlot = 0;
+
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add without synchronization or bounds checking
+ *
+ * @param e the element to add
+ * @see #add(Object)
+ */
+ public void addUnsafe(E e)
+ {
+ if (!enqueue(e))
+ throw new IllegalStateException("Full");
+ }
+
+ /* ------------------------------------------------------------ */
+ public E element()
+ {
+ synchronized (_lock)
+ {
+ if (isEmpty())
+ throw new NoSuchElementException();
+ return at(_nextE);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @SuppressWarnings("unchecked")
+ private E at(int index)
+ {
+ return (E)_elements[index];
+ }
+
+ /* ------------------------------------------------------------ */
+ public E peek()
+ {
+ synchronized (_lock)
+ {
+ if (_size == 0)
+ return null;
+ return at(_nextE);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public E peekUnsafe()
+ {
+ if (_size == 0)
+ return null;
+ return at(_nextE);
+ }
+
+ /* ------------------------------------------------------------ */
+ public E poll()
+ {
+ synchronized (_lock)
+ {
+ if (_size == 0)
+ return null;
+ return dequeue();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public E pollUnsafe()
+ {
+ if (_size == 0)
+ return null;
+ return dequeue();
+ }
+
+ /* ------------------------------------------------------------ */
+ private E dequeue()
+ {
+ E e = at(_nextE);
+ _elements[_nextE] = null;
+ _size--;
+ if (++_nextE == _elements.length)
+ _nextE = 0;
+ return e;
+ }
+
+ /* ------------------------------------------------------------ */
+ public E remove()
+ {
+ synchronized (_lock)
+ {
+ if (_size == 0)
+ throw new NoSuchElementException();
+ return dequeue();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void clear()
+ {
+ synchronized (_lock)
+ {
+ _size = 0;
+ _nextE = 0;
+ _nextSlot = 0;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isEmpty()
+ {
+ synchronized (_lock)
+ {
+ return _size == 0;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int size()
+ {
+ synchronized (_lock)
+ {
+ return _size;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public E get(int index)
+ {
+ synchronized (_lock)
+ {
+ if (index < 0 || index >= _size)
+ throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+ return getUnsafe(index);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get without synchronization or bounds checking.
+ *
+ * @param index index of the element to return
+ * @return the element at the specified index
+ * @see #get(int)
+ */
+ public E getUnsafe(int index)
+ {
+ int i = (_nextE + index) % _elements.length;
+ return at(i);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public E remove(int index)
+ {
+ synchronized (_lock)
+ {
+ if (index < 0 || index >= _size)
+ throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+ int i = (_nextE + index) % _elements.length;
+ E old = at(i);
+
+ if (i < _nextSlot)
+ {
+ // 0 _elements.length
+ // _nextE........._nextSlot
+ System.arraycopy(_elements, i + 1, _elements, i, _nextSlot - i);
+ _nextSlot--;
+ _size--;
+ }
+ else
+ {
+ // 0 _elements.length
+ // ......_nextSlot _nextE..........
+ System.arraycopy(_elements, i + 1, _elements, i, _elements.length - i - 1);
+ if (_nextSlot > 0)
+ {
+ _elements[_elements.length - 1] = _elements[0];
+ System.arraycopy(_elements, 1, _elements, 0, _nextSlot - 1);
+ _nextSlot--;
+ }
+ else
+ _nextSlot = _elements.length - 1;
+
+ _size--;
+ }
+
+ return old;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public E set(int index, E element)
+ {
+ synchronized (_lock)
+ {
+ if (index < 0 || index >= _size)
+ throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+ int i = _nextE + index;
+ if (i >= _elements.length)
+ i -= _elements.length;
+ E old = at(i);
+ _elements[i] = element;
+ return old;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void add(int index, E element)
+ {
+ synchronized (_lock)
+ {
+ if (index < 0 || index > _size)
+ throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+ if (_size == _elements.length && !grow())
+ throw new IllegalStateException("Full");
+
+ if (index == _size)
+ {
+ add(element);
+ }
+ else
+ {
+ int i = _nextE + index;
+ if (i >= _elements.length)
+ i -= _elements.length;
+
+ _size++;
+ _nextSlot++;
+ if (_nextSlot == _elements.length)
+ _nextSlot = 0;
+
+ if (i < _nextSlot)
+ {
+ // 0 _elements.length
+ // _nextE.....i..._nextSlot
+ // 0 _elements.length
+ // ..i..._nextSlot _nextE..........
+ System.arraycopy(_elements, i, _elements, i + 1, _nextSlot - i);
+ _elements[i] = element;
+ }
+ else
+ {
+ // 0 _elements.length
+ // ......_nextSlot _nextE.....i....
+ if (_nextSlot > 0)
+ {
+ System.arraycopy(_elements, 0, _elements, 1, _nextSlot);
+ _elements[0] = _elements[_elements.length - 1];
+ }
+
+ System.arraycopy(_elements, i, _elements, i + 1, _elements.length - i - 1);
+ _elements[i] = element;
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected boolean grow()
+ {
+ synchronized (_lock)
+ {
+ if (_growCapacity <= 0)
+ return false;
+
+ Object[] elements = new Object[_elements.length + _growCapacity];
+
+ int split = _elements.length - _nextE;
+ if (split > 0)
+ System.arraycopy(_elements, _nextE, elements, 0, split);
+ if (_nextE != 0)
+ System.arraycopy(_elements, 0, elements, split, _nextSlot);
+
+ _elements = elements;
+ _nextE = 0;
+ _nextSlot = _size;
+ return true;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/* ------------------------------------------------------------ */
+/** A Ternary Trie String lookup data structure.
+ * This Trie is of a fixed size and cannot grow (which can be a good thing with regards to DOS when used as a cache).
+ * <p>
+ * The Trie is stored in 3 arrays:<dl>
+ * <dt>char[] _tree</dt><dd>This is semantically 2 dimensional array flattened into a 1 dimensional char array. The second dimension
+ * is that every 4 sequential elements represents a row of: character; hi index; eq index; low index, used to build a
+ * ternary trie of key strings.</dd>
+ * <dt>String[] _key<dt><dd>An array of key values where each element matches a row in the _tree array. A non zero key element
+ * indicates that the _tree row is a complete key rather than an intermediate character of a longer key.</dd>
+ * <dt>V[] _value</dt><dd>An array of values corresponding to the _key array</dd>
+ * </dl>
+ * <p>The lookup of a value will iterate through the _tree array matching characters. If the equal tree branch is followed,
+ * then the _key array is looked up to see if this is a complete match. If a match is found then the _value array is looked up
+ * to return the matching value.
+ * </p>
+ * @param <V>
+ */
+public class ArrayTernaryTrie<V> extends AbstractTrie<V>
+{
+ private static int LO=1;
+ private static int EQ=2;
+ private static int HI=3;
+
+ /**
+ * The Size of a Trie row is the char, and the low, equal and high
+ * child pointers
+ */
+ private static final int ROW_SIZE = 4;
+
+ /**
+ * The Trie rows in a single array which allows a lookup of row,character
+ * to the next row in the Trie. This is actually a 2 dimensional
+ * array that has been flattened to achieve locality of reference.
+ */
+ private final char[] _tree;
+
+ /**
+ * The key (if any) for a Trie row.
+ * A row may be a leaf, a node or both in the Trie tree.
+ */
+ private final String[] _key;
+
+ /**
+ * The value (if any) for a Trie row.
+ * A row may be a leaf, a node or both in the Trie tree.
+ */
+ private final Object[] _value;
+
+ /**
+ * The number of rows allocated
+ */
+ private char _rows;
+
+ public ArrayTernaryTrie()
+ {
+ this(128);
+ }
+
+ public ArrayTernaryTrie(boolean insensitive)
+ {
+ this(insensitive,128);
+ }
+
+ public ArrayTernaryTrie(int capacityInNodes)
+ {
+ this(true,capacityInNodes);
+ }
+
+ public ArrayTernaryTrie(boolean insensitive, int capacityInNodes)
+ {
+ super(insensitive);
+ _value=new Object[capacityInNodes];
+ _tree=new char[capacityInNodes*ROW_SIZE];
+ _key=new String[capacityInNodes];
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Copy Trie and change capacity by a factor
+ * @param trie
+ * @param factor
+ */
+ public ArrayTernaryTrie(ArrayTernaryTrie<V> trie, double factor)
+ {
+ super(trie.isCaseInsensitive());
+ int capacity=(int)(trie._value.length*factor);
+ _rows=trie._rows;
+ _value=Arrays.copyOf(trie._value, capacity);
+ _tree=Arrays.copyOf(trie._tree, capacity*ROW_SIZE);
+ _key=Arrays.copyOf(trie._key, capacity);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean put(String s, V v)
+ {
+ int t=0;
+ int limit = s.length();
+ int last=0;
+ for(int k=0; k < limit; k++)
+ {
+ char c=s.charAt(k);
+ if(isCaseInsensitive() && c<128)
+ c=StringUtil.lowercases[c];
+
+ while (true)
+ {
+ int row=ROW_SIZE*t;
+
+ // Do we need to create the new row?
+ if (t==_rows)
+ {
+ _rows++;
+ if (_rows>=_key.length)
+ {
+ _rows--;
+ return false;
+ }
+ _tree[row]=c;
+ }
+
+ char n=_tree[row];
+ int diff=n-c;
+ if (diff==0)
+ t=_tree[last=(row+EQ)];
+ else if (diff<0)
+ t=_tree[last=(row+LO)];
+ else
+ t=_tree[last=(row+HI)];
+
+ // do we need a new row?
+ if (t==0)
+ {
+ t=_rows;
+ _tree[last]=(char)t;
+ }
+
+ if (diff==0)
+ break;
+ }
+ }
+
+ // Do we need to create the new row?
+ if (t==_rows)
+ {
+ _rows++;
+ if (_rows>=_key.length)
+ {
+ _rows--;
+ return false;
+ }
+ }
+
+ // Put the key and value
+ _key[t]=v==null?null:s;
+ _value[t] = v;
+
+ return true;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V get(String s,int offset, int len)
+ {
+ int t = 0;
+ for(int i=0; i < len;)
+ {
+ char c=s.charAt(offset+i++);
+ if(isCaseInsensitive() && c<128)
+ c=StringUtil.lowercases[c];
+
+ while (true)
+ {
+ int row = ROW_SIZE*t;
+ char n=_tree[row];
+ int diff=n-c;
+
+ if (diff==0)
+ {
+ t=_tree[row+EQ];
+ if (t==0)
+ return null;
+ break;
+ }
+
+ t=_tree[row+hilo(diff)];
+ if (t==0)
+ return null;
+ }
+ }
+
+ return (V)_value[t];
+ }
+
+
+ @Override
+ public V get(ByteBuffer b, int offset, int len)
+ {
+ int t = 0;
+ offset+=b.position();
+
+ for(int i=0; i < len;)
+ {
+ byte c=(byte)(b.get(offset+i++)&0x7f);
+ if(isCaseInsensitive())
+ c=(byte)StringUtil.lowercases[c];
+
+ while (true)
+ {
+ int row = ROW_SIZE*t;
+ char n=_tree[row];
+ int diff=n-c;
+
+ if (diff==0)
+ {
+ t=_tree[row+EQ];
+ if (t==0)
+ return null;
+ break;
+ }
+
+ t=_tree[row+hilo(diff)];
+ if (t==0)
+ return null;
+ }
+ }
+
+ return (V)_value[t];
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V getBest(String s)
+ {
+ return getBest(0,s,0,s.length());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V getBest(String s, int offset, int length)
+ {
+ return getBest(0,s,offset,length);
+ }
+
+ /* ------------------------------------------------------------ */
+ private V getBest(int t,String s,int offset,int len)
+ {
+ int node=t;
+ loop: for(int i=0; i<len; i++)
+ {
+ char c=s.charAt(offset+i);
+ if(isCaseInsensitive() && c<128)
+ c=StringUtil.lowercases[c];
+
+ while (true)
+ {
+ int row = ROW_SIZE*t;
+ char n=_tree[row];
+ int diff=n-c;
+
+ if (diff==0)
+ {
+ t=_tree[row+EQ];
+ if (t==0)
+ break loop;
+
+ // if this node is a match, recurse to remember
+ if (_key[t]!=null)
+ {
+ node=t;
+ V best=getBest(t,s,offset+i+1,len-i-1);
+ if (best!=null)
+ return best;
+ }
+ break;
+ }
+
+ t=_tree[row+hilo(diff)];
+ if (t==0)
+ break loop;
+ }
+ }
+ return (V)_value[node];
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V getBest(ByteBuffer b, int offset, int len)
+ {
+ if (b.hasArray())
+ return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+ return getBest(0,b,offset,len);
+ }
+
+ /* ------------------------------------------------------------ */
+ private V getBest(int t,byte[] b, int offset, int len)
+ {
+ int node=t;
+ loop: for(int i=0; i<len; i++)
+ {
+ byte c=(byte)(b[offset+i]&0x7f);
+ if(isCaseInsensitive())
+ c=(byte)StringUtil.lowercases[c];
+
+ while (true)
+ {
+ int row = ROW_SIZE*t;
+ char n=_tree[row];
+ int diff=n-c;
+
+ if (diff==0)
+ {
+ t=_tree[row+EQ];
+ if (t==0)
+ break loop;
+
+ // if this node is a match, recurse to remember
+ if (_key[t]!=null)
+ {
+ node=t;
+ V best=getBest(t,b,offset+i+1,len-i-1);
+ if (best!=null)
+ return best;
+ }
+ break;
+ }
+
+ t=_tree[row+hilo(diff)];
+ if (t==0)
+ break loop;
+ }
+ }
+ return (V)_value[node];
+ }
+
+ /* ------------------------------------------------------------ */
+ private V getBest(int t,ByteBuffer b, int offset, int len)
+ {
+ int node=t;
+ int o= offset+b.position();
+
+ loop: for(int i=0; i<len; i++)
+ {
+ byte c=(byte)(b.get(o+i)&0x7f);
+ if(isCaseInsensitive())
+ c=(byte)StringUtil.lowercases[c];
+
+ while (true)
+ {
+ int row = ROW_SIZE*t;
+ char n=_tree[row];
+ int diff=n-c;
+
+ if (diff==0)
+ {
+ t=_tree[row+EQ];
+ if (t==0)
+ break loop;
+
+ // if this node is a match, recurse to remember
+ if (_key[t]!=null)
+ {
+ node=t;
+ V best=getBest(t,b,offset+i+1,len-i-1);
+ if (best!=null)
+ return best;
+ }
+ break;
+ }
+
+ t=_tree[row+hilo(diff)];
+ if (t==0)
+ break loop;
+ }
+ }
+ return (V)_value[node];
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder();
+ for (int r=0;r<=_rows;r++)
+ {
+ if (_key[r]!=null && _value[r]!=null)
+ {
+ buf.append(',');
+ buf.append(_key[r]);
+ buf.append('=');
+ buf.append(_value[r].toString());
+ }
+ }
+ if (buf.length()==0)
+ return "{}";
+
+ buf.setCharAt(0,'{');
+ buf.append('}');
+ return buf.toString();
+ }
+
+
+
+ @Override
+ public Set<String> keySet()
+ {
+ Set<String> keys = new HashSet<>();
+
+ for (int r=0;r<=_rows;r++)
+ {
+ if (_key[r]!=null && _value[r]!=null)
+ keys.add(_key[r]);
+ }
+ return keys;
+ }
+
+ @Override
+ public boolean isFull()
+ {
+ return _rows+1==_key.length;
+ }
+
+ public static int hilo(int diff)
+ {
+ // branchless equivalent to return ((diff<0)?LO:HI);
+ // return 3+2*((diff&Integer.MIN_VALUE)>>Integer.SIZE-1);
+ return 1+(diff|Integer.MAX_VALUE)/(Integer.MAX_VALUE/2);
+ }
+
+ public void dump()
+ {
+ for (int r=0;r<_rows;r++)
+ {
+ char c=_tree[r*ROW_SIZE+0];
+ System.err.printf("%4d [%s,%d,%d,%d] '%s':%s%n",
+ r,
+ (c<' '||c>127)?(""+(int)c):"'"+c+"'",
+ (int)_tree[r*ROW_SIZE+LO],
+ (int)_tree[r*ROW_SIZE+EQ],
+ (int)_tree[r*ROW_SIZE+HI],
+ _key[r],
+ _value[r]);
+ }
+
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure using a fixed size array.
+ * <p>This implementation is always case insensitive and is optimal for
+ * a small number of fixed strings with few special characters.
+ * </p>
+ * @param <V>
+ */
+public class ArrayTrie<V> extends AbstractTrie<V>
+{
+ /**
+ * The Size of a Trie row is how many characters can be looked
+ * up directly without going to a big index. This is set at
+ * 32 to cover case insensitive alphabet and a few other common
+ * characters.
+ */
+ private static final int ROW_SIZE = 32;
+
+ /**
+ * The index lookup table, this maps a character as a byte
+ * (ISO-8859-1 or UTF8) to an index within a Trie row
+ */
+ private static final int[] __lookup =
+ { // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30,
+ /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, -1, -1,
+ /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
+ /*4*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ /*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ };
+
+ /**
+ * The Trie rows in a single array which allows a lookup of row,character
+ * to the next row in the Trie. This is actually a 2 dimensional
+ * array that has been flattened to achieve locality of reference.
+ * The first ROW_SIZE entries are for row 0, then next ROW_SIZE
+ * entries are for row 1 etc. So in general instead of using
+ * _rows[row][index], we use _rows[row*ROW_SIZE+index] to look up
+ * the next row for a given character.
+ *
+ * The array is of characters rather than integers to save space.
+ */
+ private final char[] _rowIndex;
+
+ /**
+ * The key (if any) for a Trie row.
+ * A row may be a leaf, a node or both in the Trie tree.
+ */
+ private final String[] _key;
+
+ /**
+ * The value (if any) for a Trie row.
+ * A row may be a leaf, a node or both in the Trie tree.
+ */
+ private final Object[] _value;
+
+ /**
+ * A big index for each row.
+ * If a character outside of the lookup map is needed,
+ * then a big index will be created for the row, with
+ * 256 entries, one for each possible byte.
+ */
+ private char[][] _bigIndex;
+
+ /**
+ * The number of rows allocated
+ */
+ private char _rows;
+
+ public ArrayTrie()
+ {
+ this(128);
+ }
+
+ public ArrayTrie(int capacityInNodes)
+ {
+ super(true);
+ _value=new Object[capacityInNodes];
+ _rowIndex=new char[capacityInNodes*32];
+ _key=new String[capacityInNodes];
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean put(String s, V v)
+ {
+ int t=0;
+ int k;
+ int limit = s.length();
+ for(k=0; k < limit; k++)
+ {
+ char c=s.charAt(k);
+
+ int index=__lookup[c&0x7f];
+ if (index>=0)
+ {
+ int idx=t*ROW_SIZE+index;
+ t=_rowIndex[idx];
+ if (t==0)
+ {
+ if (++_rows>=_value.length)
+ return false;
+ t=_rowIndex[idx]=_rows;
+ }
+ }
+ else if (c>127)
+ throw new IllegalArgumentException("non ascii character");
+ else
+ {
+ if (_bigIndex==null)
+ _bigIndex=new char[_value.length][];
+ if (t>=_bigIndex.length)
+ return false;
+ char[] big=_bigIndex[t];
+ if (big==null)
+ big=_bigIndex[t]=new char[128];
+ t=big[c];
+ if (t==0)
+ {
+ if (_rows==_value.length)
+ return false;
+ t=big[c]=++_rows;
+ }
+ }
+ }
+ _key[t]=v==null?null:s;
+ V old=(V)_value[t];
+ _value[t] = v;
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V get(String s, int offset, int len)
+ {
+ int t = 0;
+ for(int i=0; i < len; i++)
+ {
+ char c=s.charAt(offset+i);
+ int index=__lookup[c&0x7f];
+ if (index>=0)
+ {
+ int idx=t*ROW_SIZE+index;
+ t=_rowIndex[idx];
+ if (t==0)
+ return null;
+ }
+ else
+ {
+ char[] big = _bigIndex==null?null:_bigIndex[t];
+ if (big==null)
+ return null;
+ t=big[c];
+ if (t==0)
+ return null;
+ }
+ }
+ return (V)_value[t];
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V get(ByteBuffer b,int offset,int len)
+ {
+ int t = 0;
+ for(int i=0; i < len; i++)
+ {
+ byte c=b.get(offset+i);
+ int index=__lookup[c&0x7f];
+ if (index>=0)
+ {
+ int idx=t*ROW_SIZE+index;
+ t=_rowIndex[idx];
+ if (t==0)
+ return null;
+ }
+ else
+ {
+ char[] big = _bigIndex==null?null:_bigIndex[t];
+ if (big==null)
+ return null;
+ t=big[c];
+ if (t==0)
+ return null;
+ }
+ }
+ return (V)_value[t];
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V getBest(byte[] b,int offset,int len)
+ {
+ return getBest(0,b,offset,len);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V getBest(ByteBuffer b,int offset,int len)
+ {
+ if (b.hasArray())
+ return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
+ return getBest(0,b,offset,len);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public V getBest(String s, int offset, int len)
+ {
+ return getBest(0,s,offset,len);
+ }
+
+ /* ------------------------------------------------------------ */
+ private V getBest(int t, String s, int offset, int len)
+ {
+ int pos=offset;
+ for(int i=0; i < len; i++)
+ {
+ char c=s.charAt(pos++);
+ int index=__lookup[c&0x7f];
+ if (index>=0)
+ {
+ int idx=t*ROW_SIZE+index;
+ int nt=_rowIndex[idx];
+ if (nt==0)
+ break;
+ t=nt;
+ }
+ else
+ {
+ char[] big = _bigIndex==null?null:_bigIndex[t];
+ if (big==null)
+ return null;
+ int nt=big[c];
+ if (nt==0)
+ break;
+ t=nt;
+ }
+
+ // Is the next Trie is a match
+ if (_key[t]!=null)
+ {
+ // Recurse so we can remember this possibility
+ V best=getBest(t,s,offset+i+1,len-i-1);
+ if (best!=null)
+ return best;
+ return (V)_value[t];
+ }
+ }
+ return (V)_value[t];
+ }
+
+ /* ------------------------------------------------------------ */
+ private V getBest(int t,byte[] b,int offset,int len)
+ {
+ for(int i=0; i < len; i++)
+ {
+ byte c=b[offset+i];
+ int index=__lookup[c&0x7f];
+ if (index>=0)
+ {
+ int idx=t*ROW_SIZE+index;
+ int nt=_rowIndex[idx];
+ if (nt==0)
+ break;
+ t=nt;
+ }
+ else
+ {
+ char[] big = _bigIndex==null?null:_bigIndex[t];
+ if (big==null)
+ return null;
+ int nt=big[c];
+ if (nt==0)
+ break;
+ t=nt;
+ }
+
+ // Is the next Trie is a match
+ if (_key[t]!=null)
+ {
+ // Recurse so we can remember this possibility
+ V best=getBest(t,b,offset+i+1,len-i-1);
+ if (best!=null)
+ return best;
+ break;
+ }
+ }
+ return (V)_value[t];
+ }
+
+ private V getBest(int t,ByteBuffer b,int offset,int len)
+ {
+ int pos=b.position()+offset;
+ for(int i=0; i < len; i++)
+ {
+ byte c=b.get(pos++);
+ int index=__lookup[c&0x7f];
+ if (index>=0)
+ {
+ int idx=t*ROW_SIZE+index;
+ int nt=_rowIndex[idx];
+ if (nt==0)
+ break;
+ t=nt;
+ }
+ else
+ {
+ char[] big = _bigIndex==null?null:_bigIndex[t];
+ if (big==null)
+ return null;
+ int nt=big[c];
+ if (nt==0)
+ break;
+ t=nt;
+ }
+
+ // Is the next Trie is a match
+ if (_key[t]!=null)
+ {
+ // Recurse so we can remember this possibility
+ V best=getBest(t,b,offset+i+1,len-i-1);
+ if (best!=null)
+ return best;
+ break;
+ }
+ }
+ return (V)_value[t];
+ }
+
+
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder();
+ toString(buf,0);
+
+ if (buf.length()==0)
+ return "{}";
+
+ buf.setCharAt(0,'{');
+ buf.append('}');
+ return buf.toString();
+ }
+
+
+ private <V> void toString(Appendable out, int t)
+ {
+ if (_value[t]!=null)
+ {
+ try
+ {
+ out.append(',');
+ out.append(_key[t]);
+ out.append('=');
+ out.append(_value[t].toString());
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ for(int i=0; i < ROW_SIZE; i++)
+ {
+ int idx=t*ROW_SIZE+i;
+ if (_rowIndex[idx] != 0)
+ toString(out,_rowIndex[idx]);
+ }
+
+ char[] big = _bigIndex==null?null:_bigIndex[t];
+ if (big!=null)
+ {
+ for (int i:big)
+ if (i!=0)
+ toString(out,i);
+ }
+
+ }
+
+ @Override
+ public Set<String> keySet()
+ {
+ Set<String> keys = new HashSet<>();
+ keySet(keys,0);
+ return keys;
+ }
+
+ private void keySet(Set<String> set, int t)
+ {
+ if (t<_value.length&&_value[t]!=null)
+ set.add(_key[t]);
+
+ for(int i=0; i < ROW_SIZE; i++)
+ {
+ int idx=t*ROW_SIZE+i;
+ if (idx<_rowIndex.length && _rowIndex[idx] != 0)
+ keySet(set,_rowIndex[idx]);
+ }
+
+ char[] big = _bigIndex==null||t>=_bigIndex.length?null:_bigIndex[t];
+ if (big!=null)
+ {
+ for (int i:big)
+ if (i!=0)
+ keySet(set,i);
+ }
+ }
+
+ @Override
+ public boolean isFull()
+ {
+ return _rows+1==_key.length;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class ArrayUtil
+ implements Cloneable, Serializable
+{
+
+ /* ------------------------------------------------------------ */
+ public static<T> T[] removeFromArray(T[] array, Object item)
+ {
+ if (item==null || array==null)
+ return array;
+ for (int i=array.length;i-->0;)
+ {
+ if (item.equals(array[i]))
+ {
+ Class<?> c = array==null?item.getClass():array.getClass().getComponentType();
+ @SuppressWarnings("unchecked")
+ T[] na = (T[])Array.newInstance(c, Array.getLength(array)-1);
+ if (i>0)
+ System.arraycopy(array, 0, na, 0, i);
+ if (i+1<array.length)
+ System.arraycopy(array, i+1, na, i, array.length-(i+1));
+ return na;
+ }
+ }
+ return array;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add element to an array
+ * @param array The array to add to (or null)
+ * @param item The item to add
+ * @param type The type of the array (in case of null array)
+ * @return new array with contents of array plus item
+ */
+ public static<T> T[] addToArray(T[] array, T item, Class<?> type)
+ {
+ if (array==null)
+ {
+ if (type==null && item!=null)
+ type= item.getClass();
+ @SuppressWarnings("unchecked")
+ T[] na = (T[])Array.newInstance(type, 1);
+ na[0]=item;
+ return na;
+ }
+ else
+ {
+ T[] na = Arrays.copyOf(array,array.length+1);
+ na[array.length]=item;
+ return na;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add element to the start of an array
+ * @param array The array to add to (or null)
+ * @param item The item to add
+ * @param type The type of the array (in case of null array)
+ * @return new array with contents of array plus item
+ */
+ public static<T> T[] prependToArray(T item, T[] array, Class<?> type)
+ {
+ if (array==null)
+ {
+ if (type==null && item!=null)
+ type= item.getClass();
+ @SuppressWarnings("unchecked")
+ T[] na = (T[])Array.newInstance(type, 1);
+ na[0]=item;
+ return na;
+ }
+ else
+ {
+ Class<?> c = array.getClass().getComponentType();
+ @SuppressWarnings("unchecked")
+ T[] na = (T[])Array.newInstance(c, Array.getLength(array)+1);
+ System.arraycopy(array, 0, na, 1, array.length);
+ na[0]=item;
+ return na;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param array Any array of object
+ * @return A new <i>modifiable</i> list initialised with the elements from <code>array</code>.
+ */
+ public static<E> List<E> asMutableList(E[] array)
+ {
+ if (array==null || array.length==0)
+ return new ArrayList<E>();
+ return new ArrayList<E>(Arrays.asList(array));
+ }
+
+ /* ------------------------------------------------------------ */
+ public static <T> T[] removeNulls(T[] array)
+ {
+ for (T t : array)
+ {
+ if (t==null)
+ {
+ List<T> list = new ArrayList<>();
+ for (T t2:array)
+ if (t2!=null)
+ list.add(t2);
+ return list.toArray(Arrays.copyOf(array,list.size()));
+ }
+ }
+ return array;
+ }
+
+}
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Atomics
+{
+ private Atomics()
+ {
+ }
+
+ public static void updateMin(AtomicLong currentMin, long newValue)
+ {
+ long oldValue = currentMin.get();
+ while (newValue < oldValue)
+ {
+ if (currentMin.compareAndSet(oldValue, newValue))
+ break;
+ oldValue = currentMin.get();
+ }
+ }
+
+ public static void updateMax(AtomicLong currentMax, long newValue)
+ {
+ long oldValue = currentMax.get();
+ while (newValue > oldValue)
+ {
+ if (currentMax.compareAndSet(oldValue, newValue))
+ break;
+ oldValue = currentMax.get();
+ }
+ }
+
+ public static void updateMin(AtomicInteger currentMin, int newValue)
+ {
+ int oldValue = currentMin.get();
+ while (newValue < oldValue)
+ {
+ if (currentMin.compareAndSet(oldValue, newValue))
+ break;
+ oldValue = currentMin.get();
+ }
+ }
+
+ public static void updateMax(AtomicInteger currentMax, int newValue)
+ {
+ int oldValue = currentMax.get();
+ while (newValue > oldValue)
+ {
+ if (currentMax.compareAndSet(oldValue, newValue))
+ break;
+ oldValue = currentMax.get();
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.Enumeration;
+
+/* ------------------------------------------------------------ */
+/** Attributes.
+ * Interface commonly used for storing attributes.
+ *
+ *
+ */
+public interface Attributes
+{
+ public void removeAttribute(String name);
+ public void setAttribute(String name, Object attribute);
+ public Object getAttribute(String name);
+ public Enumeration<String> getAttributeNames();
+ public void clearAttributes();
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class AttributesMap implements Attributes
+{
+ private final AtomicReference<ConcurrentMap<String, Object>> _map = new AtomicReference<>();
+
+ public AttributesMap()
+ {
+ }
+
+ public AttributesMap(AttributesMap attributes)
+ {
+ ConcurrentMap<String, Object> map = attributes.map();
+ if (map != null)
+ _map.set(new ConcurrentHashMap<>(map));
+ }
+
+ private ConcurrentMap<String, Object> map()
+ {
+ return _map.get();
+ }
+
+ private ConcurrentMap<String, Object> ensureMap()
+ {
+ while (true)
+ {
+ ConcurrentMap<String, Object> map = map();
+ if (map != null)
+ return map;
+ map = new ConcurrentHashMap<>();
+ if (_map.compareAndSet(null, map))
+ return map;
+ }
+ }
+
+ @Override
+ public void removeAttribute(String name)
+ {
+ Map<String, Object> map = map();
+ if (map != null)
+ map.remove(name);
+ }
+
+ @Override
+ public void setAttribute(String name, Object attribute)
+ {
+ if (attribute == null)
+ removeAttribute(name);
+ else
+ ensureMap().put(name, attribute);
+ }
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ Map<String, Object> map = map();
+ return map == null ? null : map.get(name);
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames()
+ {
+ return Collections.enumeration(getAttributeNameSet());
+ }
+
+ public Set<String> getAttributeNameSet()
+ {
+ return keySet();
+ }
+
+ public Set<Map.Entry<String, Object>> getAttributeEntrySet()
+ {
+ Map<String, Object> map = map();
+ return map == null ? Collections.<Map.Entry<String, Object>>emptySet() : map.entrySet();
+ }
+
+ public static Enumeration<String> getAttributeNamesCopy(Attributes attrs)
+ {
+ if (attrs instanceof AttributesMap)
+ return Collections.enumeration(((AttributesMap)attrs).keySet());
+
+ List<String> names = new ArrayList<>();
+ names.addAll(Collections.list(attrs.getAttributeNames()));
+ return Collections.enumeration(names);
+ }
+
+ @Override
+ public void clearAttributes()
+ {
+ Map<String, Object> map = map();
+ if (map != null)
+ map.clear();
+ }
+
+ public int size()
+ {
+ Map<String, Object> map = map();
+ return map == null ? 0 : map.size();
+ }
+
+ @Override
+ public String toString()
+ {
+ Map<String, Object> map = map();
+ return map == null ? "{}" : map.toString();
+ }
+
+ private Set<String> keySet()
+ {
+ Map<String, Object> map = map();
+ return map == null ? Collections.<String>emptySet() : map.keySet();
+ }
+
+ public void addAll(Attributes attributes)
+ {
+ Enumeration<String> e = attributes.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ String name = e.nextElement();
+ setAttribute(name, attributes.getAttribute(name));
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+
+
+/** Fast B64 Encoder/Decoder as described in RFC 1421.
+ * <p>Does not insert or interpret whitespace as described in RFC
+ * 1521. If you require this you must pre/post process your data.
+ * <p> Note that in a web context the usual case is to not want
+ * linebreaks or other white space in the encoded output.
+ *
+ */
+public class B64Code
+{
+ private static final char __pad='=';
+ private static final char[] __rfc1421alphabet=
+ {
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
+ 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
+ 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
+ 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
+ };
+
+ private static final byte[] __rfc1421nibbles;
+ static
+ {
+ __rfc1421nibbles=new byte[256];
+ for (int i=0;i<256;i++)
+ __rfc1421nibbles[i]=-1;
+ for (byte b=0;b<64;b++)
+ __rfc1421nibbles[(byte)__rfc1421alphabet[b]]=b;
+ __rfc1421nibbles[(byte)__pad]=0;
+ }
+
+ private B64Code()
+ {
+ }
+
+ /**
+ * Base 64 encode as described in RFC 1421.
+ * <p>Does not insert whitespace as described in RFC 1521.
+ * @param s String to encode.
+ * @return String containing the encoded form of the input.
+ */
+ public static String encode(String s)
+ {
+ return encode(s, (Charset)null);
+ }
+
+ /**
+ * Base 64 encode as described in RFC 1421.
+ * <p>Does not insert whitespace as described in RFC 1521.
+ * @param s String to encode.
+ * @param charEncoding String representing the name of
+ * the character encoding of the provided input String.
+ * @return String containing the encoded form of the input.
+ */
+ public static String encode(String s,String charEncoding)
+ {
+ byte[] bytes;
+ if (charEncoding==null)
+ bytes=s.getBytes(StandardCharsets.ISO_8859_1);
+ else
+ bytes=s.getBytes(Charset.forName(charEncoding));
+ return new String(encode(bytes));
+ }
+
+ /**
+ * Base 64 encode as described in RFC 1421.
+ * <p>Does not insert whitespace as described in RFC 1521.
+ * @param s String to encode.
+ * @param charEncoding The character encoding of the provided input String.
+ * @return String containing the encoded form of the input.
+ */
+ public static String encode(String s, Charset charEncoding)
+ {
+ byte[] bytes=s.getBytes(charEncoding==null ? StandardCharsets.ISO_8859_1 : charEncoding);
+ return new String(encode(bytes));
+ }
+
+ /**
+ * Fast Base 64 encode as described in RFC 1421.
+ * <p>Does not insert whitespace as described in RFC 1521.
+ * <p> Avoids creating extra copies of the input/output.
+ * @param b byte array to encode.
+ * @return char array containing the encoded form of the input.
+ */
+ public static char[] encode(byte[] b)
+ {
+ if (b==null)
+ return null;
+
+ int bLen=b.length;
+ int cLen=((bLen+2)/3)*4;
+ char c[]=new char[cLen];
+ int ci=0;
+ int bi=0;
+ byte b0, b1, b2;
+ int stop=(bLen/3)*3;
+ while (bi<stop)
+ {
+ b0=b[bi++];
+ b1=b[bi++];
+ b2=b[bi++];
+ c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+ c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+ c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
+ c[ci++]=__rfc1421alphabet[b2&0x3f];
+ }
+
+ if (bLen!=bi)
+ {
+ switch (bLen%3)
+ {
+ case 2:
+ b0=b[bi++];
+ b1=b[bi++];
+ c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+ c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+ c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
+ c[ci++]=__pad;
+ break;
+
+ case 1:
+ b0=b[bi++];
+ c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+ c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
+ c[ci++]=__pad;
+ c[ci++]=__pad;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return c;
+ }
+
+ /**
+ * Fast Base 64 encode as described in RFC 1421 and RFC2045
+ * <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true.
+ * <p> Avoids creating extra copies of the input/output.
+ * @param b byte array to encode.
+ * @param rfc2045 If true, break lines at 76 characters with CRLF
+ * @return char array containing the encoded form of the input.
+ */
+ public static char[] encode(byte[] b, boolean rfc2045)
+ {
+ if (b==null)
+ return null;
+ if (!rfc2045)
+ return encode(b);
+
+ int bLen=b.length;
+ int cLen=((bLen+2)/3)*4;
+ cLen+=2+2*(cLen/76);
+ char c[]=new char[cLen];
+ int ci=0;
+ int bi=0;
+ byte b0, b1, b2;
+ int stop=(bLen/3)*3;
+ int l=0;
+ while (bi<stop)
+ {
+ b0=b[bi++];
+ b1=b[bi++];
+ b2=b[bi++];
+ c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+ c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+ c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
+ c[ci++]=__rfc1421alphabet[b2&0x3f];
+ l+=4;
+ if (l%76==0)
+ {
+ c[ci++]=13;
+ c[ci++]=10;
+ }
+ }
+
+ if (bLen!=bi)
+ {
+ switch (bLen%3)
+ {
+ case 2:
+ b0=b[bi++];
+ b1=b[bi++];
+ c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+ c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+ c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
+ c[ci++]=__pad;
+ break;
+
+ case 1:
+ b0=b[bi++];
+ c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+ c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
+ c[ci++]=__pad;
+ c[ci++]=__pad;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ c[ci++]=13;
+ c[ci++]=10;
+ return c;
+ }
+
+ /**
+ * Base 64 decode as described in RFC 2045.
+ * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+ * @param encoded String to decode.
+ * @param charEncoding String representing the character encoding
+ * used to map the decoded bytes into a String.
+ * @return String decoded byte array.
+ * @throws UnsupportedCharsetException if the encoding is not supported
+ * @throws IllegalArgumentException if the input is not a valid
+ * B64 encoding.
+ */
+ public static String decode(String encoded,String charEncoding)
+ {
+ byte[] decoded=decode(encoded);
+ if (charEncoding==null)
+ return new String(decoded);
+ return new String(decoded,Charset.forName(charEncoding));
+ }
+
+ /**
+ * Base 64 decode as described in RFC 2045.
+ * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+ * @param encoded String to decode.
+ * @param charEncoding Character encoding
+ * used to map the decoded bytes into a String.
+ * @return String decoded byte array.
+ * @throws IllegalArgumentException if the input is not a valid
+ * B64 encoding.
+ */
+ public static String decode(String encoded, Charset charEncoding)
+ {
+ byte[] decoded=decode(encoded);
+ if (charEncoding==null)
+ return new String(decoded);
+ return new String(decoded, charEncoding);
+ }
+
+ /**
+ * Fast Base 64 decode as described in RFC 1421.
+ *
+ * <p>Unlike other decode methods, this does not attempt to
+ * cope with extra whitespace as described in RFC 1521/2045.
+ * <p> Avoids creating extra copies of the input/output.
+ * <p> Note this code has been flattened for performance.
+ * @param b char array to decode.
+ * @return byte array containing the decoded form of the input.
+ * @throws IllegalArgumentException if the input is not a valid
+ * B64 encoding.
+ */
+ public static byte[] decode(char[] b)
+ {
+ if (b==null)
+ return null;
+
+ int bLen=b.length;
+ if (bLen%4!=0)
+ throw new IllegalArgumentException("Input block size is not 4");
+
+ int li=bLen-1;
+ while (li>=0 && b[li]==(byte)__pad)
+ li--;
+
+ if (li<0)
+ return new byte[0];
+
+ // Create result array of exact required size.
+ int rLen=((li+1)*3)/4;
+ byte r[]=new byte[rLen];
+ int ri=0;
+ int bi=0;
+ int stop=(rLen/3)*3;
+ byte b0,b1,b2,b3;
+ try
+ {
+ while (ri<stop)
+ {
+ b0=__rfc1421nibbles[b[bi++]];
+ b1=__rfc1421nibbles[b[bi++]];
+ b2=__rfc1421nibbles[b[bi++]];
+ b3=__rfc1421nibbles[b[bi++]];
+ if (b0<0 || b1<0 || b2<0 || b3<0)
+ throw new IllegalArgumentException("Not B64 encoded");
+
+ r[ri++]=(byte)(b0<<2|b1>>>4);
+ r[ri++]=(byte)(b1<<4|b2>>>2);
+ r[ri++]=(byte)(b2<<6|b3);
+ }
+
+ if (rLen!=ri)
+ {
+ switch (rLen%3)
+ {
+ case 2:
+ b0=__rfc1421nibbles[b[bi++]];
+ b1=__rfc1421nibbles[b[bi++]];
+ b2=__rfc1421nibbles[b[bi++]];
+ if (b0<0 || b1<0 || b2<0)
+ throw new IllegalArgumentException("Not B64 encoded");
+ r[ri++]=(byte)(b0<<2|b1>>>4);
+ r[ri++]=(byte)(b1<<4|b2>>>2);
+ break;
+
+ case 1:
+ b0=__rfc1421nibbles[b[bi++]];
+ b1=__rfc1421nibbles[b[bi++]];
+ if (b0<0 || b1<0)
+ throw new IllegalArgumentException("Not B64 encoded");
+ r[ri++]=(byte)(b0<<2|b1>>>4);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ catch (IndexOutOfBoundsException e)
+ {
+ throw new IllegalArgumentException("char "+bi
+ +" was not B64 encoded");
+ }
+
+ return r;
+ }
+
+ /**
+ * Base 64 decode as described in RFC 2045.
+ * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+ * @param encoded String to decode.
+ * @return byte array containing the decoded form of the input.
+ * @throws IllegalArgumentException if the input is not a valid
+ * B64 encoding.
+ */
+ public static byte[] decode(String encoded)
+ {
+ if (encoded==null)
+ return null;
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream(4*encoded.length()/3);
+ decode(encoded, bout);
+ return bout.toByteArray();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Base 64 decode as described in RFC 2045.
+ * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+ * @param encoded String to decode.
+ * @param bout stream for decoded bytes
+ * @throws IllegalArgumentException if the input is not a valid
+ * B64 encoding.
+ */
+ static public void decode (String encoded, ByteArrayOutputStream bout)
+ {
+ if (encoded==null)
+ return;
+
+ if (bout == null)
+ throw new IllegalArgumentException("No outputstream for decoded bytes");
+
+ int ci=0;
+ byte nibbles[] = new byte[4];
+ int s=0;
+
+ while (ci<encoded.length())
+ {
+ char c=encoded.charAt(ci++);
+
+ if (c==__pad)
+ break;
+
+ if (Character.isWhitespace(c))
+ continue;
+
+ byte nibble=__rfc1421nibbles[c];
+ if (nibble<0)
+ throw new IllegalArgumentException("Not B64 encoded");
+
+ nibbles[s++]=__rfc1421nibbles[c];
+
+ switch(s)
+ {
+ case 1:
+ break;
+ case 2:
+ bout.write(nibbles[0]<<2|nibbles[1]>>>4);
+ break;
+ case 3:
+ bout.write(nibbles[1]<<4|nibbles[2]>>>2);
+ break;
+ case 4:
+ bout.write(nibbles[2]<<6|nibbles[3]);
+ s=0;
+ break;
+ }
+
+ }
+
+ return;
+ }
+
+
+ public static void encode(int value,Appendable buf) throws IOException
+ {
+ buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4)]);
+ buf.append('=');
+ }
+
+ public static void encode(long lvalue,Appendable buf) throws IOException
+ {
+ int value=(int)(0xFFFFFFFC&(lvalue>>32));
+ buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
+
+ buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4) + (0xf&(int)(lvalue>>28))]);
+
+ value=0x0FFFFFFF&(int)lvalue;
+ buf.append(__rfc1421alphabet[0x3f&((0x0FC00000&value)>>22)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x003F0000&value)>>16)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x0000FC00&value)>>10)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x000003F0&value)>>4)]);
+ buf.append(__rfc1421alphabet[0x3f&((0x0000000F&value)<<2)]);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.AbstractList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A BlockingQueue backed by a circular array capable or growing.
+ * <p/>
+ * This queue is uses a variant of the two lock queue algorithm to provide an efficient queue or list backed by a growable circular array.
+ * <p/>
+ * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is able to grow and provides a blocking put call.
+ * <p/>
+ * The queue has both a capacity (the size of the array currently allocated) and a max capacity (the maximum size that may be allocated), which defaults to
+ * {@link Integer#MAX_VALUE}.
+ *
+ * @param <E>
+ * The element type
+ */
+public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQueue<E>
+{
+ /**
+ * The head offset in the {@link #_indexes} array, displaced by 15 slots to avoid false sharing with the array length (stored before the first element of
+ * the array itself).
+ */
+ private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
+ /**
+ * The tail offset in the {@link #_indexes} array, displaced by 16 slots from the head to avoid false sharing with it.
+ */
+ private static final int TAIL_OFFSET = HEAD_OFFSET + MemoryUtils.getIntegersPerCacheLine();
+ /**
+ * Default initial capacity, 128.
+ */
+ public static final int DEFAULT_CAPACITY = 128;
+ /**
+ * Default growth factor, 64.
+ */
+ public static final int DEFAULT_GROWTH = 64;
+
+ private final int _maxCapacity;
+ private final int _growCapacity;
+ /**
+ * Array that holds the head and tail indexes, separated by a cache line to avoid false sharing
+ */
+ private final int[] _indexes = new int[TAIL_OFFSET + 1];
+ private final Lock _tailLock = new ReentrantLock();
+ private final AtomicInteger _size = new AtomicInteger();
+ private final Lock _headLock = new ReentrantLock();
+ private final Condition _notEmpty = _headLock.newCondition();
+ private Object[] _elements;
+
+ /**
+ * Creates an unbounded {@link BlockingArrayQueue} with default initial capacity and grow factor.
+ *
+ * @see #DEFAULT_CAPACITY
+ * @see #DEFAULT_GROWTH
+ */
+ public BlockingArrayQueue()
+ {
+ _elements = new Object[DEFAULT_CAPACITY];
+ _growCapacity = DEFAULT_GROWTH;
+ _maxCapacity = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Creates a bounded {@link BlockingArrayQueue} that does not grow. The capacity of the queue is fixed and equal to the given parameter.
+ *
+ * @param maxCapacity
+ * the maximum capacity
+ */
+ public BlockingArrayQueue(int maxCapacity)
+ {
+ _elements = new Object[maxCapacity];
+ _growCapacity = -1;
+ _maxCapacity = maxCapacity;
+ }
+
+ /**
+ * Creates an unbounded {@link BlockingArrayQueue} that grows by the given parameter.
+ *
+ * @param capacity
+ * the initial capacity
+ * @param growBy
+ * the growth factor
+ */
+ public BlockingArrayQueue(int capacity, int growBy)
+ {
+ _elements = new Object[capacity];
+ _growCapacity = growBy;
+ _maxCapacity = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Create a bounded {@link BlockingArrayQueue} that grows by the given parameter.
+ *
+ * @param capacity
+ * the initial capacity
+ * @param growBy
+ * the growth factor
+ * @param maxCapacity
+ * the maximum capacity
+ */
+ public BlockingArrayQueue(int capacity, int growBy, int maxCapacity)
+ {
+ if (capacity > maxCapacity)
+ throw new IllegalArgumentException();
+ _elements = new Object[capacity];
+ _growCapacity = growBy;
+ _maxCapacity = maxCapacity;
+ }
+
+ /*----------------------------------------------------------------------------*/
+ /* Collection methods */
+ /*----------------------------------------------------------------------------*/
+
+ @Override
+ public void clear()
+ {
+
+ _tailLock.lock();
+ try
+ {
+
+ _headLock.lock();
+ try
+ {
+ _indexes[HEAD_OFFSET] = 0;
+ _indexes[TAIL_OFFSET] = 0;
+ _size.set(0);
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ @Override
+ public int size()
+ {
+ return _size.get();
+ }
+
+ @Override
+ public Iterator<E> iterator()
+ {
+ return listIterator();
+ }
+
+ /*----------------------------------------------------------------------------*/
+ /* Queue methods */
+ /*----------------------------------------------------------------------------*/
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public E poll()
+ {
+ if (_size.get() == 0)
+ return null;
+
+ E e = null;
+
+ _headLock.lock(); // Size cannot shrink
+ try
+ {
+ if (_size.get() > 0)
+ {
+ final int head = _indexes[HEAD_OFFSET];
+ e = (E)_elements[head];
+ _elements[head] = null;
+ _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+ if (_size.decrementAndGet() > 0)
+ _notEmpty.signal();
+ }
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ return e;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public E peek()
+ {
+ if (_size.get() == 0)
+ return null;
+
+ E e = null;
+
+ _headLock.lock(); // Size cannot shrink
+ try
+ {
+ if (_size.get() > 0)
+ e = (E)_elements[_indexes[HEAD_OFFSET]];
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ return e;
+ }
+
+ @Override
+ public E remove()
+ {
+ E e = poll();
+ if (e == null)
+ throw new NoSuchElementException();
+ return e;
+ }
+
+ @Override
+ public E element()
+ {
+ E e = peek();
+ if (e == null)
+ throw new NoSuchElementException();
+ return e;
+ }
+
+ /*----------------------------------------------------------------------------*/
+ /* BlockingQueue methods */
+ /*----------------------------------------------------------------------------*/
+
+ @Override
+ public boolean offer(E e)
+ {
+ Objects.requireNonNull(e);
+
+ boolean notEmpty = false;
+ _tailLock.lock(); // Size cannot grow... only shrink
+ try
+ {
+ int size = _size.get();
+ if (size >= _maxCapacity)
+ return false;
+
+ // Should we expand array?
+ if (size == _elements.length)
+ {
+ _headLock.lock();
+ try
+ {
+ if (!grow())
+ return false;
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+
+ // Re-read head and tail after a possible grow
+ int tail = _indexes[TAIL_OFFSET];
+ _elements[tail] = e;
+ _indexes[TAIL_OFFSET] = (tail + 1) % _elements.length;
+ notEmpty = _size.getAndIncrement() == 0;
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+
+ if (notEmpty)
+ {
+ _headLock.lock();
+ try
+ {
+ _notEmpty.signal();
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean add(E e)
+ {
+ if (offer(e))
+ return true;
+ else
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public void put(E o) throws InterruptedException
+ {
+ // The mechanism to await and signal when the queue is full is not implemented
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException
+ {
+ // The mechanism to await and signal when the queue is full is not implemented
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public E take() throws InterruptedException
+ {
+ E e = null;
+
+ _headLock.lockInterruptibly(); // Size cannot shrink
+ try
+ {
+ try
+ {
+ while (_size.get() == 0)
+ {
+ _notEmpty.await();
+ }
+ }
+ catch (InterruptedException ie)
+ {
+ _notEmpty.signal();
+ throw ie;
+ }
+
+ final int head = _indexes[HEAD_OFFSET];
+ e = (E)_elements[head];
+ _elements[head] = null;
+ _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+
+ if (_size.decrementAndGet() > 0)
+ _notEmpty.signal();
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ return e;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public E poll(long time, TimeUnit unit) throws InterruptedException
+ {
+ long nanos = unit.toNanos(time);
+ E e = null;
+
+ _headLock.lockInterruptibly(); // Size cannot shrink
+ try
+ {
+ try
+ {
+ while (_size.get() == 0)
+ {
+ if (nanos <= 0)
+ return null;
+ nanos = _notEmpty.awaitNanos(nanos);
+ }
+ }
+ catch (InterruptedException x)
+ {
+ _notEmpty.signal();
+ throw x;
+ }
+
+ int head = _indexes[HEAD_OFFSET];
+ e = (E)_elements[head];
+ _elements[head] = null;
+ _indexes[HEAD_OFFSET] = (head + 1) % _elements.length;
+
+ if (_size.decrementAndGet() > 0)
+ _notEmpty.signal();
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ return e;
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+
+ _tailLock.lock();
+ try
+ {
+
+ _headLock.lock();
+ try
+ {
+ if (isEmpty())
+ return false;
+
+ final int head = _indexes[HEAD_OFFSET];
+ final int tail = _indexes[TAIL_OFFSET];
+ final int capacity = _elements.length;
+
+ int i = head;
+ while (true)
+ {
+ if (Objects.equals(_elements[i],o))
+ {
+ remove(i >= head?i - head:capacity - head + i);
+ return true;
+ }
+ ++i;
+ if (i == capacity)
+ i = 0;
+ if (i == tail)
+ return false;
+ }
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ @Override
+ public int remainingCapacity()
+ {
+
+ _tailLock.lock();
+ try
+ {
+
+ _headLock.lock();
+ try
+ {
+ return getCapacity() - size();
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ @Override
+ public int drainTo(Collection<? super E> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int drainTo(Collection<? super E> c, int maxElements)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /*----------------------------------------------------------------------------*/
+ /* List methods */
+ /*----------------------------------------------------------------------------*/
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public E get(int index)
+ {
+
+ _tailLock.lock();
+ try
+ {
+
+ _headLock.lock();
+ try
+ {
+ if (index < 0 || index >= _size.get())
+ throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+ int i = _indexes[HEAD_OFFSET] + index;
+ int capacity = _elements.length;
+ if (i >= capacity)
+ i -= capacity;
+ return (E)_elements[i];
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ @Override
+ public void add(int index, E e)
+ {
+ if (e == null)
+ throw new NullPointerException();
+
+ _tailLock.lock();
+ try
+ {
+
+ _headLock.lock();
+ try
+ {
+ final int size = _size.get();
+
+ if (index < 0 || index > size)
+ throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+ if (index == size)
+ {
+ add(e);
+ }
+ else
+ {
+ if (_indexes[TAIL_OFFSET] == _indexes[HEAD_OFFSET])
+ if (!grow())
+ throw new IllegalStateException("full");
+
+ // Re-read head and tail after a possible grow
+ int i = _indexes[HEAD_OFFSET] + index;
+ int capacity = _elements.length;
+
+ if (i >= capacity)
+ i -= capacity;
+
+ _size.incrementAndGet();
+ int tail = _indexes[TAIL_OFFSET];
+ _indexes[TAIL_OFFSET] = tail = (tail + 1) % capacity;
+
+ if (i < tail)
+ {
+ System.arraycopy(_elements,i,_elements,i + 1,tail - i);
+ _elements[i] = e;
+ }
+ else
+ {
+ if (tail > 0)
+ {
+ System.arraycopy(_elements,0,_elements,1,tail);
+ _elements[0] = _elements[capacity - 1];
+ }
+
+ System.arraycopy(_elements,i,_elements,i + 1,capacity - i - 1);
+ _elements[i] = e;
+ }
+ }
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public E set(int index, E e)
+ {
+ Objects.requireNonNull(e);
+
+ _tailLock.lock();
+ try
+ {
+
+ _headLock.lock();
+ try
+ {
+ if (index < 0 || index >= _size.get())
+ throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+ int i = _indexes[HEAD_OFFSET] + index;
+ int capacity = _elements.length;
+ if (i >= capacity)
+ i -= capacity;
+ E old = (E)_elements[i];
+ _elements[i] = e;
+ return old;
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public E remove(int index)
+ {
+
+ _tailLock.lock();
+ try
+ {
+
+ _headLock.lock();
+ try
+ {
+ if (index < 0 || index >= _size.get())
+ throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+ int i = _indexes[HEAD_OFFSET] + index;
+ int capacity = _elements.length;
+ if (i >= capacity)
+ i -= capacity;
+ E old = (E)_elements[i];
+
+ int tail = _indexes[TAIL_OFFSET];
+ if (i < tail)
+ {
+ System.arraycopy(_elements,i + 1,_elements,i,tail - i);
+ --_indexes[TAIL_OFFSET];
+ }
+ else
+ {
+ System.arraycopy(_elements,i + 1,_elements,i,capacity - i - 1);
+ _elements[capacity - 1] = _elements[0];
+ if (tail > 0)
+ {
+ System.arraycopy(_elements,1,_elements,0,tail);
+ --_indexes[TAIL_OFFSET];
+ }
+ else
+ {
+ _indexes[TAIL_OFFSET] = capacity - 1;
+ }
+ _elements[_indexes[TAIL_OFFSET]] = null;
+ }
+
+ _size.decrementAndGet();
+
+ return old;
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ @Override
+ public ListIterator<E> listIterator(int index)
+ {
+
+ _tailLock.lock();
+ try
+ {
+
+ _headLock.lock();
+ try
+ {
+ Object[] elements = new Object[size()];
+ if (size() > 0)
+ {
+ int head = _indexes[HEAD_OFFSET];
+ int tail = _indexes[TAIL_OFFSET];
+ if (head < tail)
+ {
+ System.arraycopy(_elements,head,elements,0,tail - head);
+ }
+ else
+ {
+ int chunk = _elements.length - head;
+ System.arraycopy(_elements,head,elements,0,chunk);
+ System.arraycopy(_elements,0,elements,chunk,tail);
+ }
+ }
+ return new Itr(elements,index);
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ /*----------------------------------------------------------------------------*/
+ /* Additional methods */
+ /*----------------------------------------------------------------------------*/
+
+ /**
+ * @return the current capacity of this queue
+ */
+ public int getCapacity()
+ {
+ _tailLock.lock();
+ try
+ {
+ return _elements.length;
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ /**
+ * @return the max capacity of this queue, or -1 if this queue is unbounded
+ */
+ public int getMaxCapacity()
+ {
+ return _maxCapacity;
+ }
+
+ /*----------------------------------------------------------------------------*/
+ /* Implementation methods */
+ /*----------------------------------------------------------------------------*/
+
+ private boolean grow()
+ {
+ if (_growCapacity <= 0)
+ return false;
+
+ _tailLock.lock();
+ try
+ {
+
+ _headLock.lock();
+ try
+ {
+ final int head = _indexes[HEAD_OFFSET];
+ final int tail = _indexes[TAIL_OFFSET];
+ final int newTail;
+ final int capacity = _elements.length;
+
+ Object[] elements = new Object[capacity + _growCapacity];
+
+ if (head < tail)
+ {
+ newTail = tail - head;
+ System.arraycopy(_elements,head,elements,0,newTail);
+ }
+ else if (head > tail || _size.get() > 0)
+ {
+ newTail = capacity + tail - head;
+ int cut = capacity - head;
+ System.arraycopy(_elements,head,elements,0,cut);
+ System.arraycopy(_elements,0,elements,cut,tail);
+ }
+ else
+ {
+ newTail = 0;
+ }
+
+ _elements = elements;
+ _indexes[HEAD_OFFSET] = 0;
+ _indexes[TAIL_OFFSET] = newTail;
+ return true;
+ }
+ finally
+ {
+ _headLock.unlock();
+ }
+ }
+ finally
+ {
+ _tailLock.unlock();
+ }
+ }
+
+ private class Itr implements ListIterator<E>
+ {
+ private final Object[] _elements;
+ private int _cursor;
+
+ public Itr(Object[] elements, int offset)
+ {
+ _elements = elements;
+ _cursor = offset;
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return _cursor < _elements.length;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public E next()
+ {
+ return (E)_elements[_cursor++];
+ }
+
+ @Override
+ public boolean hasPrevious()
+ {
+ return _cursor > 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public E previous()
+ {
+ return (E)_elements[--_cursor];
+ }
+
+ @Override
+ public int nextIndex()
+ {
+ return _cursor + 1;
+ }
+
+ @Override
+ public int previousIndex()
+ {
+ return _cursor - 1;
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void set(E e)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void add(E e)
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+/* ------------------------------------------------------------ */
+/**
+ * An implementation of Callback that blocks until success or failure.
+ */
+public class BlockingCallback implements Callback
+{
+ private static final Logger LOG = Log.getLogger(BlockingCallback.class);
+
+ private static Throwable SUCCEEDED=new Throwable()
+ {
+ @Override
+ public String toString() { return "SUCCEEDED"; }
+ };
+
+ private final CountDownLatch _latch = new CountDownLatch(1);
+ private final AtomicReference<Throwable> _state = new AtomicReference<>();
+
+ public BlockingCallback()
+ {}
+
+ @Override
+ public void succeeded()
+ {
+ if (_state.compareAndSet(null,SUCCEEDED))
+ _latch.countDown();
+ }
+
+ @Override
+ public void failed(Throwable cause)
+ {
+ if (_state.compareAndSet(null,cause))
+ _latch.countDown();
+ }
+
+ /** Block until the Callback has succeeded or failed and
+ * after the return leave in the state to allow reuse.
+ * This is useful for code that wants to repeatable use a FutureCallback to convert
+ * an asynchronous API to a blocking API.
+ * @throws IOException if exception was caught during blocking, or callback was cancelled
+ */
+ public void block() throws IOException
+ {
+ if (NonBlockingThread.isNonBlockingThread())
+ LOG.warn("Blocking a NonBlockingThread: ",new Throwable());
+
+ try
+ {
+ _latch.await();
+ Throwable state=_state.get();
+ if (state==SUCCEEDED)
+ return;
+ if (state instanceof IOException)
+ throw (IOException) state;
+ if (state instanceof CancellationException)
+ throw (CancellationException) state;
+ throw new IOException(state);
+ }
+ catch (final InterruptedException e)
+ {
+ throw new InterruptedIOException(){{initCause(e);}};
+ }
+ finally
+ {
+ _state.set(null);
+ }
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s}",BlockingCallback.class.getSimpleName(),hashCode(),_state.get());
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.Buffer;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------------------------- */
+/**
+ * Buffer utility methods.
+ * <p>The standard JVM {@link ByteBuffer} can exist in two modes: In fill mode the valid
+ * data is between 0 and pos; In flush mode the valid data is between the pos and the limit.
+ * The various ByteBuffer methods assume a mode and some of them will switch or enforce a mode:
+ * Allocate and clear set fill mode; flip and compact switch modes; read and write assume fill
+ * and flush modes. This duality can result in confusing code such as:
+ * <pre>
+ * buffer.clear();
+ * channel.write(buffer);
+ * </pre>
+ * Which looks as if it should write no data, but in fact writes the buffer worth of garbage.
+ * </p>
+ * <p>
+ * The BufferUtil class provides a set of utilities that operate on the convention that ByteBuffers
+ * will always be left, passed in an API or returned from a method in the flush mode - ie with
+ * valid data between the pos and limit. This convention is adopted so as to avoid confusion as to
+ * what state a buffer is in and to avoid excessive copying of data that can result with the usage
+ * of compress.</p>
+ * <p>
+ * Thus this class provides alternate implementations of {@link #allocate(int)},
+ * {@link #allocateDirect(int)} and {@link #clear(ByteBuffer)} that leave the buffer
+ * in flush mode. Thus the following tests will pass:<pre>
+ * ByteBuffer buffer = BufferUtil.allocate(1024);
+ * assert(buffer.remaining()==0);
+ * BufferUtil.clear(buffer);
+ * assert(buffer.remaining()==0);
+ * </pre>
+ * </p>
+ * <p>If the BufferUtil methods {@link #fill(ByteBuffer, byte[], int, int)},
+ * {@link #append(ByteBuffer, byte[], int, int)} or {@link #put(ByteBuffer, ByteBuffer)} are used,
+ * then the caller does not need to explicitly switch the buffer to fill mode.
+ * If the caller wishes to use other ByteBuffer bases libraries to fill a buffer,
+ * then they can use explicit calls of #flipToFill(ByteBuffer) and #flipToFlush(ByteBuffer, int)
+ * to change modes. Note because this convention attempts to avoid the copies of compact, the position
+ * is not set to zero on each fill cycle and so its value must be remembered:
+ * <pre>
+ * int pos = BufferUtil.flipToFill(buffer);
+ * try
+ * {
+ * buffer.put(data);
+ * }
+ * finally
+ * {
+ * flipToFlush(buffer, pos);
+ * }
+ * </pre>
+ * The flipToFill method will effectively clear the buffer if it is emtpy and will compact the buffer if there is no space.
+ *
+ */
+public class BufferUtil
+{
+ static final int TEMP_BUFFER_SIZE = 4096;
+ static final byte SPACE = 0x20;
+ static final byte MINUS = '-';
+ static final byte[] DIGIT =
+ {(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D',
+ (byte)'E', (byte)'F'};
+
+ public static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]);
+
+ /* ------------------------------------------------------------ */
+ /** Allocate ByteBuffer in flush mode.
+ * The position and limit will both be zero, indicating that the buffer is
+ * empty and must be flipped before any data is put to it.
+ * @param capacity capacity of the allocated ByteBuffer
+ * @return Buffer
+ */
+ public static ByteBuffer allocate(int capacity)
+ {
+ ByteBuffer buf = ByteBuffer.allocate(capacity);
+ buf.limit(0);
+ return buf;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Allocate ByteBuffer in flush mode.
+ * The position and limit will both be zero, indicating that the buffer is
+ * empty and in flush mode.
+ * @param capacity capacity of the allocated ByteBuffer
+ * @return Buffer
+ */
+ public static ByteBuffer allocateDirect(int capacity)
+ {
+ ByteBuffer buf = ByteBuffer.allocateDirect(capacity);
+ buf.limit(0);
+ return buf;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Clear the buffer to be empty in flush mode.
+ * The position and limit are set to 0;
+ * @param buffer The buffer to clear.
+ */
+ public static void clear(ByteBuffer buffer)
+ {
+ if (buffer != null)
+ {
+ buffer.position(0);
+ buffer.limit(0);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Clear the buffer to be empty in fill mode.
+ * The position is set to 0 and the limit is set to the capacity.
+ * @param buffer The buffer to clear.
+ */
+ public static void clearToFill(ByteBuffer buffer)
+ {
+ if (buffer != null)
+ {
+ buffer.position(0);
+ buffer.limit(buffer.capacity());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Flip the buffer to fill mode.
+ * The position is set to the first unused position in the buffer
+ * (the old limit) and the limit is set to the capacity.
+ * If the buffer is empty, then this call is effectively {@link #clearToFill(ByteBuffer)}.
+ * If there is no unused space to fill, a {@link ByteBuffer#compact()} is done to attempt
+ * to create space.
+ * <p>
+ * This method is used as a replacement to {@link ByteBuffer#compact()}.
+ *
+ * @param buffer The buffer to flip
+ * @return The position of the valid data before the flipped position. This value should be
+ * passed to a subsequent call to {@link #flipToFlush(ByteBuffer, int)}
+ */
+ public static int flipToFill(ByteBuffer buffer)
+ {
+ int position = buffer.position();
+ int limit = buffer.limit();
+ if (position == limit)
+ {
+ buffer.position(0);
+ buffer.limit(buffer.capacity());
+ return 0;
+ }
+
+ int capacity = buffer.capacity();
+ if (limit == capacity)
+ {
+ buffer.compact();
+ return 0;
+ }
+
+ buffer.position(limit);
+ buffer.limit(capacity);
+ return position;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Flip the buffer to Flush mode.
+ * The limit is set to the first unused byte(the old position) and
+ * the position is set to the passed position.
+ * <p>
+ * This method is used as a replacement of {@link Buffer#flip()}.
+ * @param buffer the buffer to be flipped
+ * @param position The position of valid data to flip to. This should
+ * be the return value of the previous call to {@link #flipToFill(ByteBuffer)}
+ */
+ public static void flipToFlush(ByteBuffer buffer, int position)
+ {
+ buffer.limit(buffer.position());
+ buffer.position(position);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Convert a ByteBuffer to a byte array.
+ * @param buffer The buffer to convert in flush mode. The buffer is not altered.
+ * @return An array of bytes duplicated from the buffer.
+ */
+ public static byte[] toArray(ByteBuffer buffer)
+ {
+ if (buffer.hasArray())
+ {
+ byte[] array = buffer.array();
+ int from=buffer.arrayOffset() + buffer.position();
+ return Arrays.copyOfRange(array,from,from+buffer.remaining());
+ }
+ else
+ {
+ byte[] to = new byte[buffer.remaining()];
+ buffer.slice().get(to);
+ return to;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Check for an empty or null buffer.
+ * @param buf the buffer to check
+ * @return true if the buffer is null or empty.
+ */
+ public static boolean isEmpty(ByteBuffer buf)
+ {
+ return buf == null || buf.remaining() == 0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Check for a non null and non empty buffer.
+ * @param buf the buffer to check
+ * @return true if the buffer is not null and not empty.
+ */
+ public static boolean hasContent(ByteBuffer buf)
+ {
+ return buf != null && buf.remaining() > 0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Check for a non null and full buffer.
+ * @param buf the buffer to check
+ * @return true if the buffer is not null and the limit equals the capacity.
+ */
+ public static boolean isFull(ByteBuffer buf)
+ {
+ return buf != null && buf.limit() == buf.capacity();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get remaining from null checked buffer
+ * @param buffer The buffer to get the remaining from, in flush mode.
+ * @return 0 if the buffer is null, else the bytes remaining in the buffer.
+ */
+ public static int length(ByteBuffer buffer)
+ {
+ return buffer == null ? 0 : buffer.remaining();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the space from the limit to the capacity
+ * @param buffer the buffer to get the space from
+ * @return space
+ */
+ public static int space(ByteBuffer buffer)
+ {
+ if (buffer == null)
+ return 0;
+ return buffer.capacity() - buffer.limit();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Compact the buffer
+ * @param buffer the buffer to compact
+ * @return true if the compact made a full buffer have space
+ */
+ public static boolean compact(ByteBuffer buffer)
+ {
+ if (buffer.position()==0)
+ return false;
+ boolean full = buffer.limit() == buffer.capacity();
+ buffer.compact().flip();
+ return full && buffer.limit() < buffer.capacity();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Put data from one buffer into another, avoiding over/under flows
+ * @param from Buffer to take bytes from in flush mode
+ * @param to Buffer to put bytes to in fill mode.
+ * @return number of bytes moved
+ */
+ public static int put(ByteBuffer from, ByteBuffer to)
+ {
+ int put;
+ int remaining = from.remaining();
+ if (remaining > 0)
+ {
+ if (remaining <= to.remaining())
+ {
+ to.put(from);
+ put = remaining;
+ from.position(0);
+ from.limit(0);
+ }
+ else if (from.hasArray())
+ {
+ put = to.remaining();
+ to.put(from.array(), from.arrayOffset() + from.position(), put);
+ from.position(from.position() + put);
+ }
+ else
+ {
+ put = to.remaining();
+ ByteBuffer slice = from.slice();
+ slice.limit(put);
+ to.put(slice);
+ from.position(from.position() + put);
+ }
+ }
+ else
+ put = 0;
+
+ return put;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Put data from one buffer into another, avoiding over/under flows
+ * @param from Buffer to take bytes from in flush mode
+ * @param to Buffer to put bytes to in flush mode. The buffer is flipToFill before the put and flipToFlush after.
+ * @return number of bytes moved
+ * @deprecated use {@link #append(ByteBuffer, ByteBuffer)}
+ */
+ public static int flipPutFlip(ByteBuffer from, ByteBuffer to)
+ {
+ return append(to,from);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Append bytes to a buffer.
+ * @param to Buffer is flush mode
+ * @param b bytes to append
+ * @param off offset into byte
+ * @param len length to append
+ * @throws BufferOverflowException
+ */
+ public static void append(ByteBuffer to, byte[] b, int off, int len) throws BufferOverflowException
+ {
+ int pos = flipToFill(to);
+ try
+ {
+ to.put(b, off, len);
+ }
+ finally
+ {
+ flipToFlush(to, pos);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Appends a byte to a buffer
+ * @param to Buffer is flush mode
+ * @param b byte to append
+ */
+ public static void append(ByteBuffer to, byte b)
+ {
+ int pos = flipToFill(to);
+ try
+ {
+ to.put(b);
+ }
+ finally
+ {
+ flipToFlush(to, pos);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Appends a byte to a buffer
+ * @param to Buffer is flush mode
+ * @param b bytes to append
+ */
+ public static int append(ByteBuffer to, ByteBuffer b)
+ {
+ int pos = flipToFill(to);
+ try
+ {
+ return put(b, to);
+ }
+ finally
+ {
+ flipToFlush(to, pos);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Like append, but does not throw {@link BufferOverflowException}
+ * @param to Buffer is flush mode
+ * @param b bytes to fill
+ * @param off offset into byte
+ * @param len length to fill
+ */
+ public static int fill(ByteBuffer to, byte[] b, int off, int len)
+ {
+ int pos = flipToFill(to);
+ try
+ {
+ int remaining = to.remaining();
+ int take = remaining < len ? remaining : len;
+ to.put(b, off, take);
+ return take;
+ }
+ finally
+ {
+ flipToFlush(to, pos);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static void readFrom(File file, ByteBuffer buffer) throws IOException
+ {
+ try(RandomAccessFile raf = new RandomAccessFile(file,"r"))
+ {
+ FileChannel channel = raf.getChannel();
+ long needed=raf.length();
+
+ while (needed>0 && buffer.hasRemaining())
+ needed=needed-channel.read(buffer);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void readFrom(InputStream is, int needed, ByteBuffer buffer) throws IOException
+ {
+ ByteBuffer tmp = allocate(8192);
+
+ while (needed > 0 && buffer.hasRemaining())
+ {
+ int l = is.read(tmp.array(), 0, 8192);
+ if (l < 0)
+ break;
+ tmp.position(0);
+ tmp.limit(l);
+ buffer.put(tmp);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void writeTo(ByteBuffer buffer, OutputStream out) throws IOException
+ {
+ if (buffer.hasArray())
+ out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
+ else
+ {
+ byte[] bytes = new byte[TEMP_BUFFER_SIZE];
+ while(buffer.hasRemaining()){
+ int byteCountToWrite = Math.min(buffer.remaining(), TEMP_BUFFER_SIZE);
+ buffer.get(bytes, 0, byteCountToWrite);
+ out.write(bytes,0 , byteCountToWrite);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert the buffer to an ISO-8859-1 String
+ * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+ * @return The buffer as a string.
+ */
+ public static String toString(ByteBuffer buffer)
+ {
+ return toString(buffer, StandardCharsets.ISO_8859_1);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert the buffer to an UTF-8 String
+ * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+ * @return The buffer as a string.
+ */
+ public static String toUTF8String(ByteBuffer buffer)
+ {
+ return toString(buffer, StandardCharsets.UTF_8);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert the buffer to an ISO-8859-1 String
+ * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+ * @param charset The {@link Charset} to use to convert the bytes
+ * @return The buffer as a string.
+ */
+ public static String toString(ByteBuffer buffer, Charset charset)
+ {
+ if (buffer == null)
+ return null;
+ byte[] array = buffer.hasArray() ? buffer.array() : null;
+ if (array == null)
+ {
+ byte[] to = new byte[buffer.remaining()];
+ buffer.slice().get(to);
+ return new String(to, 0, to.length, charset);
+ }
+ return new String(array, buffer.arrayOffset() + buffer.position(), buffer.remaining(), charset);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert a partial buffer to an ISO-8859-1 String
+ * @param buffer The buffer to convert in flush mode. The buffer is unchanged
+ * @param charset The {@link Charset} to use to convert the bytes
+ * @return The buffer as a string.
+ */
+ public static String toString(ByteBuffer buffer, int position, int length, Charset charset)
+ {
+ if (buffer == null)
+ return null;
+ byte[] array = buffer.hasArray() ? buffer.array() : null;
+ if (array == null)
+ {
+ ByteBuffer ro = buffer.asReadOnlyBuffer();
+ ro.position(position);
+ ro.limit(position + length);
+ byte[] to = new byte[length];
+ ro.get(to);
+ return new String(to, 0, to.length, charset);
+ }
+ return new String(array, buffer.arrayOffset() + position, length, charset);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+ *
+ * @param buffer
+ * A buffer containing an integer in flush mode. The position is not changed.
+ * @return an int
+ */
+ public static int toInt(ByteBuffer buffer)
+ {
+ int val = 0;
+ boolean started = false;
+ boolean minus = false;
+
+ for (int i = buffer.position(); i < buffer.limit(); i++)
+ {
+ byte b = buffer.get(i);
+ if (b <= SPACE)
+ {
+ if (started)
+ break;
+ }
+ else if (b >= '0' && b <= '9')
+ {
+ val = val * 10 + (b - '0');
+ started = true;
+ }
+ else if (b == MINUS && !started)
+ {
+ minus = true;
+ }
+ else
+ break;
+ }
+
+ if (started)
+ return minus ? (-val) : val;
+ throw new NumberFormatException(toString(buffer));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+ *
+ * @param buffer
+ * A buffer containing an integer in flush mode. The position is updated.
+ * @return an int
+ */
+ public static int takeInt(ByteBuffer buffer)
+ {
+ int val = 0;
+ boolean started = false;
+ boolean minus = false;
+ int i;
+ for (i = buffer.position(); i < buffer.limit(); i++)
+ {
+ byte b = buffer.get(i);
+ if (b <= SPACE)
+ {
+ if (started)
+ break;
+ }
+ else if (b >= '0' && b <= '9')
+ {
+ val = val * 10 + (b - '0');
+ started = true;
+ }
+ else if (b == MINUS && !started)
+ {
+ minus = true;
+ }
+ else
+ break;
+ }
+
+ if (started)
+ {
+ buffer.position(i);
+ return minus ? (-val) : val;
+ }
+ throw new NumberFormatException(toString(buffer));
+ }
+
+ /**
+ * Convert buffer to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+ *
+ * @param buffer
+ * A buffer containing an integer in flush mode. The position is not changed.
+ * @return an int
+ */
+ public static long toLong(ByteBuffer buffer)
+ {
+ long val = 0;
+ boolean started = false;
+ boolean minus = false;
+
+ for (int i = buffer.position(); i < buffer.limit(); i++)
+ {
+ byte b = buffer.get(i);
+ if (b <= SPACE)
+ {
+ if (started)
+ break;
+ }
+ else if (b >= '0' && b <= '9')
+ {
+ val = val * 10L + (b - '0');
+ started = true;
+ }
+ else if (b == MINUS && !started)
+ {
+ minus = true;
+ }
+ else
+ break;
+ }
+
+ if (started)
+ return minus ? (-val) : val;
+ throw new NumberFormatException(toString(buffer));
+ }
+
+ public static void putHexInt(ByteBuffer buffer, int n)
+ {
+ if (n < 0)
+ {
+ buffer.put((byte)'-');
+
+ if (n == Integer.MIN_VALUE)
+ {
+ buffer.put((byte)(0x7f & '8'));
+ buffer.put((byte)(0x7f & '0'));
+ buffer.put((byte)(0x7f & '0'));
+ buffer.put((byte)(0x7f & '0'));
+ buffer.put((byte)(0x7f & '0'));
+ buffer.put((byte)(0x7f & '0'));
+ buffer.put((byte)(0x7f & '0'));
+ buffer.put((byte)(0x7f & '0'));
+
+ return;
+ }
+ n = -n;
+ }
+
+ if (n < 0x10)
+ {
+ buffer.put(DIGIT[n]);
+ }
+ else
+ {
+ boolean started = false;
+ // This assumes constant time int arithmatic
+ for (int hexDivisor : hexDivisors)
+ {
+ if (n < hexDivisor)
+ {
+ if (started)
+ buffer.put((byte)'0');
+ continue;
+ }
+
+ started = true;
+ int d = n / hexDivisor;
+ buffer.put(DIGIT[d]);
+ n = n - d * hexDivisor;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void putDecInt(ByteBuffer buffer, int n)
+ {
+ if (n < 0)
+ {
+ buffer.put((byte)'-');
+
+ if (n == Integer.MIN_VALUE)
+ {
+ buffer.put((byte)'2');
+ n = 147483648;
+ }
+ else
+ n = -n;
+ }
+
+ if (n < 10)
+ {
+ buffer.put(DIGIT[n]);
+ }
+ else
+ {
+ boolean started = false;
+ // This assumes constant time int arithmatic
+ for (int decDivisor : decDivisors)
+ {
+ if (n < decDivisor)
+ {
+ if (started)
+ buffer.put((byte)'0');
+ continue;
+ }
+
+ started = true;
+ int d = n / decDivisor;
+ buffer.put(DIGIT[d]);
+ n = n - d * decDivisor;
+ }
+ }
+ }
+
+ public static void putDecLong(ByteBuffer buffer, long n)
+ {
+ if (n < 0)
+ {
+ buffer.put((byte)'-');
+
+ if (n == Long.MIN_VALUE)
+ {
+ buffer.put((byte)'9');
+ n = 223372036854775808L;
+ }
+ else
+ n = -n;
+ }
+
+ if (n < 10)
+ {
+ buffer.put(DIGIT[(int)n]);
+ }
+ else
+ {
+ boolean started = false;
+ // This assumes constant time int arithmatic
+ for (long aDecDivisorsL : decDivisorsL)
+ {
+ if (n < aDecDivisorsL)
+ {
+ if (started)
+ buffer.put((byte)'0');
+ continue;
+ }
+
+ started = true;
+ long d = n / aDecDivisorsL;
+ buffer.put(DIGIT[(int)d]);
+ n = n - d * aDecDivisorsL;
+ }
+ }
+ }
+
+ public static ByteBuffer toBuffer(int value)
+ {
+ ByteBuffer buf = ByteBuffer.allocate(32);
+ putDecInt(buf, value);
+ return buf;
+ }
+
+ public static ByteBuffer toBuffer(long value)
+ {
+ ByteBuffer buf = ByteBuffer.allocate(32);
+ putDecLong(buf, value);
+ return buf;
+ }
+
+ public static ByteBuffer toBuffer(String s)
+ {
+ return ByteBuffer.wrap(s.getBytes(StandardCharsets.ISO_8859_1));
+ }
+
+ public static ByteBuffer toDirectBuffer(String s)
+ {
+ byte[] bytes = s.getBytes(StandardCharsets.ISO_8859_1);
+ ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
+ buf.put(bytes);
+ buf.flip();
+ return buf;
+ }
+
+ public static ByteBuffer toBuffer(String s, Charset charset)
+ {
+ return ByteBuffer.wrap(s.getBytes(charset));
+ }
+
+ public static ByteBuffer toDirectBuffer(String s, Charset charset)
+ {
+ byte[] bytes = s.getBytes(charset);
+ ByteBuffer buf = ByteBuffer.allocateDirect(bytes.length);
+ buf.put(bytes);
+ buf.flip();
+ return buf;
+ }
+
+ /**
+ * Create a new ByteBuffer using provided byte array.
+ *
+ * @param array
+ * the byte array to back buffer with.
+ * @return ByteBuffer with provided byte array, in flush mode
+ */
+ public static ByteBuffer toBuffer(byte array[])
+ {
+ return ByteBuffer.wrap(array);
+ }
+
+ /**
+ * Create a new ByteBuffer using the provided byte array.
+ *
+ * @param array
+ * the byte array to use.
+ * @param offset
+ * the offset within the byte array to use from
+ * @param length
+ * the length in bytes of the array to use
+ * @return ByteBuffer with provided byte array, in flush mode
+ */
+ public static ByteBuffer toBuffer(byte array[], int offset, int length)
+ {
+ return ByteBuffer.wrap(array, offset, length);
+ }
+
+ public static ByteBuffer toMappedBuffer(File file) throws IOException
+ {
+ try (RandomAccessFile raf = new RandomAccessFile(file, "r"))
+ {
+ return raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length());
+ }
+ }
+
+ public static ByteBuffer toBuffer(Resource resource,boolean direct) throws IOException
+ {
+ int len=(int)resource.length();
+ if (len<0)
+ throw new IllegalArgumentException("invalid resource: "+String.valueOf(resource)+" len="+len);
+
+ ByteBuffer buffer = direct?BufferUtil.allocateDirect(len):BufferUtil.allocate(len);
+
+ int pos=BufferUtil.flipToFill(buffer);
+ if (resource.getFile()!=null)
+ BufferUtil.readFrom(resource.getFile(),buffer);
+ else
+ {
+ try (InputStream is = resource.getInputStream();)
+ {
+ BufferUtil.readFrom(is,len,buffer);
+ }
+ }
+ BufferUtil.flipToFlush(buffer,pos);
+
+ return buffer;
+ }
+
+ public static String toSummaryString(ByteBuffer buffer)
+ {
+ if (buffer == null)
+ return "null";
+ StringBuilder buf = new StringBuilder();
+ buf.append("[p=");
+ buf.append(buffer.position());
+ buf.append(",l=");
+ buf.append(buffer.limit());
+ buf.append(",c=");
+ buf.append(buffer.capacity());
+ buf.append(",r=");
+ buf.append(buffer.remaining());
+ buf.append("]");
+ return buf.toString();
+ }
+
+ public static String toDetailString(ByteBuffer[] buffer)
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append('[');
+ for (int i = 0; i < buffer.length; i++)
+ {
+ if (i > 0) builder.append(',');
+ builder.append(toDetailString(buffer[i]));
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+
+ public static String toDetailString(ByteBuffer buffer)
+ {
+ if (buffer == null)
+ return "null";
+
+ StringBuilder buf = new StringBuilder();
+ buf.append(buffer.getClass().getSimpleName());
+ buf.append("@");
+ if (buffer.hasArray())
+ buf.append(Integer.toHexString(((Object)buffer.array()).hashCode()));
+ else
+ buf.append(Integer.toHexString(buf.hashCode()));
+ buf.append("[p=");
+ buf.append(buffer.position());
+ buf.append(",l=");
+ buf.append(buffer.limit());
+ buf.append(",c=");
+ buf.append(buffer.capacity());
+ buf.append(",r=");
+ buf.append(buffer.remaining());
+ buf.append("]={");
+
+ for (int i = 0; i < buffer.position(); i++)
+ {
+ char c = (char)buffer.get(i);
+ if (c >= ' ' && c <= 127)
+ buf.append(c);
+ else if (c == '\r' || c == '\n')
+ buf.append('|');
+ else
+ buf.append('\ufffd');
+ if (i == 16 && buffer.position() > 32)
+ {
+ buf.append("...");
+ i = buffer.position() - 16;
+ }
+ }
+ buf.append("<<<");
+ for (int i = buffer.position(); i < buffer.limit(); i++)
+ {
+ char c = (char)buffer.get(i);
+ if (c >= ' ' && c <= 127)
+ buf.append(c);
+ else if (c == '\r' || c == '\n')
+ buf.append('|');
+ else
+ buf.append('\ufffd');
+ if (i == buffer.position() + 16 && buffer.limit() > buffer.position() + 32)
+ {
+ buf.append("...");
+ i = buffer.limit() - 16;
+ }
+ }
+ buf.append(">>>");
+ int limit = buffer.limit();
+ buffer.limit(buffer.capacity());
+ for (int i = limit; i < buffer.capacity(); i++)
+ {
+ char c = (char)buffer.get(i);
+ if (c >= ' ' && c <= 127)
+ buf.append(c);
+ else if (c == '\r' || c == '\n')
+ buf.append('|');
+ else
+ buf.append('\ufffd');
+ if (i == limit + 16 && buffer.capacity() > limit + 32)
+ {
+ buf.append("...");
+ i = buffer.capacity() - 16;
+ }
+ }
+ buffer.limit(limit);
+ buf.append("}");
+
+ return buf.toString();
+ }
+
+
+ private final static int[] decDivisors =
+ {1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1};
+
+ private final static int[] hexDivisors =
+ {0x10000000, 0x1000000, 0x100000, 0x10000, 0x1000, 0x100, 0x10, 0x1};
+
+ private final static long[] decDivisorsL =
+ {1000000000000000000L, 100000000000000000L, 10000000000000000L, 1000000000000000L, 100000000000000L, 10000000000000L, 1000000000000L, 100000000000L,
+ 10000000000L, 1000000000L, 100000000L, 10000000L, 1000000L, 100000L, 10000L, 1000L, 100L, 10L, 1L};
+
+ public static void putCRLF(ByteBuffer buffer)
+ {
+ buffer.put((byte)13);
+ buffer.put((byte)10);
+ }
+
+ public static boolean isPrefix(ByteBuffer prefix, ByteBuffer buffer)
+ {
+ if (prefix.remaining() > buffer.remaining())
+ return false;
+ int bi = buffer.position();
+ for (int i = prefix.position(); i < prefix.limit(); i++)
+ if (prefix.get(i) != buffer.get(bi++))
+ return false;
+ return true;
+ }
+
+ public static ByteBuffer ensureCapacity(ByteBuffer buffer, int capacity)
+ {
+ if (buffer==null)
+ return allocate(capacity);
+
+ if (buffer.capacity()>=capacity)
+ return buffer;
+
+ if (buffer.hasArray())
+ return ByteBuffer.wrap(Arrays.copyOfRange(buffer.array(), buffer.arrayOffset(), buffer.arrayOffset()+capacity),buffer.position(),buffer.remaining());
+
+ throw new UnsupportedOperationException();
+ }
+
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+
+/* ------------------------------------------------------------ */
+/** Byte Array ISO 8859 writer.
+ * This class combines the features of a OutputStreamWriter for
+ * ISO8859 encoding with that of a ByteArrayOutputStream. It avoids
+ * many inefficiencies associated with these standard library classes.
+ * It has been optimized for standard ASCII characters.
+ *
+ *
+ */
+public class ByteArrayISO8859Writer extends Writer
+{
+ private byte[] _buf;
+ private int _size;
+ private ByteArrayOutputStream2 _bout=null;
+ private OutputStreamWriter _writer=null;
+ private boolean _fixed=false;
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ */
+ public ByteArrayISO8859Writer()
+ {
+ _buf=new byte[2048];
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ * @param capacity Buffer capacity
+ */
+ public ByteArrayISO8859Writer(int capacity)
+ {
+ _buf=new byte[capacity];
+ }
+
+ /* ------------------------------------------------------------ */
+ public ByteArrayISO8859Writer(byte[] buf)
+ {
+ _buf=buf;
+ _fixed=true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getLock()
+ {
+ return lock;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int size()
+ {
+ return _size;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int capacity()
+ {
+ return _buf.length;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int spareCapacity()
+ {
+ return _buf.length-_size;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setLength(int l)
+ {
+ _size=l;
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getBuf()
+ {
+ return _buf;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void writeTo(OutputStream out)
+ throws IOException
+ {
+ out.write(_buf,0,_size);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void write(char c)
+ throws IOException
+ {
+ ensureSpareCapacity(1);
+ if (c>=0&&c<=0x7f)
+ _buf[_size++]=(byte)c;
+ else
+ {
+ char[] ca ={c};
+ writeEncoded(ca,0,1);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(char[] ca)
+ throws IOException
+ {
+ ensureSpareCapacity(ca.length);
+ for (int i=0;i<ca.length;i++)
+ {
+ char c=ca[i];
+ if (c>=0&&c<=0x7f)
+ _buf[_size++]=(byte)c;
+ else
+ {
+ writeEncoded(ca,i,ca.length-i);
+ break;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(char[] ca,int offset, int length)
+ throws IOException
+ {
+ ensureSpareCapacity(length);
+ for (int i=0;i<length;i++)
+ {
+ char c=ca[offset+i];
+ if (c>=0&&c<=0x7f)
+ _buf[_size++]=(byte)c;
+ else
+ {
+ writeEncoded(ca,offset+i,length-i);
+ break;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(String s)
+ throws IOException
+ {
+ if (s==null)
+ {
+ write("null",0,4);
+ return;
+ }
+
+ int length=s.length();
+ ensureSpareCapacity(length);
+ for (int i=0;i<length;i++)
+ {
+ char c=s.charAt(i);
+ if (c>=0x0&&c<=0x7f)
+ _buf[_size++]=(byte)c;
+ else
+ {
+ writeEncoded(s.toCharArray(),i,length-i);
+ break;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(String s,int offset, int length)
+ throws IOException
+ {
+ ensureSpareCapacity(length);
+ for (int i=0;i<length;i++)
+ {
+ char c=s.charAt(offset+i);
+ if (c>=0&&c<=0x7f)
+ _buf[_size++]=(byte)c;
+ else
+ {
+ writeEncoded(s.toCharArray(),offset+i,length-i);
+ break;
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void writeEncoded(char[] ca,int offset, int length)
+ throws IOException
+ {
+ if (_bout==null)
+ {
+ _bout = new ByteArrayOutputStream2(2*length);
+ _writer = new OutputStreamWriter(_bout,StandardCharsets.ISO_8859_1);
+ }
+ else
+ _bout.reset();
+ _writer.write(ca,offset,length);
+ _writer.flush();
+ ensureSpareCapacity(_bout.getCount());
+ System.arraycopy(_bout.getBuf(),0,_buf,_size,_bout.getCount());
+ _size+=_bout.getCount();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void flush()
+ {}
+
+ /* ------------------------------------------------------------ */
+ public void resetWriter()
+ {
+ _size=0;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void close()
+ {}
+
+ /* ------------------------------------------------------------ */
+ public void destroy()
+ {
+ _buf=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void ensureSpareCapacity(int n)
+ throws IOException
+ {
+ if (_size+n>_buf.length)
+ {
+ if (_fixed)
+ throw new IOException("Buffer overflow: "+_buf.length);
+ _buf=Arrays.copyOf(_buf,(_buf.length+n)*4/3);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getByteArray()
+ {
+ return Arrays.copyOf(_buf,_size);
+ }
+
+}
+
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.Charset;
+
+/* ------------------------------------------------------------ */
+/** ByteArrayOutputStream with public internals
+
+ *
+ */
+public class ByteArrayOutputStream2 extends ByteArrayOutputStream
+{
+ public ByteArrayOutputStream2(){super();}
+ public ByteArrayOutputStream2(int size){super(size);}
+ public byte[] getBuf(){return buf;}
+ public int getCount(){return count;}
+ public void setCount(int count){this.count = count;}
+
+ public void reset(int minSize)
+ {
+ reset();
+ if (buf.length<minSize)
+ {
+ buf=new byte[minSize];
+ }
+ }
+
+ public void writeUnchecked(int b)
+ {
+ buf[count++]=(byte)b;
+ }
+
+ public String toString(Charset charset)
+ {
+ return new String(buf, 0, count, charset);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/*
+ * Copyright (c) 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.eclipse.jetty.util;
+
+/**
+ * <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
+ *
+ * <p>Semantically this is equivalent to an optimise Promise<Void>, but callback is a more meaningful
+ * name than EmptyPromise</p>
+ */
+public interface Callback
+{
+ /**
+ * <p>Callback invoked when the operation completes.</p>
+ *
+ * @see #failed(Throwable)
+ */
+ public abstract void succeeded();
+
+ /**
+ * <p>Callback invoked when the operation fails.</p>
+ * @param x the reason for the operation failure
+ */
+ public void failed(Throwable x);
+
+ /**
+ * <p>Empty implementation of {@link Callback}</p>
+ */
+ public static class Adapter implements Callback
+ {
+ /**
+ * Instance of Adapter that can be used when the callback methods need an empty
+ * implementation without incurring in the cost of allocating a new Adapter object.
+ */
+ public static final Adapter INSTANCE = new Adapter();
+
+ @Override
+ public void succeeded()
+ {
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+
+
+/**
+ * ClassLoadingObjectInputStream
+ *
+ * For re-inflating serialized objects, this class uses the thread context classloader
+ * rather than the jvm's default classloader selection.
+ *
+ */
+public class ClassLoadingObjectInputStream extends ObjectInputStream
+{
+ /* ------------------------------------------------------------ */
+ public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+ {
+ super(in);
+ }
+
+ /* ------------------------------------------------------------ */
+ public ClassLoadingObjectInputStream () throws IOException
+ {
+ super();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+ {
+ try
+ {
+ return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
+ }
+ catch (ClassNotFoundException e)
+ {
+ return super.resolveClass(cl);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected Class<?> resolveProxyClass(String[] interfaces)
+ throws IOException, ClassNotFoundException
+ {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+
+ ClassLoader nonPublicLoader = null;
+ boolean hasNonPublicInterface = false;
+
+ // define proxy in class loader of non-public interface(s), if any
+ Class[] classObjs = new Class[interfaces.length];
+ for (int i = 0; i < interfaces.length; i++)
+ {
+ Class cl = Class.forName(interfaces[i], false, loader);
+ if ((cl.getModifiers() & Modifier.PUBLIC) == 0)
+ {
+ if (hasNonPublicInterface)
+ {
+ if (nonPublicLoader != cl.getClassLoader())
+ {
+ throw new IllegalAccessError(
+ "conflicting non-public interface class loaders");
+ }
+ }
+ else
+ {
+ nonPublicLoader = cl.getClassLoader();
+ hasNonPublicInterface = true;
+ }
+ }
+ classObjs[i] = cl;
+ }
+ try
+ {
+ return Proxy.getProxyClass(hasNonPublicInterface ? nonPublicLoader : loader,classObjs);
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new ClassNotFoundException(null, e);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A callback to be used by driver code that needs to know whether the callback has been
+ * succeeded or failed (that is, completed) just after the asynchronous operation or not,
+ * typically because further processing depends on the callback being completed.
+ * The driver code competes with the asynchronous operation to complete the callback.
+ * <p />
+ * If the callback is already completed, the driver code continues the processing,
+ * otherwise it suspends it. If it is suspended, the callback will be completed some time
+ * later, and {@link #resume()} or {@link #abort(Throwable)} will be called to allow the
+ * application to resume the processing.
+ * <p />
+ * Typical usage:
+ * <pre>
+ * CompletableCallback callback = new CompletableCallback()
+ * {
+ * @Override
+ * public void resume()
+ * {
+ * // continue processing
+ * }
+ *
+ * @Override
+ * public void abort(Throwable failure)
+ * {
+ * // abort processing
+ * }
+ * }
+ * asyncOperation(callback);
+ * boolean completed = callback.tryComplete();
+ * if (completed)
+ * // suspend processing, async operation not done yet
+ * else
+ * // continue processing, async operation already done
+ * </pre>
+ */
+public abstract class CompletableCallback implements Callback
+{
+ private final AtomicBoolean completed = new AtomicBoolean();
+
+ @Override
+ public void succeeded()
+ {
+ if (!tryComplete())
+ resume();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ if (!tryComplete())
+ abort(x);
+ }
+
+ /**
+ * Callback method invoked when this callback is succeeded
+ * <em>after</em> a first call to {@link #tryComplete()}.
+ */
+ public abstract void resume();
+
+ /**
+ * Callback method invoked when this callback is failed
+ * <em>after</em> a first call to {@link #tryComplete()}.
+ */
+ public abstract void abort(Throwable failure);
+
+ /**
+ * Tries to complete this callback; driver code should call
+ * this method once <em>after</em> the asynchronous operation
+ * to detect whether the asynchronous operation has already
+ * completed or not.
+ *
+ * @return whether the attempt to complete was successful.
+ */
+ public boolean tryComplete()
+ {
+ return completed.compareAndSet(false, true);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.AbstractQueue;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicIntegerArray;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * A concurrent, unbounded implementation of {@link Queue} that uses singly-linked array blocks
+ * to store elements.
+ * <p/>
+ * This class is a drop-in replacement for {@link ConcurrentLinkedQueue}, with similar performance
+ * but producing less garbage because arrays are used to store elements rather than nodes.
+ * <p/>
+ * The algorithm used is a variation of the algorithm from Gidenstam, Sundell and Tsigas
+ * (http://www.adm.hb.se/~AGD/Presentations/CacheAwareQueue_OPODIS.pdf).
+ *
+ * @param <T>
+ */
+public class ConcurrentArrayQueue<T> extends AbstractQueue<T>
+{
+ public static final int DEFAULT_BLOCK_SIZE = 512;
+ public static final Object REMOVED_ELEMENT = new Object()
+ {
+ @Override
+ public String toString()
+ {
+ return "X";
+ }
+ };
+
+ private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1;
+ private static final int TAIL_OFFSET = MemoryUtils.getIntegersPerCacheLine()*2 -1;
+
+ private final AtomicReferenceArray<Block<T>> _blocks = new AtomicReferenceArray<>(TAIL_OFFSET + 1);
+ private final int _blockSize;
+
+ public ConcurrentArrayQueue()
+ {
+ this(DEFAULT_BLOCK_SIZE);
+ }
+
+ public ConcurrentArrayQueue(int blockSize)
+ {
+ _blockSize = blockSize;
+ Block<T> block = newBlock();
+ _blocks.set(HEAD_OFFSET,block);
+ _blocks.set(TAIL_OFFSET,block);
+ }
+
+ public int getBlockSize()
+ {
+ return _blockSize;
+ }
+
+ protected Block<T> getHeadBlock()
+ {
+ return _blocks.get(HEAD_OFFSET);
+ }
+
+ protected Block<T> getTailBlock()
+ {
+ return _blocks.get(TAIL_OFFSET);
+ }
+
+ @Override
+ public boolean offer(T item)
+ {
+ item = Objects.requireNonNull(item);
+
+ final Block<T> initialTailBlock = getTailBlock();
+ Block<T> currentTailBlock = initialTailBlock;
+ int tail = currentTailBlock.tail();
+ while (true)
+ {
+ if (tail == getBlockSize())
+ {
+ Block<T> nextTailBlock = currentTailBlock.next();
+ if (nextTailBlock == null)
+ {
+ nextTailBlock = newBlock();
+ if (currentTailBlock.link(nextTailBlock))
+ {
+ // Linking succeeded, loop
+ currentTailBlock = nextTailBlock;
+ }
+ else
+ {
+ // Concurrent linking, use other block and loop
+ currentTailBlock = currentTailBlock.next();
+ }
+ }
+ else
+ {
+ // Not at last block, loop
+ currentTailBlock = nextTailBlock;
+ }
+ tail = currentTailBlock.tail();
+ }
+ else
+ {
+ if (currentTailBlock.peek(tail) == null)
+ {
+ if (currentTailBlock.store(tail, item))
+ {
+ // Item stored
+ break;
+ }
+ else
+ {
+ // Concurrent store, try next index
+ ++tail;
+ }
+ }
+ else
+ {
+ // Not free, try next index
+ ++tail;
+ }
+ }
+ }
+
+ updateTailBlock(initialTailBlock, currentTailBlock);
+
+ return true;
+ }
+
+ private void updateTailBlock(Block<T> oldTailBlock, Block<T> newTailBlock)
+ {
+ // Update the tail block pointer if needs to
+ if (oldTailBlock != newTailBlock)
+ {
+ // The tail block pointer is allowed to lag behind.
+ // If this update fails, it means that other threads
+ // have filled this block and installed a new one.
+ casTailBlock(oldTailBlock, newTailBlock);
+ }
+ }
+
+ protected boolean casTailBlock(Block<T> current, Block<T> update)
+ {
+ return _blocks.compareAndSet(TAIL_OFFSET,current,update);
+ }
+
+ @Override
+ public T poll()
+ {
+ final Block<T> initialHeadBlock = getHeadBlock();
+ Block<T> currentHeadBlock = initialHeadBlock;
+ int head = currentHeadBlock.head();
+ T result = null;
+ while (true)
+ {
+ if (head == getBlockSize())
+ {
+ Block<T> nextHeadBlock = currentHeadBlock.next();
+ if (nextHeadBlock == null)
+ {
+ // We could have read that the next head block was null
+ // but another thread allocated a new block and stored a
+ // new item. This thread could not detect this, but that
+ // is ok, otherwise we would not be able to exit this loop.
+
+ // Queue is empty
+ break;
+ }
+ else
+ {
+ // Use next block and loop
+ currentHeadBlock = nextHeadBlock;
+ head = currentHeadBlock.head();
+ }
+ }
+ else
+ {
+ Object element = currentHeadBlock.peek(head);
+ if (element == REMOVED_ELEMENT)
+ {
+ // Already removed, try next index
+ ++head;
+ }
+ else
+ {
+ result = (T)element;
+ if (result != null)
+ {
+ if (currentHeadBlock.remove(head, result, true))
+ {
+ // Item removed
+ break;
+ }
+ else
+ {
+ // Concurrent remove, try next index
+ ++head;
+ }
+ }
+ else
+ {
+ // Queue is empty
+ break;
+ }
+ }
+ }
+ }
+
+ updateHeadBlock(initialHeadBlock, currentHeadBlock);
+
+ return result;
+ }
+
+ private void updateHeadBlock(Block<T> oldHeadBlock, Block<T> newHeadBlock)
+ {
+ // Update the head block pointer if needs to
+ if (oldHeadBlock != newHeadBlock)
+ {
+ // The head block pointer lagged behind.
+ // If this update fails, it means that other threads
+ // have emptied this block and pointed to a new one.
+ casHeadBlock(oldHeadBlock, newHeadBlock);
+ }
+ }
+
+ protected boolean casHeadBlock(Block<T> current, Block<T> update)
+ {
+ return _blocks.compareAndSet(HEAD_OFFSET,current,update);
+ }
+
+ @Override
+ public T peek()
+ {
+ Block<T> currentHeadBlock = getHeadBlock();
+ int head = currentHeadBlock.head();
+ while (true)
+ {
+ if (head == getBlockSize())
+ {
+ Block<T> nextHeadBlock = currentHeadBlock.next();
+ if (nextHeadBlock == null)
+ {
+ // Queue is empty
+ return null;
+ }
+ else
+ {
+ // Use next block and loop
+ currentHeadBlock = nextHeadBlock;
+ head = currentHeadBlock.head();
+ }
+ }
+ else
+ {
+ Object element = currentHeadBlock.peek(head);
+ if (element == REMOVED_ELEMENT)
+ {
+ // Already removed, try next index
+ ++head;
+ }
+ else
+ {
+ return (T)element;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ Block<T> currentHeadBlock = getHeadBlock();
+ int head = currentHeadBlock.head();
+ boolean result = false;
+ while (true)
+ {
+ if (head == getBlockSize())
+ {
+ Block<T> nextHeadBlock = currentHeadBlock.next();
+ if (nextHeadBlock == null)
+ {
+ // Not found
+ break;
+ }
+ else
+ {
+ // Use next block and loop
+ currentHeadBlock = nextHeadBlock;
+ head = currentHeadBlock.head();
+ }
+ }
+ else
+ {
+ Object element = currentHeadBlock.peek(head);
+ if (element == REMOVED_ELEMENT)
+ {
+ // Removed, try next index
+ ++head;
+ }
+ else
+ {
+ if (element == null)
+ {
+ // Not found
+ break;
+ }
+ else
+ {
+ if (element.equals(o))
+ {
+ // Found
+ if (currentHeadBlock.remove(head, o, false))
+ {
+ result = true;
+ break;
+ }
+ else
+ {
+ ++head;
+ }
+ }
+ else
+ {
+ // Not the one we're looking for
+ ++head;
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c)
+ {
+ // TODO: super invocations are based on iterator.remove(), which throws
+ return super.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c)
+ {
+ // TODO: super invocations are based on iterator.remove(), which throws
+ return super.retainAll(c);
+ }
+
+ @Override
+ public Iterator<T> iterator()
+ {
+ final List<Object[]> blocks = new ArrayList<>();
+ Block<T> currentHeadBlock = getHeadBlock();
+ while (currentHeadBlock != null)
+ {
+ Object[] elements = currentHeadBlock.arrayCopy();
+ blocks.add(elements);
+ currentHeadBlock = currentHeadBlock.next();
+ }
+ return new Iterator<T>()
+ {
+ private int blockIndex;
+ private int index;
+
+ @Override
+ public boolean hasNext()
+ {
+ while (true)
+ {
+ if (blockIndex == blocks.size())
+ return false;
+
+ Object element = blocks.get(blockIndex)[index];
+
+ if (element == null)
+ return false;
+
+ if (element != REMOVED_ELEMENT)
+ return true;
+
+ advance();
+ }
+ }
+
+ @Override
+ public T next()
+ {
+ while (true)
+ {
+ if (blockIndex == blocks.size())
+ throw new NoSuchElementException();
+
+ Object element = blocks.get(blockIndex)[index];
+
+ if (element == null)
+ throw new NoSuchElementException();
+
+ advance();
+
+ if (element != REMOVED_ELEMENT)
+ return (T)element;
+ }
+ }
+
+ private void advance()
+ {
+ if (++index == getBlockSize())
+ {
+ index = 0;
+ ++blockIndex;
+ }
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ public int size()
+ {
+ Block<T> currentHeadBlock = getHeadBlock();
+ int head = currentHeadBlock.head();
+ int size = 0;
+ while (true)
+ {
+ if (head == getBlockSize())
+ {
+ Block<T> nextHeadBlock = currentHeadBlock.next();
+ if (nextHeadBlock == null)
+ {
+ break;
+ }
+ else
+ {
+ // Use next block and loop
+ currentHeadBlock = nextHeadBlock;
+ head = currentHeadBlock.head();
+ }
+ }
+ else
+ {
+ Object element = currentHeadBlock.peek(head);
+ if (element == REMOVED_ELEMENT)
+ {
+ // Already removed, try next index
+ ++head;
+ }
+ else if (element != null)
+ {
+ ++size;
+ ++head;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ return size;
+ }
+
+ protected Block<T> newBlock()
+ {
+ return new Block<>(getBlockSize());
+ }
+
+ protected int getBlockCount()
+ {
+ int result = 0;
+ Block<T> headBlock = getHeadBlock();
+ while (headBlock != null)
+ {
+ ++result;
+ headBlock = headBlock.next();
+ }
+ return result;
+ }
+
+ protected static final class Block<E>
+ {
+ private static final int headOffset = MemoryUtils.getIntegersPerCacheLine()-1;
+ private static final int tailOffset = MemoryUtils.getIntegersPerCacheLine()*2-1;
+
+ private final AtomicReferenceArray<Object> elements;
+ private final AtomicReference<Block<E>> next = new AtomicReference<>();
+ private final AtomicIntegerArray indexes = new AtomicIntegerArray(TAIL_OFFSET+1);
+
+ protected Block(int blockSize)
+ {
+ elements = new AtomicReferenceArray<>(blockSize);
+ }
+
+ public Object peek(int index)
+ {
+ return elements.get(index);
+ }
+
+ public boolean store(int index, E item)
+ {
+ boolean result = elements.compareAndSet(index, null, item);
+ if (result)
+ indexes.incrementAndGet(tailOffset);
+ return result;
+ }
+
+ public boolean remove(int index, Object item, boolean updateHead)
+ {
+ boolean result = elements.compareAndSet(index, item, REMOVED_ELEMENT);
+ if (result && updateHead)
+ indexes.incrementAndGet(headOffset);
+ return result;
+ }
+
+ public Block<E> next()
+ {
+ return next.get();
+ }
+
+ public boolean link(Block<E> nextBlock)
+ {
+ return next.compareAndSet(null, nextBlock);
+ }
+
+ public int head()
+ {
+ return indexes.get(headOffset);
+ }
+
+ public int tail()
+ {
+ return indexes.get(tailOffset);
+ }
+
+ public Object[] arrayCopy()
+ {
+ Object[] result = new Object[elements.length()];
+ for (int i = 0; i < result.length; ++i)
+ result[i] = elements.get(i);
+ return result;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>
+{
+ private final Map<E, Boolean> _map = new ConcurrentHashMap<E, Boolean>();
+ private transient Set<E> _keys = _map.keySet();
+
+ public ConcurrentHashSet()
+ {
+ }
+
+ @Override
+ public boolean add(E e)
+ {
+ return _map.put(e,Boolean.TRUE) == null;
+ }
+
+ @Override
+ public void clear()
+ {
+ _map.clear();
+ }
+
+ @Override
+ public boolean contains(Object o)
+ {
+ return _map.containsKey(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c)
+ {
+ return _keys.containsAll(c);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return o == this || _keys.equals(o);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return _keys.hashCode();
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return _map.isEmpty();
+ }
+
+ @Override
+ public Iterator<E> iterator()
+ {
+ return _keys.iterator();
+ }
+
+ @Override
+ public boolean remove(Object o)
+ {
+ return _map.remove(o) != null;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c)
+ {
+ return _keys.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c)
+ {
+ return _keys.retainAll(c);
+ }
+
+ @Override
+ public int size()
+ {
+ return _map.size();
+ }
+
+ @Override
+ public Object[] toArray()
+ {
+ return _keys.toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a)
+ {
+ return _keys.toArray(a);
+ }
+
+ @Override
+ public String toString()
+ {
+ return _keys.toString();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/* ------------------------------------------------------------ */
+/** Date Format Cache.
+ * Computes String representations of Dates and caches
+ * the results so that subsequent requests within the same second
+ * will be fast.
+ *
+ * Only format strings that contain either "ss". Sub second formatting is
+ * not handled.
+ *
+ * The timezone of the date may be included as an ID with the "zzz"
+ * format string or as an offset with the "ZZZ" format string.
+ *
+ * If consecutive calls are frequently very different, then this
+ * may be a little slower than a normal DateFormat.
+ *
+ */
+
+public class DateCache
+{
+ public static final String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy";
+
+ private final String _formatString;
+ private final String _tzFormatString;
+ private final SimpleDateFormat _tzFormat;
+ private final Locale _locale ;
+
+ private volatile Tick _tick;
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public static class Tick
+ {
+ final long _seconds;
+ final String _string;
+ public Tick(long seconds, String string)
+ {
+ _seconds = seconds;
+ _string = string;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ * Make a DateCache that will use a default format. The default format
+ * generates the same results as Date.toString().
+ */
+ public DateCache()
+ {
+ this(DEFAULT_FORMAT);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ * Make a DateCache that will use the given format
+ */
+ public DateCache(String format)
+ {
+ this(format,null,TimeZone.getDefault());
+ }
+
+ /* ------------------------------------------------------------ */
+ public DateCache(String format,Locale l)
+ {
+ this(format,l,TimeZone.getDefault());
+ }
+
+ /* ------------------------------------------------------------ */
+ public DateCache(String format,Locale l,String tz)
+ {
+ this(format,l,TimeZone.getTimeZone(tz));
+ }
+
+ /* ------------------------------------------------------------ */
+ public DateCache(String format,Locale l,TimeZone tz)
+ {
+ _formatString=format;
+ _locale = l;
+
+
+ int zIndex = _formatString.indexOf( "ZZZ" );
+ if( zIndex >= 0 )
+ {
+ String ss1 = _formatString.substring( 0, zIndex );
+ String ss2 = _formatString.substring( zIndex+3 );
+ int tzOffset = tz.getRawOffset();
+
+ StringBuilder sb = new StringBuilder(_formatString.length()+10);
+ sb.append(ss1);
+ sb.append("'");
+ if( tzOffset >= 0 )
+ sb.append( '+' );
+ else
+ {
+ tzOffset = -tzOffset;
+ sb.append( '-' );
+ }
+
+ int raw = tzOffset / (1000*60); // Convert to seconds
+ int hr = raw / 60;
+ int min = raw % 60;
+
+ if( hr < 10 )
+ sb.append( '0' );
+ sb.append( hr );
+ if( min < 10 )
+ sb.append( '0' );
+ sb.append( min );
+ sb.append( '\'' );
+
+ sb.append(ss2);
+ _tzFormatString=sb.toString();
+ }
+ else
+ _tzFormatString=_formatString;
+
+ if( _locale != null )
+ {
+ _tzFormat=new SimpleDateFormat(_tzFormatString,_locale);
+ }
+ else
+ {
+ _tzFormat=new SimpleDateFormat(_tzFormatString);
+ }
+ _tzFormat.setTimeZone(tz);
+
+ _tick=null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public TimeZone getTimeZone()
+ {
+ return _tzFormat.getTimeZone();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Format a date according to our stored formatter.
+ * @param inDate
+ * @return Formatted date
+ */
+ public String format(Date inDate)
+ {
+ long seconds = inDate.getTime() / 1000;
+
+ Tick tick=_tick;
+
+ // Is this the cached time
+ if (tick==null || seconds!=tick._seconds)
+ {
+ // It's a cache miss
+ synchronized (this)
+ {
+ return _tzFormat.format(inDate);
+ }
+ }
+
+ return tick._string;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Format a date according to our stored formatter.
+ * If it happens to be in the same second as the last formatNow
+ * call, then the format is reused.
+ * @param inDate
+ * @return Formatted date
+ */
+ public String format(long inDate)
+ {
+ long seconds = inDate / 1000;
+
+ Tick tick=_tick;
+
+ // Is this the cached time
+ if (tick==null || seconds!=tick._seconds)
+ {
+ // It's a cache miss
+ Date d = new Date(inDate);
+ synchronized (this)
+ {
+ return _tzFormat.format(d);
+ }
+ }
+
+ return tick._string;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Format a date according to our stored formatter.
+ * The passed time is expected to be close to the current time, so it is
+ * compared to the last value passed and if it is within the same second,
+ * the format is reused. Otherwise a new cached format is created.
+ * @param now
+ * @return Formatted date
+ */
+ public String formatNow(long now)
+ {
+ long seconds = now / 1000;
+
+ Tick tick=_tick;
+
+ // Is this the cached time
+ if (tick!=null && tick._seconds==seconds)
+ return tick._string;
+ return formatTick(now)._string;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String now()
+ {
+ return formatNow(System.currentTimeMillis());
+ }
+
+ /* ------------------------------------------------------------ */
+ public Tick tick()
+ {
+ return formatTick(System.currentTimeMillis());
+ }
+
+ /* ------------------------------------------------------------ */
+ protected Tick formatTick(long now)
+ {
+ long seconds = now / 1000;
+
+ // Synchronize to protect _tzFormat
+ synchronized (this)
+ {
+ // recheck the tick, to save multiple formats
+ if (_tick==null || _tick._seconds!=seconds)
+ {
+ String s= _tzFormat.format(new Date(now));
+ return _tick=new Tick(seconds,s);
+ }
+ return _tick;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getFormatString()
+ {
+ return _formatString;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>A container for name/value pairs, known as fields.</p>
+ * <p>A {@link Field} is composed of a name string that can be case-sensitive
+ * or case-insensitive (by specifying the option at the constructor) and
+ * of a case-sensitive set of value strings.</p>
+ * <p>The implementation of this class is not thread safe.</p>
+ */
+public class Fields implements Iterable<Fields.Field>
+{
+ private final boolean caseSensitive;
+ private final Map<String, Field> fields;
+
+ /**
+ * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+ * @see #Fields(Fields, boolean)
+ */
+ public Fields()
+ {
+ this(false);
+ }
+
+ /**
+ * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
+ * @param caseSensitive whether this {@link Fields} instance must be case sensitive
+ * @see #Fields(Fields, boolean)
+ */
+ public Fields(boolean caseSensitive)
+ {
+ this.caseSensitive = caseSensitive;
+ fields = new LinkedHashMap<>();
+ }
+
+ /**
+ * <p>Creates a {@link Fields} instance by copying the fields from the given
+ * {@link Fields} and making it (im)mutable depending on the given {@code immutable} parameter</p>
+ *
+ * @param original the {@link Fields} to copy fields from
+ * @param immutable whether this instance is immutable
+ */
+ public Fields(Fields original, boolean immutable)
+ {
+ this.caseSensitive = original.caseSensitive;
+ Map<String, Field> copy = new LinkedHashMap<>();
+ copy.putAll(original.fields);
+ fields = immutable ? Collections.unmodifiableMap(copy) : copy;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null || getClass() != obj.getClass())
+ return false;
+ Fields that = (Fields)obj;
+ if (getSize() != that.getSize())
+ return false;
+ if (caseSensitive != that.caseSensitive)
+ return false;
+ for (Map.Entry<String, Field> entry : fields.entrySet())
+ {
+ String name = entry.getKey();
+ Field value = entry.getValue();
+ if (!value.equals(that.get(name), caseSensitive))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return fields.hashCode();
+ }
+
+ /**
+ * @return a set of field names
+ */
+ public Set<String> getNames()
+ {
+ Set<String> result = new LinkedHashSet<>();
+ for (Field field : fields.values())
+ result.add(field.getName());
+ return result;
+ }
+
+ private String normalizeName(String name)
+ {
+ return caseSensitive ? name : name.toLowerCase(Locale.ENGLISH);
+ }
+
+ /**
+ * @param name the field name
+ * @return the {@link Field} with the given name, or null if no such field exists
+ */
+ public Field get(String name)
+ {
+ return fields.get(normalizeName(name));
+ }
+
+ /**
+ * <p>Inserts or replaces the given name/value pair as a single-valued {@link Field}.</p>
+ *
+ * @param name the field name
+ * @param value the field value
+ */
+ public void put(String name, String value)
+ {
+ // Preserve the case for the field name
+ Field field = new Field(name, value);
+ fields.put(normalizeName(name), field);
+ }
+
+ /**
+ * <p>Inserts or replaces the given {@link Field}, mapped to the {@link Field#getName() field's name}</p>
+ *
+ * @param field the field to put
+ */
+ public void put(Field field)
+ {
+ if (field != null)
+ fields.put(normalizeName(field.getName()), field);
+ }
+
+ /**
+ * <p>Adds the given value to a field with the given name,
+ * creating a {@link Field} is none exists for the given name.</p>
+ *
+ * @param name the field name
+ * @param value the field value to add
+ */
+ public void add(String name, String value)
+ {
+ String key = normalizeName(name);
+ Field field = fields.get(key);
+ if (field == null)
+ {
+ // Preserve the case for the field name
+ field = new Field(name, value);
+ fields.put(key, field);
+ }
+ else
+ {
+ field = new Field(field.getName(), field.getValues(), value);
+ fields.put(key, field);
+ }
+ }
+
+ /**
+ * <p>Removes the {@link Field} with the given name</p>
+ *
+ * @param name the name of the field to remove
+ * @return the removed field, or null if no such field existed
+ */
+ public Field remove(String name)
+ {
+ return fields.remove(normalizeName(name));
+ }
+
+ /**
+ * <p>Empties this {@link Fields} instance from all fields</p>
+ * @see #isEmpty()
+ */
+ public void clear()
+ {
+ fields.clear();
+ }
+
+ /**
+ * @return whether this {@link Fields} instance is empty
+ */
+ public boolean isEmpty()
+ {
+ return fields.isEmpty();
+ }
+
+ /**
+ * @return the number of fields
+ */
+ public int getSize()
+ {
+ return fields.size();
+ }
+
+ /**
+ * @return an iterator over the {@link Field}s present in this instance
+ */
+ @Override
+ public Iterator<Field> iterator()
+ {
+ return fields.values().iterator();
+ }
+
+ @Override
+ public String toString()
+ {
+ return fields.toString();
+ }
+
+ /**
+ * <p>A named list of string values.</p>
+ * <p>The name is case-sensitive and there must be at least one value.</p>
+ */
+ public static class Field
+ {
+ private final String name;
+ private final List<String> values;
+
+ public Field(String name, String value)
+ {
+ this(name, Collections.singletonList(value));
+ }
+
+ private Field(String name, List<String> values, String... moreValues)
+ {
+ this.name = name;
+ List<String> list = new ArrayList<>(values.size() + moreValues.length);
+ list.addAll(values);
+ list.addAll(Arrays.asList(moreValues));
+ this.values = Collections.unmodifiableList(list);
+ }
+
+ public boolean equals(Field that, boolean caseSensitive)
+ {
+ if (this == that)
+ return true;
+ if (that == null)
+ return false;
+ if (caseSensitive)
+ return equals(that);
+ return name.equalsIgnoreCase(that.name) && values.equals(that.values);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null || getClass() != obj.getClass())
+ return false;
+ Field that = (Field)obj;
+ return name.equals(that.name) && values.equals(that.values);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = name.hashCode();
+ result = 31 * result + values.hashCode();
+ return result;
+ }
+
+ /**
+ * @return the field's name
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * @return the first field's value
+ */
+ public String getValue()
+ {
+ return values.get(0);
+ }
+
+ /**
+ * <p>Attempts to convert the result of {@link #getValue()} to an integer,
+ * returning it if the conversion is successful; returns null if the
+ * result of {@link #getValue()} is null.</p>
+ *
+ * @return the result of {@link #getValue()} converted to an integer, or null
+ * @throws NumberFormatException if the conversion fails
+ */
+ public Integer getValueAsInt()
+ {
+ final String value = getValue();
+ return value == null ? null : Integer.valueOf(value);
+ }
+
+ /**
+ * @return the field's values
+ */
+ public List<String> getValues()
+ {
+ return values;
+ }
+
+ /**
+ * @return whether the field has multiple values
+ */
+ public boolean hasMultipleValues()
+ {
+ return values.size() > 1;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s=%s", name, values);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+/**
+ * Utility class that splits calls to {@link #invoke(Object)} into calls to {@link #fork(Object)} or {@link #call(Object)}
+ * depending on the max number of reentrant calls to {@link #invoke(Object)}.
+ * <p/>
+ * This class prevents {@link StackOverflowError}s in case of methods that end up invoking themselves,
+ * such is common for {@link Callback#succeeded()}.
+ * <p/>
+ * Typical use case is:
+ * <pre>
+ * public void reentrantMethod(Object param)
+ * {
+ * if (condition || tooManyReenters)
+ * fork(param)
+ * else
+ * call(param)
+ * }
+ * </pre>
+ * Calculating {@code tooManyReenters} usually involves using a {@link ThreadLocal} and algebra on the
+ * number of reentrant invocations, which is factored out in this class for convenience.
+ * <p />
+ * The same code using this class becomes:
+ * <pre>
+ * private final ForkInvoker invoker = ...;
+ *
+ * public void reentrantMethod(Object param)
+ * {
+ * invoker.invoke(param);
+ * }
+ * </pre>
+ *
+ */
+public abstract class ForkInvoker<T>
+{
+ private static final ThreadLocal<Integer> __invocations = new ThreadLocal<Integer>()
+ {
+ @Override
+ protected Integer initialValue()
+ {
+ return 0;
+ }
+ };
+ private final int _maxInvocations;
+
+ /**
+ * Creates an instance with the given max number of reentrant calls to {@link #invoke(Object)}
+ * <p/>
+ * If {@code maxInvocations} is zero or negative, it is interpreted
+ * as if the max number of reentrant calls is infinite.
+ *
+ * @param maxInvocations the max number of reentrant calls to {@link #invoke(Object)}
+ */
+ public ForkInvoker(int maxInvocations)
+ {
+ _maxInvocations = maxInvocations;
+ }
+
+ /**
+ * Invokes either {@link #fork(Object)} or {@link #call(Object)}.
+ * If {@link #condition()} returns true, {@link #fork(Object)} is invoked.
+ * Otherwise, if the max number of reentrant calls is positive and the
+ * actual number of reentrant invocations exceeds it, {@link #fork(Object)} is invoked.
+ * Otherwise, {@link #call(Object)} is invoked.
+ * @param arg TODO
+ *
+ * @return true if {@link #fork(Object)} has been called, false otherwise
+ */
+ public boolean invoke(T arg)
+ {
+ boolean countInvocations = _maxInvocations > 0;
+ int invocations = __invocations.get();
+ if (condition() || countInvocations && invocations > _maxInvocations)
+ {
+ fork(arg);
+ return true;
+ }
+ else
+ {
+ if (countInvocations)
+ __invocations.set(invocations + 1);
+ try
+ {
+ call(arg);
+ return false;
+ }
+ finally
+ {
+ if (countInvocations)
+ __invocations.set(invocations);
+ }
+ }
+ }
+
+ /**
+ * Subclasses should override this method returning true if they want
+ * {@link #invoke(Object)} to call {@link #fork(Object)}.
+ *
+ * @return true if {@link #invoke(Object)} should call {@link #fork(Object)}, false otherwise
+ */
+ protected boolean condition()
+ {
+ return false;
+ }
+
+ /**
+ * Executes the forked invocation
+ * @param arg TODO
+ */
+ public abstract void fork(T arg);
+
+ /**
+ * Executes the direct, non-forked, invocation
+ * @param arg TODO
+ */
+ public abstract void call(T arg);
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.IOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FutureCallback implements Future<Void>,Callback
+{
+ private static Throwable COMPLETED=new Throwable();
+ private final AtomicBoolean _done=new AtomicBoolean(false);
+ private final CountDownLatch _latch=new CountDownLatch(1);
+ private Throwable _cause;
+
+ public FutureCallback()
+ {}
+
+ public FutureCallback(boolean completed)
+ {
+ if (completed)
+ {
+ _cause=COMPLETED;
+ _done.set(true);
+ _latch.countDown();
+ }
+ }
+
+ public FutureCallback(Throwable failed)
+ {
+ _cause=failed;
+ _done.set(true);
+ _latch.countDown();
+ }
+
+ @Override
+ public void succeeded()
+ {
+ if (_done.compareAndSet(false,true))
+ {
+ _cause=COMPLETED;
+ _latch.countDown();
+ }
+ }
+
+ @Override
+ public void failed(Throwable cause)
+ {
+ if (_done.compareAndSet(false,true))
+ {
+ _cause=cause;
+ _latch.countDown();
+ }
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning)
+ {
+ if (_done.compareAndSet(false,true))
+ {
+ _cause=new CancellationException();
+ _latch.countDown();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled()
+ {
+ if (_done.get())
+ {
+ try
+ {
+ _latch.await();
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ return _cause instanceof CancellationException;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isDone()
+ {
+ return _done.get() && _latch.getCount()==0;
+ }
+
+ @Override
+ public Void get() throws InterruptedException, ExecutionException
+ {
+ _latch.await();
+ if (_cause==COMPLETED)
+ return null;
+ if (_cause instanceof CancellationException)
+ throw (CancellationException) new CancellationException().initCause(_cause);
+ throw new ExecutionException(_cause);
+ }
+
+ @Override
+ public Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+ {
+ if (!_latch.await(timeout,unit))
+ throw new TimeoutException();
+
+ if (_cause==COMPLETED)
+ return null;
+ if (_cause instanceof TimeoutException)
+ throw (TimeoutException)_cause;
+ if (_cause instanceof CancellationException)
+ throw (CancellationException) new CancellationException().initCause(_cause);
+ throw new ExecutionException(_cause);
+ }
+
+ public static void rethrow(ExecutionException e) throws IOException
+ {
+ Throwable cause=e.getCause();
+ if (cause instanceof IOException)
+ throw (IOException)cause;
+ if (cause instanceof Error)
+ throw (Error)cause;
+ if (cause instanceof RuntimeException)
+ throw (RuntimeException)cause;
+ throw new RuntimeException(cause);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("FutureCallback@%x{%b,%b}",hashCode(),_done,_cause==COMPLETED);
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.IOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class FuturePromise<C> implements Future<C>,Promise<C>
+{
+ private static Throwable COMPLETED=new Throwable();
+ private final AtomicBoolean _done=new AtomicBoolean(false);
+ private final CountDownLatch _latch=new CountDownLatch(1);
+ private Throwable _cause;
+ private C _result;
+
+ public FuturePromise()
+ {}
+
+ public FuturePromise(C result)
+ {
+ _cause=COMPLETED;
+ _result=result;
+ _done.set(true);
+ _latch.countDown();
+ }
+
+ public FuturePromise(C ctx, Throwable failed)
+ {
+ _result=ctx;
+ _cause=failed;
+ _done.set(true);
+ _latch.countDown();
+ }
+
+ @Override
+ public void succeeded(C result)
+ {
+ if (_done.compareAndSet(false,true))
+ {
+ _result=result;
+ _cause=COMPLETED;
+ _latch.countDown();
+ }
+ }
+
+ @Override
+ public void failed(Throwable cause)
+ {
+ if (_done.compareAndSet(false,true))
+ {
+ _cause=cause;
+ _latch.countDown();
+ }
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning)
+ {
+ if (_done.compareAndSet(false,true))
+ {
+ _result=null;
+ _cause=new CancellationException();
+ _latch.countDown();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled()
+ {
+ if (_done.get())
+ {
+ try
+ {
+ _latch.await();
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ return _cause instanceof CancellationException;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isDone()
+ {
+ return _done.get() && _latch.getCount()==0;
+ }
+
+ @Override
+ public C get() throws InterruptedException, ExecutionException
+ {
+ _latch.await();
+ if (_cause==COMPLETED)
+ return _result;
+ if (_cause instanceof CancellationException)
+ throw (CancellationException) new CancellationException().initCause(_cause);
+ throw new ExecutionException(_cause);
+ }
+
+ @Override
+ public C get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
+ {
+ if (!_latch.await(timeout,unit))
+ throw new TimeoutException();
+
+ if (_cause==COMPLETED)
+ return _result;
+ if (_cause instanceof TimeoutException)
+ throw (TimeoutException)_cause;
+ if (_cause instanceof CancellationException)
+ throw (CancellationException) new CancellationException().initCause(_cause);
+ throw new ExecutionException(_cause);
+ }
+
+ public static void rethrow(ExecutionException e) throws IOException
+ {
+ Throwable cause=e.getCause();
+ if (cause instanceof IOException)
+ throw (IOException)cause;
+ if (cause instanceof Error)
+ throw (Error)cause;
+ if (cause instanceof RuntimeException)
+ throw (RuntimeException)cause;
+ throw new RuntimeException(cause);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("FutureCallback@%x{%b,%b,%s}",hashCode(),_done,_cause==COMPLETED,_result);
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@SuppressWarnings("serial")
+public class HostMap<TYPE> extends HashMap<String, TYPE>
+{
+
+ /* --------------------------------------------------------------- */
+ /** Construct empty HostMap.
+ */
+ public HostMap()
+ {
+ super(11);
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Construct empty HostMap.
+ *
+ * @param capacity initial capacity
+ */
+ public HostMap(int capacity)
+ {
+ super (capacity);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
+ */
+ @Override
+ public TYPE put(String host, TYPE object)
+ throws IllegalArgumentException
+ {
+ return super.put(host, object);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see java.util.HashMap#get(java.lang.Object)
+ */
+ @Override
+ public TYPE get(Object key)
+ {
+ return super.get(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve a lazy list of map entries associated with specified
+ * hostname by taking into account the domain suffix matches.
+ *
+ * @param host hostname
+ * @return lazy list of map entries
+ */
+ public Object getLazyMatches(String host)
+ {
+ if (host == null)
+ return LazyList.getList(super.entrySet());
+
+ int idx = 0;
+ String domain = host.trim();
+ HashSet<String> domains = new HashSet<String>();
+ do {
+ domains.add(domain);
+ if ((idx = domain.indexOf('.')) > 0)
+ {
+ domain = domain.substring(idx+1);
+ }
+ } while (idx > 0);
+
+ Object entries = null;
+ for(Map.Entry<String, TYPE> entry: super.entrySet())
+ {
+ if (domains.contains(entry.getKey()))
+ {
+ entries = LazyList.add(entries,entry);
+ }
+ }
+
+ return entries;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.net.CookieManager;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of {@link CookieStore} that delegates to an instance created by {@link CookieManager}
+ * via {@link CookieManager#getCookieStore()}.
+ */
+public class HttpCookieStore implements CookieStore
+{
+ private final CookieStore delegate;
+
+ public HttpCookieStore()
+ {
+ delegate = new CookieManager().getCookieStore();
+ }
+
+ @Override
+ public void add(URI uri, HttpCookie cookie)
+ {
+ delegate.add(uri, cookie);
+ }
+
+ @Override
+ public List<HttpCookie> get(URI uri)
+ {
+ return delegate.get(uri);
+ }
+
+ @Override
+ public List<HttpCookie> getCookies()
+ {
+ return delegate.getCookies();
+ }
+
+ @Override
+ public List<URI> getURIs()
+ {
+ return delegate.getURIs();
+ }
+
+ @Override
+ public boolean remove(URI uri, HttpCookie cookie)
+ {
+ return delegate.remove(uri, cookie);
+ }
+
+ @Override
+ public boolean removeAll()
+ {
+ return delegate.removeAll();
+ }
+
+ public static class Empty implements CookieStore
+ {
+ @Override
+ public void add(URI uri, HttpCookie cookie)
+ {
+ }
+
+ @Override
+ public List<HttpCookie> get(URI uri)
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<HttpCookie> getCookies()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<URI> getURIs()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean remove(URI uri, HttpCookie cookie)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean removeAll()
+ {
+ return false;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ======================================================================== */
+/** IO Utilities.
+ * Provides stream handling utilities in
+ * singleton Threadpool implementation accessed by static members.
+ */
+public class IO
+{
+ private static final Logger LOG = Log.getLogger(IO.class);
+
+ /* ------------------------------------------------------------------- */
+ public final static String
+ CRLF = "\015\012";
+
+ /* ------------------------------------------------------------------- */
+ public final static byte[]
+ CRLF_BYTES = {(byte)'\015',(byte)'\012'};
+
+ /* ------------------------------------------------------------------- */
+ public static final int bufferSize = 64*1024;
+
+ /* ------------------------------------------------------------------- */
+ static class Job implements Runnable
+ {
+ InputStream in;
+ OutputStream out;
+ Reader read;
+ Writer write;
+
+ Job(InputStream in,OutputStream out)
+ {
+ this.in=in;
+ this.out=out;
+ this.read=null;
+ this.write=null;
+ }
+ Job(Reader read,Writer write)
+ {
+ this.in=null;
+ this.out=null;
+ this.read=read;
+ this.write=write;
+ }
+
+ /* ------------------------------------------------------------ */
+ /*
+ * @see java.lang.Runnable#run()
+ */
+ public void run()
+ {
+ try {
+ if (in!=null)
+ copy(in,out,-1);
+ else
+ copy(read,write,-1);
+ }
+ catch(IOException e)
+ {
+ LOG.ignore(e);
+ try{
+ if (out!=null)
+ out.close();
+ if (write!=null)
+ write.close();
+ }
+ catch(IOException e2)
+ {
+ LOG.ignore(e2);
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------------- */
+ /** Copy Stream in to Stream out until EOF or exception.
+ */
+ public static void copy(InputStream in, OutputStream out)
+ throws IOException
+ {
+ copy(in,out,-1);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /** Copy Reader to Writer out until EOF or exception.
+ */
+ public static void copy(Reader in, Writer out)
+ throws IOException
+ {
+ copy(in,out,-1);
+ }
+
+ /* ------------------------------------------------------------------- */
+ /** Copy Stream in to Stream for byteCount bytes or until EOF or exception.
+ */
+ public static void copy(InputStream in,
+ OutputStream out,
+ long byteCount)
+ throws IOException
+ {
+ byte buffer[] = new byte[bufferSize];
+ int len=bufferSize;
+
+ if (byteCount>=0)
+ {
+ while (byteCount>0)
+ {
+ int max = byteCount<bufferSize?(int)byteCount:bufferSize;
+ len=in.read(buffer,0,max);
+
+ if (len==-1)
+ break;
+
+ byteCount -= len;
+ out.write(buffer,0,len);
+ }
+ }
+ else
+ {
+ while (true)
+ {
+ len=in.read(buffer,0,bufferSize);
+ if (len<0 )
+ break;
+ out.write(buffer,0,len);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------------- */
+ /** Copy Reader to Writer for byteCount bytes or until EOF or exception.
+ */
+ public static void copy(Reader in,
+ Writer out,
+ long byteCount)
+ throws IOException
+ {
+ char buffer[] = new char[bufferSize];
+ int len=bufferSize;
+
+ if (byteCount>=0)
+ {
+ while (byteCount>0)
+ {
+ if (byteCount<bufferSize)
+ len=in.read(buffer,0,(int)byteCount);
+ else
+ len=in.read(buffer,0,bufferSize);
+
+ if (len==-1)
+ break;
+
+ byteCount -= len;
+ out.write(buffer,0,len);
+ }
+ }
+ else if (out instanceof PrintWriter)
+ {
+ PrintWriter pout=(PrintWriter)out;
+ while (!pout.checkError())
+ {
+ len=in.read(buffer,0,bufferSize);
+ if (len==-1)
+ break;
+ out.write(buffer,0,len);
+ }
+ }
+ else
+ {
+ while (true)
+ {
+ len=in.read(buffer,0,bufferSize);
+ if (len==-1)
+ break;
+ out.write(buffer,0,len);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Copy files or directories
+ * @param from
+ * @param to
+ * @throws IOException
+ */
+ public static void copy(File from,File to) throws IOException
+ {
+ if (from.isDirectory())
+ copyDir(from,to);
+ else
+ copyFile(from,to);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void copyDir(File from,File to) throws IOException
+ {
+ if (to.exists())
+ {
+ if (!to.isDirectory())
+ throw new IllegalArgumentException(to.toString());
+ }
+ else
+ to.mkdirs();
+
+ File[] files = from.listFiles();
+ if (files!=null)
+ {
+ for (int i=0;i<files.length;i++)
+ {
+ String name = files[i].getName();
+ if (".".equals(name) || "..".equals(name))
+ continue;
+ copy(files[i],new File(to,name));
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void copyFile(File from,File to) throws IOException
+ {
+ try (InputStream in=new FileInputStream(from);
+ OutputStream out=new FileOutputStream(to))
+ {
+ copy(in,out);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Read input stream to string.
+ */
+ public static String toString(InputStream in)
+ throws IOException
+ {
+ return toString(in,(Charset)null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Read input stream to string.
+ */
+ public static String toString(InputStream in,String encoding)
+ throws IOException
+ {
+ return toString(in, encoding==null?null:Charset.forName(encoding));
+ }
+
+ /** Read input stream to string.
+ */
+ public static String toString(InputStream in, Charset encoding)
+ throws IOException
+ {
+ StringWriter writer=new StringWriter();
+ InputStreamReader reader = encoding==null?new InputStreamReader(in):new InputStreamReader(in,encoding);
+
+ copy(reader,writer);
+ return writer.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Read input stream to string.
+ */
+ public static String toString(Reader in)
+ throws IOException
+ {
+ StringWriter writer=new StringWriter();
+ copy(in,writer);
+ return writer.toString();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Delete File.
+ * This delete will recursively delete directories - BE CAREFULL
+ * @param file The file to be deleted.
+ */
+ public static boolean delete(File file)
+ {
+ if (!file.exists())
+ return false;
+ if (file.isDirectory())
+ {
+ File[] files = file.listFiles();
+ for (int i=0;files!=null && i<files.length;i++)
+ delete(files[i]);
+ }
+ return file.delete();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * closes an input stream, and logs exceptions
+ *
+ * @param is the input stream to close
+ */
+ public static void close(InputStream is)
+ {
+ try
+ {
+ if (is != null)
+ is.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ /**
+ * closes a reader, and logs exceptions
+ *
+ * @param reader the reader to close
+ */
+ public static void close(Reader reader)
+ {
+ try
+ {
+ if (reader != null)
+ reader.close();
+ } catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ /**
+ * closes a writer, and logs exceptions
+ *
+ * @param writer the writer to close
+ */
+ public static void close(Writer writer)
+ {
+ try
+ {
+ if (writer != null)
+ writer.close();
+ } catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static byte[] readBytes(InputStream in)
+ throws IOException
+ {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ copy(in,bout);
+ return bout.toByteArray();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * closes an output stream, and logs exceptions
+ *
+ * @param os the output stream to close
+ */
+ public static void close(OutputStream os)
+ {
+ try
+ {
+ if (os != null)
+ os.close();
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return An outputstream to nowhere
+ */
+ public static OutputStream getNullStream()
+ {
+ return __nullStream;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return An outputstream to nowhere
+ */
+ public static InputStream getClosedStream()
+ {
+ return __closedStream;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class NullOS extends OutputStream
+ {
+ @Override
+ public void close(){}
+ @Override
+ public void flush(){}
+ @Override
+ public void write(byte[]b){}
+ @Override
+ public void write(byte[]b,int i,int l){}
+ @Override
+ public void write(int b){}
+ }
+ private static NullOS __nullStream = new NullOS();
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class ClosedIS extends InputStream
+ {
+ @Override
+ public int read() throws IOException
+ {
+ return -1;
+ }
+ }
+ private static ClosedIS __closedStream = new ClosedIS();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return An writer to nowhere
+ */
+ public static Writer getNullWriter()
+ {
+ return __nullWriter;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return An writer to nowhere
+ */
+ public static PrintWriter getNullPrintWriter()
+ {
+ return __nullPrintWriter;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private static class NullWrite extends Writer
+ {
+ @Override
+ public void close(){}
+ @Override
+ public void flush(){}
+ @Override
+ public void write(char[]b){}
+ @Override
+ public void write(char[]b,int o,int l){}
+ @Override
+ public void write(int b){}
+ @Override
+ public void write(String s){}
+ @Override
+ public void write(String s,int o,int l){}
+ }
+ private static NullWrite __nullWriter = new NullWrite();
+ private static PrintWriter __nullPrintWriter = new PrintWriter(__nullWriter);
+}
+
+
+
+
+
+
+
+
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Internet address map to object
+ * <p>
+ * Internet addresses may be specified as absolute address or as a combination of
+ * four octet wildcard specifications (a.b.c.d) that are defined as follows.
+ * </p>
+ * <pre>
+ * nnn - an absolute value (0-255)
+ * mmm-nnn - an inclusive range of absolute values,
+ * with following shorthand notations:
+ * nnn- => nnn-255
+ * -nnn => 0-nnn
+ * - => 0-255
+ * a,b,... - a list of wildcard specifications
+ * </pre>
+ */
+@SuppressWarnings("serial")
+public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
+{
+ private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
+
+ /* --------------------------------------------------------------- */
+ /** Construct empty IPAddressMap.
+ */
+ public IPAddressMap()
+ {
+ super(11);
+ }
+
+ /* --------------------------------------------------------------- */
+ /** Construct empty IPAddressMap.
+ *
+ * @param capacity initial capacity
+ */
+ public IPAddressMap(int capacity)
+ {
+ super (capacity);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Insert a new internet address into map
+ *
+ * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
+ */
+ @Override
+ public TYPE put(String addrSpec, TYPE object)
+ throws IllegalArgumentException
+ {
+ if (addrSpec == null || addrSpec.trim().length() == 0)
+ throw new IllegalArgumentException("Invalid IP address pattern: "+addrSpec);
+
+ String spec = addrSpec.trim();
+ if (_patterns.get(spec) == null)
+ _patterns.put(spec,new IPAddrPattern(spec));
+
+ return super.put(spec, object);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the object mapped to the specified internet address literal
+ *
+ * @see java.util.HashMap#get(java.lang.Object)
+ */
+ @Override
+ public TYPE get(Object key)
+ {
+ return super.get(key);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the first object that is associated with the specified
+ * internet address by taking into account the wildcard specifications.
+ *
+ * @param addr internet address
+ * @return associated object
+ */
+ public TYPE match(String addr)
+ {
+ Map.Entry<String, TYPE> entry = getMatch(addr);
+ return entry==null ? null : entry.getValue();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve the first map entry that is associated with the specified
+ * internet address by taking into account the wildcard specifications.
+ *
+ * @param addr internet address
+ * @return map entry associated
+ */
+ public Map.Entry<String, TYPE> getMatch(String addr)
+ {
+ if (addr != null)
+ {
+ for(Map.Entry<String, TYPE> entry: super.entrySet())
+ {
+ if (_patterns.get(entry.getKey()).match(addr))
+ {
+ return entry;
+ }
+ }
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieve a lazy list of map entries associated with specified
+ * internet address by taking into account the wildcard specifications.
+ *
+ * @param addr internet address
+ * @return lazy list of map entries
+ */
+ public Object getLazyMatches(String addr)
+ {
+ if (addr == null)
+ return LazyList.getList(super.entrySet());
+
+ Object entries = null;
+ for(Map.Entry<String, TYPE> entry: super.entrySet())
+ {
+ if (_patterns.get(entry.getKey()).match(addr))
+ {
+ entries = LazyList.add(entries,entry);
+ }
+ }
+ return entries;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * IPAddrPattern
+ *
+ * Represents internet address wildcard.
+ * Matches the wildcard to provided internet address.
+ */
+ private static class IPAddrPattern
+ {
+ private final OctetPattern[] _octets = new OctetPattern[4];
+ /* ------------------------------------------------------------ */
+ /**
+ * Create new IPAddrPattern
+ *
+ * @param value internet address wildcard specification
+ * @throws IllegalArgumentException if wildcard specification is invalid
+ */
+ public IPAddrPattern(String value)
+ throws IllegalArgumentException
+ {
+ if (value == null || value.trim().length() == 0)
+ throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+
+ try
+ {
+ StringTokenizer parts = new StringTokenizer(value, ".");
+
+ String part;
+ for (int idx=0; idx<4; idx++)
+ {
+ part = parts.hasMoreTokens() ? parts.nextToken().trim() : "0-255";
+
+ int len = part.length();
+ if (len == 0 && parts.hasMoreTokens())
+ throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+
+ _octets[idx] = new OctetPattern(len==0 ? "0-255" : part);
+ }
+ }
+ catch (IllegalArgumentException ex)
+ {
+ throw new IllegalArgumentException("Invalid IP address pattern: "+value, ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Match the specified internet address against the wildcard
+ *
+ * @param value internet address
+ * @return true if specified internet address matches wildcard specification
+ *
+ * @throws IllegalArgumentException if specified internet address is invalid
+ */
+ public boolean match(String value)
+ throws IllegalArgumentException
+ {
+ if (value == null || value.trim().length() == 0)
+ throw new IllegalArgumentException("Invalid IP address: "+value);
+
+ try
+ {
+ StringTokenizer parts = new StringTokenizer(value, ".");
+
+ boolean result = true;
+ for (int idx=0; idx<4; idx++)
+ {
+ if (!parts.hasMoreTokens())
+ throw new IllegalArgumentException("Invalid IP address: "+value);
+
+ if (!(result &= _octets[idx].match(parts.nextToken())))
+ break;
+ }
+ return result;
+ }
+ catch (IllegalArgumentException ex)
+ {
+ throw new IllegalArgumentException("Invalid IP address: "+value, ex);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * OctetPattern
+ *
+ * Represents a single octet wildcard.
+ * Matches the wildcard to the specified octet value.
+ */
+ private static class OctetPattern extends BitSet
+ {
+ private final BitSet _mask = new BitSet(256);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Create new OctetPattern
+ *
+ * @param octetSpec octet wildcard specification
+ * @throws IllegalArgumentException if wildcard specification is invalid
+ */
+ public OctetPattern(String octetSpec)
+ throws IllegalArgumentException
+ {
+ try
+ {
+ if (octetSpec != null)
+ {
+ String spec = octetSpec.trim();
+ if(spec.length() == 0)
+ {
+ _mask.set(0,255);
+ }
+ else
+ {
+ StringTokenizer parts = new StringTokenizer(spec,",");
+ while (parts.hasMoreTokens())
+ {
+ String part = parts.nextToken().trim();
+ if (part.length() > 0)
+ {
+ if (part.indexOf('-') < 0)
+ {
+ Integer value = Integer.valueOf(part);
+ _mask.set(value);
+ }
+ else
+ {
+ int low = 0, high = 255;
+
+ String[] bounds = part.split("-",-2);
+ if (bounds.length != 2)
+ {
+ throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+ }
+
+ if (bounds[0].length() > 0)
+ {
+ low = Integer.parseInt(bounds[0]);
+ }
+ if (bounds[1].length() > 0)
+ {
+ high = Integer.parseInt(bounds[1]);
+ }
+
+ if (low > high)
+ {
+ throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+ }
+
+ _mask.set(low, high+1);
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new IllegalArgumentException("Invalid octet spec: "+octetSpec, ex);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Match specified octet value against the wildcard
+ *
+ * @param value octet value
+ * @return true if specified octet value matches the wildcard
+ * @throws IllegalArgumentException if specified octet value is invalid
+ */
+ public boolean match(String value)
+ throws IllegalArgumentException
+ {
+ if (value == null || value.trim().length() == 0)
+ throw new IllegalArgumentException("Invalid octet: "+value);
+
+ try
+ {
+ int number = Integer.parseInt(value);
+ return match(number);
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new IllegalArgumentException("Invalid octet: "+value);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Match specified octet value against the wildcard
+ *
+ * @param number octet value
+ * @return true if specified octet value matches the wildcard
+ * @throws IllegalArgumentException if specified octet value is invalid
+ */
+ public boolean match(int number)
+ throws IllegalArgumentException
+ {
+ if (number < 0 || number > 255)
+ throw new IllegalArgumentException("Invalid octet: "+number);
+
+ return _mask.get(number);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * IntrospectionUtil
+ *
+ *
+ */
+public class IntrospectionUtil
+{
+
+ public static boolean isJavaBeanCompliantSetter (Method method)
+ {
+ if (method == null)
+ return false;
+
+ if (method.getReturnType() != Void.TYPE)
+ return false;
+
+ if (!method.getName().startsWith("set"))
+ return false;
+
+ if (method.getParameterTypes().length != 1)
+ return false;
+
+ return true;
+ }
+
+ public static Method findMethod (Class<?> clazz, String methodName, Class<?>[] args, boolean checkInheritance, boolean strictArgs)
+ throws NoSuchMethodException
+ {
+ if (clazz == null)
+ throw new NoSuchMethodException("No class");
+ if (methodName==null || methodName.trim().equals(""))
+ throw new NoSuchMethodException("No method name");
+
+ Method method = null;
+ Method[] methods = clazz.getDeclaredMethods();
+ for (int i=0;i<methods.length && method==null;i++)
+ {
+ if (methods[i].getName().equals(methodName) && checkParams(methods[i].getParameterTypes(), (args==null?new Class[] {}:args), strictArgs))
+ {
+ method = methods[i];
+ }
+
+ }
+ if (method!=null)
+ {
+ return method;
+ }
+ else if (checkInheritance)
+ return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
+ else
+ throw new NoSuchMethodException("No such method "+methodName+" on class "+clazz.getName());
+
+ }
+
+
+
+
+
+ public static Field findField (Class<?> clazz, String targetName, Class<?> targetType, boolean checkInheritance, boolean strictType)
+ throws NoSuchFieldException
+ {
+ if (clazz == null)
+ throw new NoSuchFieldException("No class");
+ if (targetName==null)
+ throw new NoSuchFieldException("No field name");
+
+ try
+ {
+ Field field = clazz.getDeclaredField(targetName);
+ if (strictType)
+ {
+ if (field.getType().equals(targetType))
+ return field;
+ }
+ else
+ {
+ if (field.getType().isAssignableFrom(targetType))
+ return field;
+ }
+ if (checkInheritance)
+ {
+ return findInheritedField(clazz.getPackage(), clazz.getSuperclass(), targetName, targetType, strictType);
+ }
+ else
+ throw new NoSuchFieldException("No field with name "+targetName+" in class "+clazz.getName()+" of type "+targetType);
+ }
+ catch (NoSuchFieldException e)
+ {
+ return findInheritedField(clazz.getPackage(),clazz.getSuperclass(), targetName,targetType,strictType);
+ }
+ }
+
+
+
+
+
+ public static boolean isInheritable (Package pack, Member member)
+ {
+ if (pack==null)
+ return false;
+ if (member==null)
+ return false;
+
+ int modifiers = member.getModifiers();
+ if (Modifier.isPublic(modifiers))
+ return true;
+ if (Modifier.isProtected(modifiers))
+ return true;
+ if (!Modifier.isPrivate(modifiers) && pack.equals(member.getDeclaringClass().getPackage()))
+ return true;
+
+ return false;
+ }
+
+
+
+
+ public static boolean checkParams (Class<?>[] formalParams, Class<?>[] actualParams, boolean strict)
+ {
+ if (formalParams==null)
+ return actualParams==null;
+ if (actualParams==null)
+ return false;
+
+ if (formalParams.length!=actualParams.length)
+ return false;
+
+ if (formalParams.length==0)
+ return true;
+
+ int j=0;
+ if (strict)
+ {
+ while (j<formalParams.length && formalParams[j].equals(actualParams[j]))
+ j++;
+ }
+ else
+ {
+ while ((j<formalParams.length) && (formalParams[j].isAssignableFrom(actualParams[j])))
+ {
+ j++;
+ }
+ }
+
+ if (j!=formalParams.length)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ public static boolean isSameSignature (Method methodA, Method methodB)
+ {
+ if (methodA==null)
+ return false;
+ if (methodB==null)
+ return false;
+
+ List<Class<?>> parameterTypesA = Arrays.asList(methodA.getParameterTypes());
+ List<Class<?>> parameterTypesB = Arrays.asList(methodB.getParameterTypes());
+
+ if (methodA.getName().equals(methodB.getName())
+ &&
+ parameterTypesA.containsAll(parameterTypesB))
+ return true;
+
+ return false;
+ }
+
+ public static boolean isTypeCompatible (Class<?> formalType, Class<?> actualType, boolean strict)
+ {
+ if (formalType==null)
+ return actualType==null;
+ if (actualType==null)
+ return false;
+
+ if (strict)
+ return formalType.equals(actualType);
+ else
+ return formalType.isAssignableFrom(actualType);
+ }
+
+
+
+
+ public static boolean containsSameMethodSignature (Method method, Class<?> c, boolean checkPackage)
+ {
+ if (checkPackage)
+ {
+ if (!c.getPackage().equals(method.getDeclaringClass().getPackage()))
+ return false;
+ }
+
+ boolean samesig = false;
+ Method[] methods = c.getDeclaredMethods();
+ for (int i=0; i<methods.length && !samesig; i++)
+ {
+ if (IntrospectionUtil.isSameSignature(method, methods[i]))
+ samesig = true;
+ }
+ return samesig;
+ }
+
+
+ public static boolean containsSameFieldName(Field field, Class<?> c, boolean checkPackage)
+ {
+ if (checkPackage)
+ {
+ if (!c.getPackage().equals(field.getDeclaringClass().getPackage()))
+ return false;
+ }
+
+ boolean sameName = false;
+ Field[] fields = c.getDeclaredFields();
+ for (int i=0;i<fields.length && !sameName; i++)
+ {
+ if (fields[i].getName().equals(field.getName()))
+ sameName = true;
+ }
+ return sameName;
+ }
+
+
+
+ protected static Method findInheritedMethod (Package pack, Class<?> clazz, String methodName, Class<?>[] args, boolean strictArgs)
+ throws NoSuchMethodException
+ {
+ if (clazz==null)
+ throw new NoSuchMethodException("No class");
+ if (methodName==null)
+ throw new NoSuchMethodException("No method name");
+
+ Method method = null;
+ Method[] methods = clazz.getDeclaredMethods();
+ for (int i=0;i<methods.length && method==null;i++)
+ {
+ if (methods[i].getName().equals(methodName)
+ && isInheritable(pack,methods[i])
+ && checkParams(methods[i].getParameterTypes(), args, strictArgs))
+ method = methods[i];
+ }
+ if (method!=null)
+ {
+ return method;
+ }
+ else
+ return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
+ }
+
+ protected static Field findInheritedField (Package pack, Class<?> clazz, String fieldName, Class<?> fieldType, boolean strictType)
+ throws NoSuchFieldException
+ {
+ if (clazz==null)
+ throw new NoSuchFieldException ("No class");
+ if (fieldName==null)
+ throw new NoSuchFieldException ("No field name");
+ try
+ {
+ Field field = clazz.getDeclaredField(fieldName);
+ if (isInheritable(pack, field) && isTypeCompatible(fieldType, field.getType(), strictType))
+ return field;
+ else
+ return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType);
+ }
+ catch (NoSuchFieldException e)
+ {
+ return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType);
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This specialized callback implements a pattern that allows
+ * a large job to be broken into smaller tasks using iteration
+ * rather than recursion.
+ * <p/>
+ * A typical example is the write of a large content to a socket,
+ * divided in chunks. Chunk C1 is written by thread T1, which
+ * also invokes the callback, which writes chunk C2, which invokes
+ * the callback again, which writes chunk C3, and so forth.
+ * <p/>
+ * The problem with the example is that if the callback thread
+ * is the same that performs the I/O operation, then the process
+ * is recursive and may result in a stack overflow.
+ * To avoid the stack overflow, a thread dispatch must be performed,
+ * causing context switching and cache misses, affecting performance.
+ * <p/>
+ * To avoid this issue, this callback uses an AtomicReference to
+ * record whether success callback has been called during the processing
+ * of a sub task, and if so then the processing iterates rather than
+ * recurring.
+ * <p/>
+ * Subclasses must implement method {@link #process()} where the sub
+ * task is executed and a suitable {@link IteratingCallback.Action} is
+ * returned to this callback to indicate the overall progress of the job.
+ * This callback is passed to the asynchronous execution of each sub
+ * task and a call the {@link #succeeded()} on this callback represents
+ * the completion of the sub task.
+ */
+public abstract class IteratingCallback implements Callback
+{
+ /**
+ * The indication of the overall progress of the overall job that
+ * implementations of {@link #process()} must return.
+ */
+ protected enum Action
+ {
+ /**
+ * Indicates that {@link #process()} has no more work to do,
+ * but the overall job is not completed yet, probably waiting
+ * for additional events to trigger more work.
+ */
+ IDLE,
+ /**
+ * Indicates that {@link #process()} is executing asynchronously
+ * a sub task, where the execution has started but the callback
+ * may have not yet been invoked.
+ */
+ SCHEDULED,
+ /**
+ * Indicates that {@link #process()} has completed the overall job.
+ */
+ SUCCEEDED,
+ /**
+ * Indicates that {@link #process()} has failed the overall job.
+ */
+ FAILED
+ }
+
+ private final AtomicReference<State> _state = new AtomicReference<>(State.INACTIVE);
+
+ /**
+ * Method called by {@link #iterate()} to process the sub task.
+ * <p/>
+ * Implementations must start the asynchronous execution of the sub task
+ * (if any) and return an appropriate action:
+ * <ul>
+ * <li>{@link Action#IDLE} when no sub tasks are available for execution
+ * but the overall job is not completed yet</li>
+ * <li>{@link Action#SCHEDULED} when the sub task asynchronous execution
+ * has been started</li>
+ * <li>{@link Action#SUCCEEDED} when the overall job is completed</li>
+ * <li>{@link Action#FAILED} when the overall job cannot be completed</li>
+ * </ul>
+ *
+ * @throws Exception if the sub task processing throws
+ */
+ protected abstract Action process() throws Exception;
+
+ /**
+ * Invoked when the overall task has completed successfully.
+ */
+ protected abstract void completed();
+
+ /**
+ * This method must be invoked by applications to start the processing
+ * of sub tasks.
+ * <p/>
+ * If {@link #process()} returns {@link Action#IDLE}, then this method
+ * should be called again to restart processing.
+ * It is safe to call iterate multiple times from multiple threads since only
+ * the first thread to move the state out of INACTIVE will actually do any iteration
+ * and processing.
+ */
+ public void iterate()
+ {
+ try
+ {
+ while (true)
+ {
+ switch (_state.get())
+ {
+ case INACTIVE:
+ {
+ if (processIterations())
+ return;
+ break;
+ }
+ case ITERATING:
+ {
+ if (_state.compareAndSet(State.ITERATING, State.ITERATE_AGAIN))
+ return;
+ break;
+ }
+ default:
+ {
+ return;
+ }
+ }
+ }
+ }
+ catch (Throwable x)
+ {
+ failed(x);
+ }
+ }
+
+ private boolean processIterations() throws Exception
+ {
+ // Keeps iterating as long as succeeded() is called during process().
+ // If we are in INACTIVE state, either this is the first iteration or
+ // succeeded()/failed() were called already.
+ while (_state.compareAndSet(State.INACTIVE, State.ITERATING))
+ {
+ // Method process() can only be called by one thread at a time because
+ // it is guarded by the CaS above. However, the case blocks below may
+ // be executed concurrently in this case: T1 calls process() which
+ // executes the asynchronous sub task, which calls succeeded(), which
+ // moves the state into INACTIVE, then returns SCHEDULED; T2 calls
+ // iterate(), state is now INACTIVE and process() is called again and
+ // returns another action. Now we have 2 threads that may execute the
+ // action case blocks below concurrently; therefore each case block
+ // has to be prepared to fail the CaS it's doing.
+
+ Action action = process();
+ switch (action)
+ {
+ case IDLE:
+ {
+ // No more progress can be made.
+ if (_state.compareAndSet(State.ITERATING, State.INACTIVE))
+ return true;
+
+ // Was iterate() called again since we already decided to go INACTIVE ?
+ // If so, try another iteration as more work may have been added
+ // while the previous call to process() was returning.
+ if (_state.compareAndSet(State.ITERATE_AGAIN, State.INACTIVE))
+ continue;
+
+ // State may have changed concurrently, try again.
+ continue;
+ }
+ case SCHEDULED:
+ {
+ // The sub task is executing, and the callback for it may or
+ // may not have already been called yet, which we figure out below.
+ // Can double CaS here because state never changes directly ITERATING_AGAIN --> ITERATE.
+ if (_state.compareAndSet(State.ITERATING, State.ACTIVE) ||
+ _state.compareAndSet(State.ITERATE_AGAIN, State.ACTIVE))
+ // Not called back yet, so wait.
+ return true;
+ // Call back must have happened, so iterate.
+ continue;
+ }
+ case SUCCEEDED:
+ {
+ // The overall job has completed.
+ if (completeSuccess())
+ completed();
+ return true;
+ }
+ case FAILED:
+ {
+ completeFailure();
+ return true;
+ }
+ default:
+ {
+ throw new IllegalStateException(toString());
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Invoked when the sub task succeeds.
+ * Subclasses that override this method must always remember to call
+ * {@code super.succeeded()}.
+ */
+ @Override
+ public void succeeded()
+ {
+ while (true)
+ {
+ State current = _state.get();
+ switch (current)
+ {
+ case ITERATE_AGAIN:
+ case ITERATING:
+ {
+ if (_state.compareAndSet(current, State.INACTIVE))
+ return;
+ continue;
+ }
+ case ACTIVE:
+ {
+ // If we can move from ACTIVE to INACTIVE
+ // then we are responsible to call iterate().
+ if (_state.compareAndSet(current, State.INACTIVE))
+ iterate();
+ // If we can't CaS, then failed() must have been
+ // called, and we just return.
+ return;
+ }
+ case INACTIVE:
+ {
+ // Support the case where the callback is scheduled
+ // externally without a call to iterate().
+ iterate();
+ return;
+ }
+ default:
+ {
+ throw new IllegalStateException(toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoked when the sub task fails.
+ * Subclasses that override this method must always remember to call
+ * {@code super.failed(Throwable)}.
+ */
+ @Override
+ public void failed(Throwable x)
+ {
+ completeFailure();
+ }
+
+ private boolean completeSuccess()
+ {
+ while (true)
+ {
+ State current = _state.get();
+ if (current == State.FAILED)
+ {
+ // Success arrived too late, sorry.
+ return false;
+ }
+ else
+ {
+ if (_state.compareAndSet(current, State.SUCCEEDED))
+ return true;
+ }
+ }
+ }
+
+ private void completeFailure()
+ {
+ while (true)
+ {
+ State current = _state.get();
+ if (current == State.SUCCEEDED)
+ {
+ // Failed arrived too late, sorry.
+ return;
+ }
+ else
+ {
+ if (_state.compareAndSet(current, State.FAILED))
+ break;
+ }
+ }
+ }
+
+ /**
+ * @return whether this callback is idle and {@link #iterate()} needs to be called
+ */
+ public boolean isIdle()
+ {
+ return _state.get() == State.INACTIVE;
+ }
+
+ /**
+ * @return whether this callback has failed
+ */
+ public boolean isFailed()
+ {
+ return _state.get() == State.FAILED;
+ }
+
+ /**
+ * @return whether this callback has succeeded
+ */
+ public boolean isSucceeded()
+ {
+ return _state.get() == State.SUCCEEDED;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%s]", super.toString(), _state);
+ }
+
+ /**
+ * The internal states of this callback
+ */
+ private enum State
+ {
+ /**
+ * This callback is inactive, ready to iterate.
+ */
+ INACTIVE,
+ /**
+ * This callback is iterating and {@link #process()} has scheduled an
+ * asynchronous operation by returning {@link Action#SCHEDULED}, but
+ * the operation is still undergoing.
+ */
+ ACTIVE,
+ /**
+ * This callback is iterating and {@link #process()} has been called
+ * but not returned yet.
+ */
+ ITERATING,
+ /**
+ * While this callback was iterating, another request for iteration
+ * has been issued, so the iteration must continue even if a previous
+ * call to {@link #process()} returned {@link Action#IDLE}.
+ */
+ ITERATE_AGAIN,
+ /**
+ * The overall job has succeeded.
+ */
+ SUCCEEDED,
+ /**
+ * The overall job has failed.
+ */
+ FAILED
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+
+/* ------------------------------------------------------------ */
+/** Iterating Nested Callback.
+ * <p>This specialized callback is used when breaking up an
+ * asynchronous task into smaller asynchronous tasks. A typical pattern
+ * is that a successful callback is used to schedule the next sub task, but
+ * if that task completes quickly and uses the calling thread to callback
+ * the success notification, this can result in a growing stack depth.
+ * </p>
+ * <p>To avoid this issue, this callback uses an AtomicBoolean to note
+ * if the success callback has been called during the processing of a
+ * sub task, and if so then the processing iterates rather than recurses.
+ * </p>
+ * <p>This callback is passed to the asynchronous handling of each sub
+ * task and a call the {@link #succeeded()} on this call back represents
+ * completion of the subtask. Only once all the subtasks are completed is
+ * the {@link Callback#succeeded()} method called on the {@link Callback} instance
+ * passed the the {@link #IteratingNestedCallback(Callback)} constructor.</p>
+ *
+ */
+public abstract class IteratingNestedCallback extends IteratingCallback
+{
+ final Callback _callback;
+
+ public IteratingNestedCallback(Callback callback)
+ {
+ _callback=callback;
+ }
+
+ @Override
+ protected void completed()
+ {
+ _callback.succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ super.failed(x);
+ _callback.failed(x);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x",getClass().getSimpleName(),hashCode());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+public class Jetty
+{
+ public static final String VERSION;
+
+ static
+ {
+ Package pkg = Jetty.class.getPackage();
+ if (pkg != null &&
+ "Eclipse.org - Jetty".equals(pkg.getImplementationVendor()) &&
+ pkg.getImplementationVersion() != null)
+ VERSION = pkg.getImplementationVersion();
+ else
+ VERSION = System.getProperty("jetty.version", "9.2.z-SNAPSHOT");
+ }
+
+ private Jetty()
+ {
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/* ------------------------------------------------------------ */
+/** Lazy List creation.
+ * A List helper class that attempts to avoid unnecessary List
+ * creation. If a method needs to create a List to return, but it is
+ * expected that this will either be empty or frequently contain a
+ * single item, then using LazyList will avoid additional object
+ * creations by using {@link Collections#EMPTY_LIST} or
+ * {@link Collections#singletonList(Object)} where possible.
+ * <p>
+ * LazyList works by passing an opaque representation of the list in
+ * and out of all the LazyList methods. This opaque object is either
+ * null for an empty list, an Object for a list with a single entry
+ * or an {@link ArrayList} for a list of items.
+ *
+ * <p><h4>Usage</h4>
+ * <pre>
+ * Object lazylist =null;
+ * while(loopCondition)
+ * {
+ * Object item = getItem();
+ * if (item.isToBeAdded())
+ * lazylist = LazyList.add(lazylist,item);
+ * }
+ * return LazyList.getList(lazylist);
+ * </pre>
+ *
+ * An ArrayList of default size is used as the initial LazyList.
+ *
+ * @see java.util.List
+ */
+@SuppressWarnings("serial")
+public class LazyList
+ implements Cloneable, Serializable
+{
+ private static final String[] __EMTPY_STRING_ARRAY = new String[0];
+
+ /* ------------------------------------------------------------ */
+ private LazyList()
+ {}
+
+ /* ------------------------------------------------------------ */
+ /** Add an item to a LazyList
+ * @param list The list to add to or null if none yet created.
+ * @param item The item to add.
+ * @return The lazylist created or added to.
+ */
+ @SuppressWarnings("unchecked")
+ public static Object add(Object list, Object item)
+ {
+ if (list==null)
+ {
+ if (item instanceof List || item==null)
+ {
+ List<Object> l = new ArrayList<Object>();
+ l.add(item);
+ return l;
+ }
+
+ return item;
+ }
+
+ if (list instanceof List)
+ {
+ ((List<Object>)list).add(item);
+ return list;
+ }
+
+ List<Object> l=new ArrayList<Object>();
+ l.add(list);
+ l.add(item);
+ return l;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add an item to a LazyList
+ * @param list The list to add to or null if none yet created.
+ * @param index The index to add the item at.
+ * @param item The item to add.
+ * @return The lazylist created or added to.
+ */
+ @SuppressWarnings("unchecked")
+ public static Object add(Object list, int index, Object item)
+ {
+ if (list==null)
+ {
+ if (index>0 || item instanceof List || item==null)
+ {
+ List<Object> l = new ArrayList<Object>();
+ l.add(index,item);
+ return l;
+ }
+ return item;
+ }
+
+ if (list instanceof List)
+ {
+ ((List<Object>)list).add(index,item);
+ return list;
+ }
+
+ List<Object> l=new ArrayList<Object>();
+ l.add(list);
+ l.add(index,item);
+ return l;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add the contents of a Collection to a LazyList
+ * @param list The list to add to or null if none yet created.
+ * @param collection The Collection whose contents should be added.
+ * @return The lazylist created or added to.
+ */
+ public static Object addCollection(Object list, Collection<?> collection)
+ {
+ Iterator<?> i=collection.iterator();
+ while(i.hasNext())
+ list=LazyList.add(list,i.next());
+ return list;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add the contents of an array to a LazyList
+ * @param list The list to add to or null if none yet created.
+ * @param array The array whose contents should be added.
+ * @return The lazylist created or added to.
+ */
+ public static Object addArray(Object list, Object[] array)
+ {
+ for(int i=0;array!=null && i<array.length;i++)
+ list=LazyList.add(list,array[i]);
+ return list;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Ensure the capacity of the underlying list.
+ *
+ */
+ public static Object ensureSize(Object list, int initialSize)
+ {
+ if (list==null)
+ return new ArrayList<Object>(initialSize);
+ if (list instanceof ArrayList)
+ {
+ ArrayList<?> ol=(ArrayList<?>)list;
+ if (ol.size()>initialSize)
+ return ol;
+ ArrayList<Object> nl = new ArrayList<Object>(initialSize);
+ nl.addAll(ol);
+ return nl;
+ }
+ List<Object> l= new ArrayList<Object>(initialSize);
+ l.add(list);
+ return l;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static Object remove(Object list, Object o)
+ {
+ if (list==null)
+ return null;
+
+ if (list instanceof List)
+ {
+ List<?> l = (List<?>)list;
+ l.remove(o);
+ if (l.size()==0)
+ return null;
+ return list;
+ }
+
+ if (list.equals(o))
+ return null;
+ return list;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static Object remove(Object list, int i)
+ {
+ if (list==null)
+ return null;
+
+ if (list instanceof List)
+ {
+ List<?> l = (List<?>)list;
+ l.remove(i);
+ if (l.size()==0)
+ return null;
+ return list;
+ }
+
+ if (i==0)
+ return null;
+ return list;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /** Get the real List from a LazyList.
+ *
+ * @param list A LazyList returned from LazyList.add(Object)
+ * @return The List of added items, which may be an EMPTY_LIST
+ * or a SingletonList.
+ */
+ public static<E> List<E> getList(Object list)
+ {
+ return getList(list,false);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Get the real List from a LazyList.
+ *
+ * @param list A LazyList returned from LazyList.add(Object) or null
+ * @param nullForEmpty If true, null is returned instead of an
+ * empty list.
+ * @return The List of added items, which may be null, an EMPTY_LIST
+ * or a SingletonList.
+ */
+ @SuppressWarnings("unchecked")
+ public static<E> List<E> getList(Object list, boolean nullForEmpty)
+ {
+ if (list==null)
+ {
+ if (nullForEmpty)
+ return null;
+ return Collections.emptyList();
+ }
+ if (list instanceof List)
+ return (List<E>)list;
+
+ return (List<E>)Collections.singletonList(list);
+ }
+
+ /**
+ * Simple utility method to test if List has at least 1 entry.
+ *
+ * @param list
+ * a LazyList, {@link List} or {@link Object}
+ * @return true if not-null and is not empty
+ */
+ public static boolean hasEntry(Object list)
+ {
+ if (list == null)
+ return false;
+ if (list instanceof List)
+ return !((List<?>)list).isEmpty();
+ return true;
+ }
+
+ /**
+ * Simple utility method to test if List is empty
+ *
+ * @param list
+ * a LazyList, {@link List} or {@link Object}
+ * @return true if null or is empty
+ */
+ public static boolean isEmpty(Object list)
+ {
+ if (list == null)
+ return true;
+ if (list instanceof List)
+ return ((List<?>)list).isEmpty();
+ return false;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static String[] toStringArray(Object list)
+ {
+ if (list==null)
+ return __EMTPY_STRING_ARRAY;
+
+ if (list instanceof List)
+ {
+ List<?> l = (List<?>)list;
+ String[] a = new String[l.size()];
+ for (int i=l.size();i-->0;)
+ {
+ Object o=l.get(i);
+ if (o!=null)
+ a[i]=o.toString();
+ }
+ return a;
+ }
+
+ return new String[] {list.toString()};
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert a lazylist to an array
+ * @param list The list to convert
+ * @param clazz The class of the array, which may be a primitive type
+ * @return array of the lazylist entries passed in
+ */
+ public static Object toArray(Object list,Class<?> clazz)
+ {
+ if (list==null)
+ return Array.newInstance(clazz,0);
+
+ if (list instanceof List)
+ {
+ List<?> l = (List<?>)list;
+ if (clazz.isPrimitive())
+ {
+ Object a = Array.newInstance(clazz,l.size());
+ for (int i=0;i<l.size();i++)
+ Array.set(a,i,l.get(i));
+ return a;
+ }
+ return l.toArray((Object[])Array.newInstance(clazz,l.size()));
+
+ }
+
+ Object a = Array.newInstance(clazz,1);
+ Array.set(a,0,list);
+ return a;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** The size of a lazy List
+ * @param list A LazyList returned from LazyList.add(Object) or null
+ * @return the size of the list.
+ */
+ public static int size(Object list)
+ {
+ if (list==null)
+ return 0;
+ if (list instanceof List)
+ return ((List<?>)list).size();
+ return 1;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get item from the list
+ * @param list A LazyList returned from LazyList.add(Object) or null
+ * @param i int index
+ * @return the item from the list.
+ */
+ @SuppressWarnings("unchecked")
+ public static <E> E get(Object list, int i)
+ {
+ if (list==null)
+ throw new IndexOutOfBoundsException();
+
+ if (list instanceof List)
+ return (E)((List<?>)list).get(i);
+
+ if (i==0)
+ return (E)list;
+
+ throw new IndexOutOfBoundsException();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static boolean contains(Object list,Object item)
+ {
+ if (list==null)
+ return false;
+
+ if (list instanceof List)
+ return ((List<?>)list).contains(item);
+
+ return list.equals(item);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static Object clone(Object list)
+ {
+ if (list==null)
+ return null;
+ if (list instanceof List)
+ return new ArrayList<Object>((List<?>)list);
+ return list;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String toString(Object list)
+ {
+ if (list==null)
+ return "[]";
+ if (list instanceof List)
+ return list.toString();
+ return "["+list+"]";
+ }
+
+ /* ------------------------------------------------------------ */
+ @SuppressWarnings("unchecked")
+ public static<E> Iterator<E> iterator(Object list)
+ {
+ if (list==null)
+ {
+ List<E> empty=Collections.emptyList();
+ return empty.iterator();
+ }
+ if (list instanceof List)
+ {
+ return ((List<E>)list).iterator();
+ }
+ List<E> l=getList(list);
+ return l.iterator();
+ }
+
+ /* ------------------------------------------------------------ */
+ @SuppressWarnings("unchecked")
+ public static<E> ListIterator<E> listIterator(Object list)
+ {
+ if (list==null)
+ {
+ List<E> empty=Collections.emptyList();
+ return empty.listIterator();
+ }
+ if (list instanceof List)
+ return ((List<E>)list).listIterator();
+
+ List<E> l=getList(list);
+ return l.listIterator();
+ }
+
+}
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A facility to detect improper usage of resource pools.
+ * <p>
+ * Resource pools usually have a method to acquire a pooled resource
+ * and a method to released it back to the pool.
+ * <p>
+ * To detect if client code acquires a resource but never releases it,
+ * the resource pool can be modified to use a {@link LeakDetector}.
+ * The modified resource pool should call {@link #acquired(Object)} every time
+ * the method to acquire a resource is called, and {@link #released(Object)}
+ * every time the method to release the resource is called.
+ * {@link LeakDetector} keeps track of these resources and invokes method
+ * {@link #leaked(org.eclipse.jetty.util.LeakDetector.LeakInfo)} when it detects that a resource
+ * has been leaked (that is, acquired but never released).
+ * <p>
+ * To detect whether client code releases a resource without having
+ * acquired it, the resource pool can be modified to check the return value
+ * of {@link #released(Object)}: if false, it means that the resource was
+ * not acquired.
+ * <p>
+ * IMPLEMENTATION NOTES
+ * <p>
+ * This class relies on {@link System#identityHashCode(Object)} to create
+ * a unique id for each resource passed to {@link #acquired(Object)} and
+ * {@link #released(Object)}. {@link System#identityHashCode(Object)} does
+ * not guarantee that it will not generate the same number for different
+ * objects, but in practice the chance of collision is rare.
+ * <p>
+ * {@link LeakDetector} uses {@link PhantomReference}s to detect leaks.
+ * {@link PhantomReference}s are enqueued in their {@link ReferenceQueue}
+ * <em>after</em> they have been garbage collected (differently from
+ * {@link WeakReference}s that are enqueued <em>before</em>).
+ * Since the resource is now garbage collected, {@link LeakDetector} checks
+ * whether it has been released and if not, it reports a leak.
+ * Using {@link PhantomReference}s is better than overriding {@link #finalize()}
+ * and works also in those cases where {@link #finalize()} is not
+ * overridable.
+ *
+ * @param <T> the resource type.
+ */
+public class LeakDetector<T> extends AbstractLifeCycle implements Runnable
+{
+ private static final Logger LOG = Log.getLogger(LeakDetector.class);
+
+ private final ReferenceQueue<T> queue = new ReferenceQueue<>();
+ private final ConcurrentMap<String, LeakInfo> resources = new ConcurrentHashMap<>();
+ private Thread thread;
+
+ /**
+ * Tracks the resource as been acquired.
+ *
+ * @param resource the resource that has been acquired
+ * @return whether the resource has been tracked
+ * @see #released(Object)
+ */
+ public boolean acquired(T resource)
+ {
+ String id = id(resource);
+ return resources.putIfAbsent(id, new LeakInfo(resource, id)) == null;
+ }
+
+ /**
+ * Tracks the resource as been released.
+ *
+ * @param resource the resource that has been released
+ * @return whether the resource has been acquired
+ * @see #acquired(Object)
+ */
+ public boolean released(T resource)
+ {
+ String id = id(resource);
+ return resources.remove(id) != null;
+ }
+
+ /**
+ * Generates a unique ID for the given resource.
+ *
+ * @param resource the resource to generate the unique ID for
+ * @return the unique ID of the given resource
+ */
+ protected String id(T resource)
+ {
+ return String.valueOf(System.identityHashCode(resource));
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ thread = new Thread(this, getClass().getSimpleName());
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ thread.interrupt();
+ super.doStop();
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ while (isRunning())
+ {
+ @SuppressWarnings("unchecked")
+ LeakInfo leakInfo = (LeakInfo)queue.remove();
+ LOG.debug("Resource GC'ed: {}", leakInfo);
+ if (resources.remove(leakInfo.id) != null)
+ leaked(leakInfo);
+ }
+ }
+ catch (InterruptedException x)
+ {
+ // Exit
+ }
+ }
+
+ /**
+ * Callback method invoked by {@link LeakDetector} when it detects that a resource has been leaked.
+ *
+ * @param leakInfo the information about the leak
+ */
+ protected void leaked(LeakInfo leakInfo)
+ {
+ LOG.warn("Resource leaked: " + leakInfo.description, leakInfo.stackFrames);
+ }
+
+ /**
+ * Information about the leak of a resource.
+ */
+ public class LeakInfo extends PhantomReference<T>
+ {
+ private final String id;
+ private final String description;
+ private final Throwable stackFrames;
+
+ private LeakInfo(T referent, String id)
+ {
+ super(referent, queue);
+ this.id = id;
+ this.description = referent.toString();
+ this.stackFrames = new Throwable();
+ }
+
+ /**
+ * @return the resource description as provided by the resource's {@link Object#toString()} method.
+ */
+ public String getResourceDescription()
+ {
+ return description;
+ }
+
+ /**
+ * @return a Throwable instance that contains the stack frames at the time of resource acquisition.
+ */
+ public Throwable getStackFrames()
+ {
+ return stackFrames;
+ }
+
+ @Override
+ public String toString()
+ {
+ return description;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** ClassLoader Helper.
+ * This helper class allows classes to be loaded either from the
+ * Thread's ContextClassLoader, the classloader of the derived class
+ * or the system ClassLoader.
+ *
+ * <B>Usage:</B><PRE>
+ * public class MyClass {
+ * void myMethod() {
+ * ...
+ * Class c=Loader.loadClass(this.getClass(),classname);
+ * ...
+ * }
+ * </PRE>
+ *
+ */
+public class Loader
+{
+ /* ------------------------------------------------------------ */
+ public static URL getResource(Class<?> loadClass,String name)
+ {
+ URL url =null;
+ ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+ if (context_loader!=null)
+ url=context_loader.getResource(name);
+
+ if (url==null && loadClass!=null)
+ {
+ ClassLoader load_loader=loadClass.getClassLoader();
+ if (load_loader!=null && load_loader!=context_loader)
+ url=load_loader.getResource(name);
+ }
+
+ if (url==null)
+ url=ClassLoader.getSystemResource(name);
+
+ return url;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Load a class.
+ *
+ * @param loadClass
+ * @param name
+ * @return Class
+ * @throws ClassNotFoundException
+ */
+ @SuppressWarnings("rawtypes")
+ public static Class loadClass(Class loadClass,String name)
+ throws ClassNotFoundException
+ {
+ ClassNotFoundException ex=null;
+ Class<?> c =null;
+ ClassLoader context_loader=Thread.currentThread().getContextClassLoader();
+ if (context_loader!=null )
+ {
+ try { c=context_loader.loadClass(name); }
+ catch (ClassNotFoundException e) {ex=e;}
+ }
+
+ if (c==null && loadClass!=null)
+ {
+ ClassLoader load_loader=loadClass.getClassLoader();
+ if (load_loader!=null && load_loader!=context_loader)
+ {
+ try { c=load_loader.loadClass(name); }
+ catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+ }
+ }
+
+ if (c==null)
+ {
+ try { c=Class.forName(name); }
+ catch (ClassNotFoundException e)
+ {
+ if(ex!=null)
+ throw ex;
+ throw e;
+ }
+ }
+
+ return c;
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ public static ResourceBundle getResourceBundle(Class<?> loadClass,String name,boolean checkParents, Locale locale)
+ throws MissingResourceException
+ {
+ MissingResourceException ex=null;
+ ResourceBundle bundle =null;
+ ClassLoader loader=Thread.currentThread().getContextClassLoader();
+ while (bundle==null && loader!=null )
+ {
+ try { bundle=ResourceBundle.getBundle(name, locale, loader); }
+ catch (MissingResourceException e) {if(ex==null)ex=e;}
+ loader=(bundle==null&&checkParents)?loader.getParent():null;
+ }
+
+ loader=loadClass==null?null:loadClass.getClassLoader();
+ while (bundle==null && loader!=null )
+ {
+ try { bundle=ResourceBundle.getBundle(name, locale, loader); }
+ catch (MissingResourceException e) {if(ex==null)ex=e;}
+ loader=(bundle==null&&checkParents)?loader.getParent():null;
+ }
+
+ if (bundle==null)
+ {
+ try { bundle=ResourceBundle.getBundle(name, locale); }
+ catch (MissingResourceException e) {if(ex==null)ex=e;}
+ }
+
+ if (bundle!=null)
+ return bundle;
+ throw ex;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Generate the classpath (as a string) of all classloaders
+ * above the given classloader.
+ *
+ * This is primarily used for jasper.
+ * @return the system class path
+ */
+ public static String getClassPath(ClassLoader loader) throws Exception
+ {
+ StringBuilder classpath=new StringBuilder();
+ while (loader != null && (loader instanceof URLClassLoader))
+ {
+ URL[] urls = ((URLClassLoader)loader).getURLs();
+ if (urls != null)
+ {
+ for (int i=0;i<urls.length;i++)
+ {
+ Resource resource = Resource.newResource(urls[i]);
+ File file=resource.getFile();
+ if (file!=null && file.exists())
+ {
+ if (classpath.length()>0)
+ classpath.append(File.pathSeparatorChar);
+ classpath.append(file.getAbsolutePath());
+ }
+ }
+ }
+ loader = loader.getParent();
+ }
+ return classpath.toString();
+ }
+}
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * {@link MemoryUtils} provides an abstraction over memory properties and operations.
+ * <p />
+ */
+public class MemoryUtils
+{
+ private static final int cacheLineBytes;
+ static
+ {
+ final int defaultValue = 64;
+ int value = defaultValue;
+ try
+ {
+ value = Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction<String>()
+ {
+ @Override
+ public String run()
+ {
+ return System.getProperty("org.eclipse.jetty.util.cacheLineBytes", String.valueOf(defaultValue));
+ }
+ }));
+ }
+ catch (Exception ignored)
+ {
+ }
+ cacheLineBytes = value;
+ }
+
+ private MemoryUtils()
+ {
+ }
+
+ public static int getCacheLineBytes()
+ {
+ return cacheLineBytes;
+ }
+
+ public static int getIntegersPerCacheLine()
+ {
+ return getCacheLineBytes() >> 2;
+ }
+
+ public static int getLongsPerCacheLine()
+ {
+ return getCacheLineBytes() >> 3;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Wraps multiple exceptions.
+ *
+ * Allows multiple exceptions to be thrown as a single exception.
+ */
+@SuppressWarnings("serial")
+public class MultiException extends Exception
+{
+ private List<Throwable> nested;
+
+ /* ------------------------------------------------------------ */
+ public MultiException()
+ {
+ super("Multiple exceptions");
+ }
+
+ /* ------------------------------------------------------------ */
+ public void add(Throwable e)
+ {
+ if(nested == null)
+ {
+ nested = new ArrayList<>();
+ }
+
+ if (e instanceof MultiException)
+ {
+ MultiException me = (MultiException)e;
+ nested.addAll(me.nested);
+ }
+ else
+ nested.add(e);
+ }
+
+ /* ------------------------------------------------------------ */
+ public int size()
+ {
+ return (nested ==null)?0:nested.size();
+ }
+
+ /* ------------------------------------------------------------ */
+ public List<Throwable> getThrowables()
+ {
+ if(nested == null) {
+ return Collections.emptyList();
+ }
+ return nested;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Throwable getThrowable(int i)
+ {
+ return nested.get(i);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Throw a multiexception.
+ * If this multi exception is empty then no action is taken. If it
+ * contains a single exception that is thrown, otherwise the this
+ * multi exception is thrown.
+ * @exception Exception
+ */
+ public void ifExceptionThrow()
+ throws Exception
+ {
+ if(nested == null)
+ return;
+
+ switch (nested.size())
+ {
+ case 0:
+ break;
+ case 1:
+ Throwable th=nested.get(0);
+ if (th instanceof Error)
+ throw (Error)th;
+ if (th instanceof Exception)
+ throw (Exception)th;
+ default:
+ throw this;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Throw a Runtime exception.
+ * If this multi exception is empty then no action is taken. If it
+ * contains a single error or runtime exception that is thrown, otherwise the this
+ * multi exception is thrown, wrapped in a runtime exception.
+ * @exception Error If this exception contains exactly 1 {@link Error}
+ * @exception RuntimeException If this exception contains 1 {@link Throwable} but it is not an error,
+ * or it contains more than 1 {@link Throwable} of any type.
+ */
+ public void ifExceptionThrowRuntime()
+ throws Error
+ {
+ if(nested == null)
+ return;
+
+ switch (nested.size())
+ {
+ case 0:
+ break;
+ case 1:
+ Throwable th=nested.get(0);
+ if (th instanceof Error)
+ throw (Error)th;
+ else if (th instanceof RuntimeException)
+ throw (RuntimeException)th;
+ else
+ throw new RuntimeException(th);
+ default:
+ throw new RuntimeException(this);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Throw a multiexception.
+ * If this multi exception is empty then no action is taken. If it
+ * contains a any exceptions then this
+ * multi exception is thrown.
+ */
+ public void ifExceptionThrowMulti()
+ throws MultiException
+ {
+ if(nested == null)
+ return;
+
+ if (nested.size()>0)
+ throw this;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append(MultiException.class.getSimpleName());
+ if((nested == null) || (nested.size()<=0)) {
+ str.append("[]");
+ } else {
+ str.append(nested);
+ }
+ return str.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void printStackTrace()
+ {
+ super.printStackTrace();
+ if(nested != null) {
+ for(Throwable t: nested) {
+ t.printStackTrace();
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * @see java.lang.Throwable#printStackTrace(java.io.PrintStream)
+ */
+ @Override
+ public void printStackTrace(PrintStream out)
+ {
+ super.printStackTrace(out);
+ if(nested != null) {
+ for(Throwable t: nested) {
+ t.printStackTrace(out);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * @see java.lang.Throwable#printStackTrace(java.io.PrintWriter)
+ */
+ @Override
+ public void printStackTrace(PrintWriter out)
+ {
+ super.printStackTrace(out);
+ if(nested != null) {
+ for(Throwable t: nested) {
+ t.printStackTrace(out);
+ }
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A multi valued Map.
+ */
+@SuppressWarnings("serial")
+public class MultiMap<V> extends HashMap<String,List<V>>
+{
+ public MultiMap()
+ {
+ super();
+ }
+
+ public MultiMap(Map<String,List<V>> map)
+ {
+ super(map);
+ }
+
+ public MultiMap(MultiMap<V> map)
+ {
+ super(map);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Get multiple values.
+ * Single valued entries are converted to singleton lists.
+ * @param name The entry key.
+ * @return Unmodifieable List of values.
+ */
+ public List<V> getValues(String name)
+ {
+ List<V> vals = super.get(name);
+ if((vals == null) || vals.isEmpty()) {
+ return null;
+ }
+ return vals;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get a value from a multiple value.
+ * If the value is not a multivalue, then index 0 retrieves the
+ * value or null.
+ * @param name The entry key.
+ * @param i Index of element to get.
+ * @return Unmodifieable List of values.
+ */
+ public V getValue(String name,int i)
+ {
+ List<V> vals = getValues(name);
+ if(vals == null) {
+ return null;
+ }
+ if (i==0 && vals.isEmpty()) {
+ return null;
+ }
+ return vals.get(i);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Get value as String.
+ * Single valued items are converted to a String with the toString()
+ * Object method. Multi valued entries are converted to a comma separated
+ * List. No quoting of commas within values is performed.
+ * @param name The entry key.
+ * @return String value.
+ */
+ public String getString(String name)
+ {
+ List<V> vals =get(name);
+ if ((vals == null) || (vals.isEmpty()))
+ {
+ return null;
+ }
+
+ if (vals.size() == 1)
+ {
+ // simple form.
+ return vals.get(0).toString();
+ }
+
+ // delimited form
+ StringBuilder values=new StringBuilder(128);
+ for (V e : vals)
+ {
+ if (e != null)
+ {
+ if (values.length() > 0)
+ values.append(',');
+ values.append(e.toString());
+ }
+ }
+ return values.toString();
+ }
+
+ /**
+ * Put multi valued entry.
+ * @param name The entry key.
+ * @param value The simple value
+ * @return The previous value or null.
+ */
+ public List<V> put(String name, V value)
+ {
+ if(value == null) {
+ return super.put(name, null);
+ }
+ List<V> vals = new ArrayList<>();
+ vals.add(value);
+ return put(name,vals);
+ }
+
+ /**
+ * Shorthand version of putAll
+ * @param input the input map
+ */
+ public void putAllValues(Map<String, V> input)
+ {
+ for(Map.Entry<String,V> entry: input.entrySet())
+ {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Put multi valued entry.
+ * @param name The entry key.
+ * @param values The List of multiple values.
+ * @return The previous value or null.
+ */
+ public List<V> putValues(String name, List<V> values)
+ {
+ return super.put(name,values);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Put multi valued entry.
+ * @param name The entry key.
+ * @param values The array of multiple values.
+ * @return The previous value or null.
+ */
+ @SafeVarargs
+ public final List<V> putValues(String name, V... values)
+ {
+ List<V> list = new ArrayList<>();
+ list.addAll(Arrays.asList(values));
+ return super.put(name,list);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Add value to multi valued entry.
+ * If the entry is single valued, it is converted to the first
+ * value of a multi valued entry.
+ * @param name The entry key.
+ * @param value The entry value.
+ */
+ public void add(String name, V value)
+ {
+ List<V> lo = get(name);
+ if(lo == null) {
+ lo = new ArrayList<>();
+ }
+ lo.add(value);
+ super.put(name,lo);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add values to multi valued entry.
+ * If the entry is single valued, it is converted to the first
+ * value of a multi valued entry.
+ * @param name The entry key.
+ * @param values The List of multiple values.
+ */
+ public void addValues(String name, List<V> values)
+ {
+ List<V> lo = get(name);
+ if(lo == null) {
+ lo = new ArrayList<>();
+ }
+ lo.addAll(values);
+ put(name,lo);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Add values to multi valued entry.
+ * If the entry is single valued, it is converted to the first
+ * value of a multi valued entry.
+ * @param name The entry key.
+ * @param values The String array of multiple values.
+ */
+ public void addValues(String name, V[] values)
+ {
+ List<V> lo = get(name);
+ if(lo == null) {
+ lo = new ArrayList<>();
+ }
+ lo.addAll(Arrays.asList(values));
+ put(name,lo);
+ }
+
+ /**
+ * Merge values.
+ *
+ * @param map
+ * the map to overlay on top of this one, merging together values if needed.
+ * @return true if an existing key was merged with potentially new values, false if either no change was made, or there were only new keys.
+ */
+ public boolean addAllValues(MultiMap<V> map)
+ {
+ boolean merged = false;
+
+ if ((map == null) || (map.isEmpty()))
+ {
+ // done
+ return merged;
+ }
+
+ for (Map.Entry<String, List<V>> entry : map.entrySet())
+ {
+ String name = entry.getKey();
+ List<V> values = entry.getValue();
+
+ if (this.containsKey(name))
+ {
+ merged = true;
+ }
+
+ this.addValues(name,values);
+ }
+
+ return merged;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Remove value.
+ * @param name The entry key.
+ * @param value The entry value.
+ * @return true if it was removed.
+ */
+ public boolean removeValue(String name,V value)
+ {
+ List<V> lo = get(name);
+ if((lo == null)||(lo.isEmpty())) {
+ return false;
+ }
+ boolean ret = lo.remove(value);
+ if(lo.isEmpty()) {
+ remove(name);
+ } else {
+ put(name,lo);
+ }
+ return ret;
+ }
+
+ /**
+ * Test for a specific single value in the map.
+ * <p>
+ * NOTE: This is a SLOW operation, and is actively discouraged.
+ * @param value
+ * @return true if contains simple value
+ */
+ public boolean containsSimpleValue(V value)
+ {
+ for (List<V> vals : values())
+ {
+ if ((vals.size() == 1) && vals.contains(value))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ Iterator<Entry<String, List<V>>> iter = entrySet().iterator();
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ boolean delim = false;
+ while (iter.hasNext())
+ {
+ Entry<String, List<V>> e = iter.next();
+ if (delim)
+ {
+ sb.append(", ");
+ }
+ String key = e.getKey();
+ List<V> vals = e.getValue();
+ sb.append(key);
+ sb.append('=');
+ if (vals.size() == 1)
+ {
+ sb.append(vals.get(0));
+ }
+ else
+ {
+ sb.append(vals);
+ }
+ delim = true;
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Map of String arrays
+ */
+ public Map<String,String[]> toStringArrayMap()
+ {
+ HashMap<String,String[]> map = new HashMap<String,String[]>(size()*3/2)
+ {
+ @Override
+ public String toString()
+ {
+ StringBuilder b=new StringBuilder();
+ b.append('{');
+ for (String k:super.keySet())
+ {
+ if(b.length()>1)
+ b.append(',');
+ b.append(k);
+ b.append('=');
+ b.append(Arrays.asList(super.get(k)));
+ }
+
+ b.append('}');
+ return b.toString();
+ }
+ };
+
+ for(Map.Entry<String,List<V>> entry: entrySet())
+ {
+ String[] a = null;
+ if (entry.getValue() != null)
+ {
+ a = new String[entry.getValue().size()];
+ a = entry.getValue().toArray(a);
+ }
+ map.put(entry.getKey(),a);
+ }
+ return map;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletException;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/**
+ * MultiPartInputStream
+ *
+ * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
+ */
+public class MultiPartInputStreamParser
+{
+ private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class);
+ public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
+ protected InputStream _in;
+ protected MultipartConfigElement _config;
+ protected String _contentType;
+ protected MultiMap _parts;
+ protected File _tmpDir;
+ protected File _contextTmpDir;
+ protected boolean _deleteOnExit;
+
+
+
+ public class MultiPart implements Part
+ {
+ protected String _name;
+ protected String _filename;
+ protected File _file;
+ protected OutputStream _out;
+ protected ByteArrayOutputStream2 _bout;
+ protected String _contentType;
+ protected MultiMap _headers;
+ protected long _size = 0;
+ protected boolean _temporary = true;
+
+ public MultiPart (String name, String filename)
+ throws IOException
+ {
+ _name = name;
+ _filename = filename;
+ }
+
+ protected void setContentType (String contentType)
+ {
+ _contentType = contentType;
+ }
+
+
+ protected void open()
+ throws IOException
+ {
+ //We will either be writing to a file, if it has a filename on the content-disposition
+ //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we
+ //will need to change to write to a file.
+ if (_filename != null && _filename.trim().length() > 0)
+ {
+ createFile();
+ }
+ else
+ {
+ //Write to a buffer in memory until we discover we've exceed the
+ //MultipartConfig fileSizeThreshold
+ _out = _bout= new ByteArrayOutputStream2();
+ }
+ }
+
+ protected void close()
+ throws IOException
+ {
+ _out.close();
+ }
+
+
+ protected void write (int b)
+ throws IOException
+ {
+ if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getMaxFileSize())
+ throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+
+ if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
+ createFile();
+ _out.write(b);
+ _size ++;
+ }
+
+ protected void write (byte[] bytes, int offset, int length)
+ throws IOException
+ {
+ if (MultiPartInputStreamParser.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStreamParser.this._config.getMaxFileSize())
+ throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+
+ if (MultiPartInputStreamParser.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStreamParser.this._config.getFileSizeThreshold() && _file==null)
+ createFile();
+
+ _out.write(bytes, offset, length);
+ _size += length;
+ }
+
+ protected void createFile ()
+ throws IOException
+ {
+ _file = File.createTempFile("MultiPart", "", MultiPartInputStreamParser.this._tmpDir);
+ if (_deleteOnExit)
+ _file.deleteOnExit();
+ FileOutputStream fos = new FileOutputStream(_file);
+ BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+ if (_size > 0 && _out != null)
+ {
+ //already written some bytes, so need to copy them into the file
+ _out.flush();
+ _bout.writeTo(bos);
+ _out.close();
+ _bout = null;
+ }
+ _out = bos;
+ }
+
+
+
+ protected void setHeaders(MultiMap headers)
+ {
+ _headers = headers;
+ }
+
+ /**
+ * @see javax.servlet.http.Part#getContentType()
+ */
+ public String getContentType()
+ {
+ return _contentType;
+ }
+
+ /**
+ * @see javax.servlet.http.Part#getHeader(java.lang.String)
+ */
+ public String getHeader(String name)
+ {
+ if (name == null)
+ return null;
+ return (String)_headers.getValue(name.toLowerCase(Locale.ENGLISH), 0);
+ }
+
+ /**
+ * @see javax.servlet.http.Part#getHeaderNames()
+ */
+ public Collection<String> getHeaderNames()
+ {
+ return _headers.keySet();
+ }
+
+ /**
+ * @see javax.servlet.http.Part#getHeaders(java.lang.String)
+ */
+ public Collection<String> getHeaders(String name)
+ {
+ return _headers.getValues(name);
+ }
+
+ /**
+ * @see javax.servlet.http.Part#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException
+ {
+ if (_file != null)
+ {
+ //written to a file, whether temporary or not
+ return new BufferedInputStream (new FileInputStream(_file));
+ }
+ else
+ {
+ //part content is in memory
+ return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
+ }
+ }
+
+
+ /**
+ * @see javax.servlet.http.Part#getSubmittedFileName()
+ */
+ @Override
+ public String getSubmittedFileName()
+ {
+ return getContentDispositionFilename();
+ }
+
+ public byte[] getBytes()
+ {
+ if (_bout!=null)
+ return _bout.toByteArray();
+ return null;
+ }
+
+ /**
+ * @see javax.servlet.http.Part#getName()
+ */
+ public String getName()
+ {
+ return _name;
+ }
+
+ /**
+ * @see javax.servlet.http.Part#getSize()
+ */
+ public long getSize()
+ {
+ return _size;
+ }
+
+ /**
+ * @see javax.servlet.http.Part#write(java.lang.String)
+ */
+ public void write(String fileName) throws IOException
+ {
+ if (_file == null)
+ {
+ _temporary = false;
+
+ //part data is only in the ByteArrayOutputStream and never been written to disk
+ _file = new File (_tmpDir, fileName);
+
+ BufferedOutputStream bos = null;
+ try
+ {
+ bos = new BufferedOutputStream(new FileOutputStream(_file));
+ _bout.writeTo(bos);
+ bos.flush();
+ }
+ finally
+ {
+ if (bos != null)
+ bos.close();
+ _bout = null;
+ }
+ }
+ else
+ {
+ //the part data is already written to a temporary file, just rename it
+ _temporary = false;
+
+ File f = new File(_tmpDir, fileName);
+ if (_file.renameTo(f))
+ _file = f;
+ }
+ }
+
+ /**
+ * Remove the file, whether or not Part.write() was called on it
+ * (ie no longer temporary)
+ * @see javax.servlet.http.Part#delete()
+ */
+ public void delete() throws IOException
+ {
+ if (_file != null && _file.exists())
+ _file.delete();
+ }
+
+ /**
+ * Only remove tmp files.
+ *
+ * @throws IOException
+ */
+ public void cleanUp() throws IOException
+ {
+ if (_temporary && _file != null && _file.exists())
+ _file.delete();
+ }
+
+
+ /**
+ * Get the file, if any, the data has been written to.
+ */
+ public File getFile ()
+ {
+ return _file;
+ }
+
+
+ /**
+ * Get the filename from the content-disposition.
+ * @return null or the filename
+ */
+ public String getContentDispositionFilename ()
+ {
+ return _filename;
+ }
+ }
+
+
+
+
+ /**
+ * @param in Request input stream
+ * @param contentType Content-Type header
+ * @param config MultipartConfigElement
+ * @param contextTmpDir javax.servlet.context.tempdir
+ */
+ public MultiPartInputStreamParser (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
+ {
+ _in = new ReadLineInputStream(in);
+ _contentType = contentType;
+ _config = config;
+ _contextTmpDir = contextTmpDir;
+ if (_contextTmpDir == null)
+ _contextTmpDir = new File (System.getProperty("java.io.tmpdir"));
+
+ if (_config == null)
+ _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
+ }
+
+ /**
+ * Get the already parsed parts.
+ */
+ public Collection<Part> getParsedParts()
+ {
+ if (_parts == null)
+ return Collections.emptyList();
+
+ Collection<Object> values = _parts.values();
+ List<Part> parts = new ArrayList<Part>();
+ for (Object o: values)
+ {
+ List<Part> asList = LazyList.getList(o, false);
+ parts.addAll(asList);
+ }
+ return parts;
+ }
+
+ /**
+ * Delete any tmp storage for parts, and clear out the parts list.
+ *
+ * @throws MultiException
+ */
+ public void deleteParts ()
+ throws MultiException
+ {
+ Collection<Part> parts = getParsedParts();
+ MultiException err = new MultiException();
+ for (Part p:parts)
+ {
+ try
+ {
+ ((MultiPartInputStreamParser.MultiPart)p).cleanUp();
+ }
+ catch(Exception e)
+ {
+ err.add(e);
+ }
+ }
+ _parts.clear();
+
+ err.ifExceptionThrowMulti();
+ }
+
+
+ /**
+ * Parse, if necessary, the multipart data and return the list of Parts.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ public Collection<Part> getParts()
+ throws IOException, ServletException
+ {
+ parse();
+ Collection<Object> values = _parts.values();
+ List<Part> parts = new ArrayList<Part>();
+ for (Object o: values)
+ {
+ List<Part> asList = LazyList.getList(o, false);
+ parts.addAll(asList);
+ }
+ return parts;
+ }
+
+
+ /**
+ * Get the named Part.
+ *
+ * @param name
+ * @throws IOException
+ * @throws ServletException
+ */
+ public Part getPart(String name)
+ throws IOException, ServletException
+ {
+ parse();
+ return (Part)_parts.getValue(name, 0);
+ }
+
+
+ /**
+ * Parse, if necessary, the multipart stream.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ protected void parse ()
+ throws IOException, ServletException
+ {
+ //have we already parsed the input?
+ if (_parts != null)
+ return;
+
+ //initialize
+ long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize
+ _parts = new MultiMap();
+
+ //if its not a multipart request, don't parse it
+ if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
+ return;
+
+ //sort out the location to which to write the files
+
+ if (_config.getLocation() == null)
+ _tmpDir = _contextTmpDir;
+ else if ("".equals(_config.getLocation()))
+ _tmpDir = _contextTmpDir;
+ else
+ {
+ File f = new File (_config.getLocation());
+ if (f.isAbsolute())
+ _tmpDir = f;
+ else
+ _tmpDir = new File (_contextTmpDir, _config.getLocation());
+ }
+
+ if (!_tmpDir.exists())
+ _tmpDir.mkdirs();
+
+ String contentTypeBoundary = "";
+ int bstart = _contentType.indexOf("boundary=");
+ if (bstart >= 0)
+ {
+ int bend = _contentType.indexOf(";", bstart);
+ bend = (bend < 0? _contentType.length(): bend);
+ contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend)).trim());
+ }
+
+ String boundary="--"+contentTypeBoundary;
+ byte[] byteBoundary=(boundary+"--").getBytes(StandardCharsets.ISO_8859_1);
+
+ // Get first boundary
+ String line = null;
+ try
+ {
+ line=((ReadLineInputStream)_in).readLine();
+ }
+ catch (IOException e)
+ {
+ LOG.warn("Badly formatted multipart request");
+ throw e;
+ }
+
+ if (line == null)
+ throw new IOException("Missing content for multipart request");
+
+ boolean badFormatLogged = false;
+ line=line.trim();
+ while (line != null && !line.equals(boundary))
+ {
+ if (!badFormatLogged)
+ {
+ LOG.warn("Badly formatted multipart request");
+ badFormatLogged = true;
+ }
+ line=((ReadLineInputStream)_in).readLine();
+ line=(line==null?line:line.trim());
+ }
+
+ if (line == null)
+ throw new IOException("Missing initial multi part boundary");
+
+ // Read each part
+ boolean lastPart=false;
+
+ outer:while(!lastPart)
+ {
+ String contentDisposition=null;
+ String contentType=null;
+ String contentTransferEncoding=null;
+
+ MultiMap headers = new MultiMap();
+ while(true)
+ {
+ line=((ReadLineInputStream)_in).readLine();
+
+ //No more input
+ if(line==null)
+ break outer;
+
+ //end of headers:
+ if("".equals(line))
+ break;
+
+ total += line.length();
+ if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+ throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+
+ //get content-disposition and content-type
+ int c=line.indexOf(':',0);
+ if(c>0)
+ {
+ String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH);
+ String value=line.substring(c+1,line.length()).trim();
+ headers.put(key, value);
+ if (key.equalsIgnoreCase("content-disposition"))
+ contentDisposition=value;
+ if (key.equalsIgnoreCase("content-type"))
+ contentType = value;
+ if(key.equals("content-transfer-encoding"))
+ contentTransferEncoding=value;
+ }
+ }
+
+ // Extract content-disposition
+ boolean form_data=false;
+ if(contentDisposition==null)
+ {
+ throw new IOException("Missing content-disposition");
+ }
+
+ QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true);
+ String name=null;
+ String filename=null;
+ while(tok.hasMoreTokens())
+ {
+ String t=tok.nextToken().trim();
+ String tl=t.toLowerCase(Locale.ENGLISH);
+ if(t.startsWith("form-data"))
+ form_data=true;
+ else if(tl.startsWith("name="))
+ name=value(t);
+ else if(tl.startsWith("filename="))
+ filename=filenameValue(t);
+ }
+
+ // Check disposition
+ if(!form_data)
+ {
+ continue;
+ }
+ //It is valid for reset and submit buttons to have an empty name.
+ //If no name is supplied, the browser skips sending the info for that field.
+ //However, if you supply the empty string as the name, the browser sends the
+ //field, with name as the empty string. So, only continue this loop if we
+ //have not yet seen a name field.
+ if(name==null)
+ {
+ continue;
+ }
+
+ //Have a new Part
+ MultiPart part = new MultiPart(name, filename);
+ part.setHeaders(headers);
+ part.setContentType(contentType);
+ _parts.add(name, part);
+ part.open();
+
+ InputStream partInput = null;
+ if ("base64".equalsIgnoreCase(contentTransferEncoding))
+ {
+ partInput = new Base64InputStream((ReadLineInputStream)_in);
+ }
+ else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
+ {
+ partInput = new FilterInputStream(_in)
+ {
+ @Override
+ public int read() throws IOException
+ {
+ int c = in.read();
+ if (c >= 0 && c == '=')
+ {
+ int hi = in.read();
+ int lo = in.read();
+ if (hi < 0 || lo < 0)
+ {
+ throw new IOException("Unexpected end to quoted-printable byte");
+ }
+ char[] chars = new char[] { (char)hi, (char)lo };
+ c = Integer.parseInt(new String(chars),16);
+ }
+ return c;
+ }
+ };
+ }
+ else
+ partInput = _in;
+
+
+ try
+ {
+ int state=-2;
+ int c;
+ boolean cr=false;
+ boolean lf=false;
+
+ // loop for all lines
+ while(true)
+ {
+ int b=0;
+ while((c=(state!=-2)?state:partInput.read())!=-1)
+ {
+ total ++;
+ if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+ throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+
+ state=-2;
+
+ // look for CR and/or LF
+ if(c==13||c==10)
+ {
+ if(c==13)
+ {
+ partInput.mark(1);
+ int tmp=partInput.read();
+ if (tmp!=10)
+ partInput.reset();
+ else
+ state=tmp;
+ }
+ break;
+ }
+
+ // Look for boundary
+ if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
+ {
+ b++;
+ }
+ else
+ {
+ // Got a character not part of the boundary, so we don't have the boundary marker.
+ // Write out as many chars as we matched, then the char we're looking at.
+ if(cr)
+ part.write(13);
+
+ if(lf)
+ part.write(10);
+
+ cr=lf=false;
+ if(b>0)
+ part.write(byteBoundary,0,b);
+
+ b=-1;
+ part.write(c);
+ }
+ }
+
+ // Check for incomplete boundary match, writing out the chars we matched along the way
+ if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
+ {
+ if(cr)
+ part.write(13);
+
+ if(lf)
+ part.write(10);
+
+ cr=lf=false;
+ part.write(byteBoundary,0,b);
+ b=-1;
+ }
+
+ // Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part.
+ if(b>0||c==-1)
+ {
+
+ if(b==byteBoundary.length)
+ lastPart=true;
+ if(state==10)
+ state=-2;
+ break;
+ }
+
+ // handle CR LF
+ if(cr)
+ part.write(13);
+
+ if(lf)
+ part.write(10);
+
+ cr=(c==13);
+ lf=(c==10||state==10);
+ if(state==10)
+ state=-2;
+ }
+ }
+ finally
+ {
+
+ part.close();
+ }
+ }
+ if (!lastPart)
+ throw new IOException("Incomplete parts");
+ }
+
+ public void setDeleteOnExit(boolean deleteOnExit)
+ {
+ _deleteOnExit = deleteOnExit;
+ }
+
+
+ public boolean isDeleteOnExit()
+ {
+ return _deleteOnExit;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private String value(String nameEqualsValue)
+ {
+ int idx = nameEqualsValue.indexOf('=');
+ String value = nameEqualsValue.substring(idx+1).trim();
+ return QuotedStringTokenizer.unquoteOnly(value);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private String filenameValue(String nameEqualsValue)
+ {
+ int idx = nameEqualsValue.indexOf('=');
+ String value = nameEqualsValue.substring(idx+1).trim();
+
+ if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
+ {
+ //incorrectly escaped IE filenames that have the whole path
+ //we just strip any leading & trailing quotes and leave it as is
+ char first=value.charAt(0);
+ if (first=='"' || first=='\'')
+ value=value.substring(1);
+ char last=value.charAt(value.length()-1);
+ if (last=='"' || last=='\'')
+ value = value.substring(0,value.length()-1);
+
+ return value;
+ }
+ else
+ //unquote the string, but allow any backslashes that don't
+ //form a valid escape sequence to remain as many browsers
+ //even on *nix systems will not escape a filename containing
+ //backslashes
+ return QuotedStringTokenizer.unquoteOnly(value, true);
+ }
+
+
+
+ private static class Base64InputStream extends InputStream
+ {
+ ReadLineInputStream _in;
+ String _line;
+ byte[] _buffer;
+ int _pos;
+
+
+ public Base64InputStream(ReadLineInputStream rlis)
+ {
+ _in = rlis;
+ }
+
+ @Override
+ public int read() throws IOException
+ {
+ if (_buffer==null || _pos>= _buffer.length)
+ {
+ //Any CR and LF will be consumed by the readLine() call.
+ //We need to put them back into the bytes returned from this
+ //method because the parsing of the multipart content uses them
+ //as markers to determine when we've reached the end of a part.
+ _line = _in.readLine();
+ if (_line==null)
+ return -1; //nothing left
+ if (_line.startsWith("--"))
+ _buffer=(_line+"\r\n").getBytes(); //boundary marking end of part
+ else if (_line.length()==0)
+ _buffer="\r\n".getBytes(); //blank line
+ else
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2);
+ B64Code.decode(_line, baos);
+ baos.write(13);
+ baos.write(10);
+ _buffer = baos.toByteArray();
+ }
+
+ _pos=0;
+ }
+
+ return _buffer[_pos++];
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+
+/* ================================================================ */
+/** Handle a multipart MIME response.
+ *
+ *
+ *
+*/
+public class MultiPartOutputStream extends FilterOutputStream
+{
+ /* ------------------------------------------------------------ */
+ private static final byte[] __CRLF={'\r','\n'};
+ private static final byte[] __DASHDASH={'-','-'};
+
+ public static final String MULTIPART_MIXED="multipart/mixed";
+ public static final String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace";
+
+ /* ------------------------------------------------------------ */
+ private final String boundary;
+ private final byte[] boundaryBytes;
+
+ /* ------------------------------------------------------------ */
+ private boolean inPart=false;
+
+ /* ------------------------------------------------------------ */
+ public MultiPartOutputStream(OutputStream out)
+ throws IOException
+ {
+ super(out);
+
+ boundary = "jetty"+System.identityHashCode(this)+
+ Long.toString(System.currentTimeMillis(),36);
+ boundaryBytes=boundary.getBytes(StandardCharsets.ISO_8859_1);
+ }
+
+ public MultiPartOutputStream(OutputStream out, String boundary)
+ throws IOException
+ {
+ super(out);
+
+ this.boundary = boundary;
+ boundaryBytes=boundary.getBytes(StandardCharsets.ISO_8859_1);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** End the current part.
+ * @exception IOException IOException
+ */
+ @Override
+ public void close()
+ throws IOException
+ {
+ try
+ {
+ if (inPart)
+ out.write(__CRLF);
+ out.write(__DASHDASH);
+ out.write(boundaryBytes);
+ out.write(__DASHDASH);
+ out.write(__CRLF);
+ inPart=false;
+ }
+ finally
+ {
+ super.close();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getBoundary()
+ {
+ return boundary;
+ }
+
+ public OutputStream getOut() {return out;}
+
+ /* ------------------------------------------------------------ */
+ /** Start creation of the next Content.
+ */
+ public void startPart(String contentType)
+ throws IOException
+ {
+ if (inPart)
+ out.write(__CRLF);
+ inPart=true;
+ out.write(__DASHDASH);
+ out.write(boundaryBytes);
+ out.write(__CRLF);
+ if (contentType != null)
+ out.write(("Content-Type: "+contentType).getBytes(StandardCharsets.ISO_8859_1));
+ out.write(__CRLF);
+ out.write(__CRLF);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Start creation of the next Content.
+ */
+ public void startPart(String contentType, String[] headers)
+ throws IOException
+ {
+ if (inPart)
+ out.write(__CRLF);
+ inPart=true;
+ out.write(__DASHDASH);
+ out.write(boundaryBytes);
+ out.write(__CRLF);
+ if (contentType != null)
+ out.write(("Content-Type: "+contentType).getBytes(StandardCharsets.ISO_8859_1));
+ out.write(__CRLF);
+ for (int i=0;headers!=null && i<headers.length;i++)
+ {
+ out.write(headers[i].getBytes(StandardCharsets.ISO_8859_1));
+ out.write(__CRLF);
+ }
+ out.write(__CRLF);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException
+ {
+ out.write(b,off,len);
+ }
+}
+
+
+
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+
+/* ================================================================ */
+/** Handle a multipart MIME response.
+ *
+ *
+ *
+*/
+public class MultiPartWriter extends FilterWriter
+{
+ /* ------------------------------------------------------------ */
+ private final static String __CRLF="\015\012";
+ private final static String __DASHDASH="--";
+
+ public static final String MULTIPART_MIXED=MultiPartOutputStream.MULTIPART_MIXED;
+ public static final String MULTIPART_X_MIXED_REPLACE=MultiPartOutputStream.MULTIPART_X_MIXED_REPLACE;
+
+ /* ------------------------------------------------------------ */
+ private String boundary;
+
+ /* ------------------------------------------------------------ */
+ private boolean inPart=false;
+
+ /* ------------------------------------------------------------ */
+ public MultiPartWriter(Writer out)
+ throws IOException
+ {
+ super(out);
+ boundary = "jetty"+System.identityHashCode(this)+
+ Long.toString(System.currentTimeMillis(),36);
+
+ inPart=false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** End the current part.
+ * @exception IOException IOException
+ */
+ @Override
+ public void close()
+ throws IOException
+ {
+ try
+ {
+ if (inPart)
+ out.write(__CRLF);
+ out.write(__DASHDASH);
+ out.write(boundary);
+ out.write(__DASHDASH);
+ out.write(__CRLF);
+ inPart=false;
+ }
+ finally
+ {
+ super.close();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getBoundary()
+ {
+ return boundary;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Start creation of the next Content.
+ */
+ public void startPart(String contentType)
+ throws IOException
+ {
+ if (inPart)
+ out.write(__CRLF);
+ out.write(__DASHDASH);
+ out.write(boundary);
+ out.write(__CRLF);
+ out.write("Content-Type: ");
+ out.write(contentType);
+ out.write(__CRLF);
+ out.write(__CRLF);
+ inPart=true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** end creation of the next Content.
+ */
+ public void endPart()
+ throws IOException
+ {
+ if (inPart)
+ out.write(__CRLF);
+ inPart=false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Start creation of the next Content.
+ */
+ public void startPart(String contentType, String[] headers)
+ throws IOException
+ {
+ if (inPart)
+ out.write(__CRLF);
+ out.write(__DASHDASH);
+ out.write(boundary);
+ out.write(__CRLF);
+ out.write("Content-Type: ");
+ out.write(contentType);
+ out.write(__CRLF);
+ for (int i=0;headers!=null && i<headers.length;i++)
+ {
+ out.write(headers[i]);
+ out.write(__CRLF);
+ }
+ out.write(__CRLF);
+ inPart=true;
+ }
+
+}
+
+
+
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public abstract class PatternMatcher
+{
+ public abstract void matched (URI uri) throws Exception;
+
+
+ /**
+ * Find jar names from the provided list matching a pattern.
+ *
+ * If the pattern is null and isNullInclusive is true, then
+ * all jar names will match.
+ *
+ * A pattern is a set of acceptable jar names. Each acceptable
+ * jar name is a regex. Each regex can be separated by either a
+ * "," or a "|". If you use a "|" this or's together the jar
+ * name patterns. This means that ordering of the matches is
+ * unimportant to you. If instead, you want to match particular
+ * jar names, and you want to match them in order, you should
+ * separate the regexs with "," instead.
+ *
+ * Eg "aaa-.*\\.jar|bbb-.*\\.jar"
+ * Will iterate over the jar names and match
+ * in any order.
+ *
+ * Eg "aaa-*\\.jar,bbb-.*\\.jar"
+ * Will iterate over the jar names, matching
+ * all those starting with "aaa-" first, then "bbb-".
+ *
+ * @param pattern the pattern
+ * @param uris the uris to test the pattern against
+ * @param isNullInclusive if true, an empty pattern means all names match, if false, none match
+ * @throws Exception
+ */
+ public void match (Pattern pattern, URI[] uris, boolean isNullInclusive)
+ throws Exception
+ {
+ if (uris!=null)
+ {
+ String[] patterns = (pattern==null?null:pattern.pattern().split(","));
+
+ List<Pattern> subPatterns = new ArrayList<Pattern>();
+ for (int i=0; patterns!=null && i<patterns.length;i++)
+ {
+ subPatterns.add(Pattern.compile(patterns[i]));
+ }
+ if (subPatterns.isEmpty())
+ subPatterns.add(pattern);
+
+ if (subPatterns.isEmpty())
+ {
+ matchPatterns(null, uris, isNullInclusive);
+ }
+ else
+ {
+ //for each subpattern, iterate over all the urls, processing those that match
+ for (Pattern p : subPatterns)
+ {
+ matchPatterns(p, uris, isNullInclusive);
+ }
+ }
+ }
+ }
+
+
+ public void matchPatterns (Pattern pattern, URI[] uris, boolean isNullInclusive)
+ throws Exception
+ {
+ for (int i=0; i<uris.length;i++)
+ {
+ URI uri = uris[i];
+ String s = uri.toString();
+ if ((pattern == null && isNullInclusive)
+ ||
+ (pattern!=null && pattern.matcher(s).matches()))
+ {
+ matched(uris[i]);
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * <p>A callback abstraction that handles completed/failed events of asynchronous operations.</p>
+ *
+ * @param <C> the type of the context object
+ */
+public interface Promise<C>
+{
+ /**
+ * <p>Callback invoked when the operation completes.</p>
+ *
+ * @param result the context
+ * @see #failed(Throwable)
+ */
+ public abstract void succeeded(C result);
+
+ /**
+ * <p>Callback invoked when the operation fails.</p>
+ *
+ * @param x the reason for the operation failure
+ */
+ public void failed(Throwable x);
+
+
+ /**
+ * <p>Empty implementation of {@link Promise}</p>
+ *
+ * @param <C> the type of the context object
+ */
+ public static class Adapter<C> implements Promise<C>
+ {
+ @Override
+ public void succeeded(C result)
+ {
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ Log.getLogger(this.getClass()).warn(x);
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/* ------------------------------------------------------------ */
+/** StringTokenizer with Quoting support.
+ *
+ * This class is a copy of the java.util.StringTokenizer API and
+ * the behaviour is the same, except that single and double quoted
+ * string values are recognised.
+ * Delimiters within quotes are not considered delimiters.
+ * Quotes can be escaped with '\'.
+ *
+ * @see java.util.StringTokenizer
+ *
+ */
+public class QuotedStringTokenizer
+ extends StringTokenizer
+{
+ private final static String __delim="\t\n\r";
+ private String _string;
+ private String _delim = __delim;
+ private boolean _returnQuotes=false;
+ private boolean _returnDelimiters=false;
+ private StringBuffer _token;
+ private boolean _hasToken=false;
+ private int _i=0;
+ private int _lastStart=0;
+ private boolean _double=true;
+ private boolean _single=true;
+
+ /* ------------------------------------------------------------ */
+ public QuotedStringTokenizer(String str,
+ String delim,
+ boolean returnDelimiters,
+ boolean returnQuotes)
+ {
+ super("");
+ _string=str;
+ if (delim!=null)
+ _delim=delim;
+ _returnDelimiters=returnDelimiters;
+ _returnQuotes=returnQuotes;
+
+ if (_delim.indexOf('\'')>=0 ||
+ _delim.indexOf('"')>=0)
+ throw new Error("Can't use quotes as delimiters: "+_delim);
+
+ _token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
+ }
+
+ /* ------------------------------------------------------------ */
+ public QuotedStringTokenizer(String str,
+ String delim,
+ boolean returnDelimiters)
+ {
+ this(str,delim,returnDelimiters,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ public QuotedStringTokenizer(String str,
+ String delim)
+ {
+ this(str,delim,false,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ public QuotedStringTokenizer(String str)
+ {
+ this(str,null,false,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean hasMoreTokens()
+ {
+ // Already found a token
+ if (_hasToken)
+ return true;
+
+ _lastStart=_i;
+
+ int state=0;
+ boolean escape=false;
+ while (_i<_string.length())
+ {
+ char c=_string.charAt(_i++);
+
+ switch (state)
+ {
+ case 0: // Start
+ if(_delim.indexOf(c)>=0)
+ {
+ if (_returnDelimiters)
+ {
+ _token.append(c);
+ return _hasToken=true;
+ }
+ }
+ else if (c=='\'' && _single)
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=2;
+ }
+ else if (c=='\"' && _double)
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=3;
+ }
+ else
+ {
+ _token.append(c);
+ _hasToken=true;
+ state=1;
+ }
+ break;
+
+ case 1: // Token
+ _hasToken=true;
+ if(_delim.indexOf(c)>=0)
+ {
+ if (_returnDelimiters)
+ _i--;
+ return _hasToken;
+ }
+ else if (c=='\'' && _single)
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=2;
+ }
+ else if (c=='\"' && _double)
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=3;
+ }
+ else
+ {
+ _token.append(c);
+ }
+ break;
+
+ case 2: // Single Quote
+ _hasToken=true;
+ if (escape)
+ {
+ escape=false;
+ _token.append(c);
+ }
+ else if (c=='\'')
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=1;
+ }
+ else if (c=='\\')
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ escape=true;
+ }
+ else
+ {
+ _token.append(c);
+ }
+ break;
+
+ case 3: // Double Quote
+ _hasToken=true;
+ if (escape)
+ {
+ escape=false;
+ _token.append(c);
+ }
+ else if (c=='\"')
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ state=1;
+ }
+ else if (c=='\\')
+ {
+ if (_returnQuotes)
+ _token.append(c);
+ escape=true;
+ }
+ else
+ {
+ _token.append(c);
+ }
+ break;
+ }
+ }
+
+ return _hasToken;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String nextToken()
+ throws NoSuchElementException
+ {
+ if (!hasMoreTokens() || _token==null)
+ throw new NoSuchElementException();
+ String t=_token.toString();
+ _token.setLength(0);
+ _hasToken=false;
+ return t;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String nextToken(String delim)
+ throws NoSuchElementException
+ {
+ _delim=delim;
+ _i=_lastStart;
+ _token.setLength(0);
+ _hasToken=false;
+ return nextToken();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean hasMoreElements()
+ {
+ return hasMoreTokens();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object nextElement()
+ throws NoSuchElementException
+ {
+ return nextToken();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Not implemented.
+ */
+ @Override
+ public int countTokens()
+ {
+ return -1;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Quote a string.
+ * The string is quoted only if quoting is required due to
+ * embedded delimiters, quote characters or the
+ * empty string.
+ * @param s The string to quote.
+ * @param delim the delimiter to use to quote the string
+ * @return quoted string
+ */
+ public static String quoteIfNeeded(String s, String delim)
+ {
+ if (s==null)
+ return null;
+ if (s.length()==0)
+ return "\"\"";
+
+
+ for (int i=0;i<s.length();i++)
+ {
+ char c = s.charAt(i);
+ if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0)
+ {
+ StringBuffer b=new StringBuffer(s.length()+8);
+ quote(b,s);
+ return b.toString();
+ }
+ }
+
+ return s;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Quote a string.
+ * The string is quoted only if quoting is required due to
+ * embeded delimiters, quote characters or the
+ * empty string.
+ * @param s The string to quote.
+ * @return quoted string
+ */
+ public static String quote(String s)
+ {
+ if (s==null)
+ return null;
+ if (s.length()==0)
+ return "\"\"";
+
+ StringBuffer b=new StringBuffer(s.length()+8);
+ quote(b,s);
+ return b.toString();
+
+ }
+
+ private static final char[] escapes = new char[32];
+ static
+ {
+ Arrays.fill(escapes, (char)0xFFFF);
+ escapes['\b'] = 'b';
+ escapes['\t'] = 't';
+ escapes['\n'] = 'n';
+ escapes['\f'] = 'f';
+ escapes['\r'] = 'r';
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Quote a string into an Appendable.
+ * Only quotes and backslash are escaped.
+ * @param buffer The Appendable
+ * @param input The String to quote.
+ */
+ public static void quoteOnly(Appendable buffer, String input)
+ {
+ if(input==null)
+ return;
+
+ try
+ {
+ buffer.append('"');
+ for (int i = 0; i < input.length(); ++i)
+ {
+ char c = input.charAt(i);
+ if (c == '"' || c == '\\')
+ buffer.append('\\');
+ buffer.append(c);
+ }
+ buffer.append('"');
+ }
+ catch (IOException x)
+ {
+ throw new RuntimeException(x);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Quote a string into an Appendable.
+ * The characters ", \, \n, \r, \t, \f and \b are escaped
+ * @param buffer The Appendable
+ * @param input The String to quote.
+ */
+ public static void quote(Appendable buffer, String input)
+ {
+ if(input==null)
+ return;
+
+ try
+ {
+ buffer.append('"');
+ for (int i = 0; i < input.length(); ++i)
+ {
+ char c = input.charAt(i);
+ if (c >= 32)
+ {
+ if (c == '"' || c == '\\')
+ buffer.append('\\');
+ buffer.append(c);
+ }
+ else
+ {
+ char escape = escapes[c];
+ if (escape == 0xFFFF)
+ {
+ // Unicode escape
+ buffer.append('\\').append('u').append('0').append('0');
+ if (c < 0x10)
+ buffer.append('0');
+ buffer.append(Integer.toString(c, 16));
+ }
+ else
+ {
+ buffer.append('\\').append(escape);
+ }
+ }
+ }
+ buffer.append('"');
+ }
+ catch (IOException x)
+ {
+ throw new RuntimeException(x);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static String unquoteOnly(String s)
+ {
+ return unquoteOnly(s, false);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Unquote a string, NOT converting unicode sequences
+ * @param s The string to unquote.
+ * @param lenient if true, will leave in backslashes that aren't valid escapes
+ * @return quoted string
+ */
+ public static String unquoteOnly(String s, boolean lenient)
+ {
+ if (s==null)
+ return null;
+ if (s.length()<2)
+ return s;
+
+ char first=s.charAt(0);
+ char last=s.charAt(s.length()-1);
+ if (first!=last || (first!='"' && first!='\''))
+ return s;
+
+ StringBuilder b = new StringBuilder(s.length() - 2);
+ boolean escape=false;
+ for (int i=1;i<s.length()-1;i++)
+ {
+ char c = s.charAt(i);
+
+ if (escape)
+ {
+ escape=false;
+ if (lenient && !isValidEscaping(c))
+ {
+ b.append('\\');
+ }
+ b.append(c);
+ }
+ else if (c=='\\')
+ {
+ escape=true;
+ }
+ else
+ {
+ b.append(c);
+ }
+ }
+
+ return b.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String unquote(String s)
+ {
+ return unquote(s,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Unquote a string.
+ * @param s The string to unquote.
+ * @return quoted string
+ */
+ public static String unquote(String s, boolean lenient)
+ {
+ if (s==null)
+ return null;
+ if (s.length()<2)
+ return s;
+
+ char first=s.charAt(0);
+ char last=s.charAt(s.length()-1);
+ if (first!=last || (first!='"' && first!='\''))
+ return s;
+
+ StringBuilder b = new StringBuilder(s.length() - 2);
+ boolean escape=false;
+ for (int i=1;i<s.length()-1;i++)
+ {
+ char c = s.charAt(i);
+
+ if (escape)
+ {
+ escape=false;
+ switch (c)
+ {
+ case 'n':
+ b.append('\n');
+ break;
+ case 'r':
+ b.append('\r');
+ break;
+ case 't':
+ b.append('\t');
+ break;
+ case 'f':
+ b.append('\f');
+ break;
+ case 'b':
+ b.append('\b');
+ break;
+ case '\\':
+ b.append('\\');
+ break;
+ case '/':
+ b.append('/');
+ break;
+ case '"':
+ b.append('"');
+ break;
+ case 'u':
+ b.append((char)(
+ (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
+ (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
+ (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
+ (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
+ )
+ );
+ break;
+ default:
+ if (lenient && !isValidEscaping(c))
+ {
+ b.append('\\');
+ }
+ b.append(c);
+ }
+ }
+ else if (c=='\\')
+ {
+ escape=true;
+ }
+ else
+ {
+ b.append(c);
+ }
+ }
+
+ return b.toString();
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Check that char c (which is preceded by a backslash) is a valid
+ * escape sequence.
+ * @param c
+ * @return
+ */
+ private static boolean isValidEscaping(char c)
+ {
+ return ((c == 'n') || (c == 'r') || (c == 't') ||
+ (c == 'f') || (c == 'b') || (c == '\\') ||
+ (c == '/') || (c == '"') || (c == 'u'));
+ }
+
+ /* ------------------------------------------------------------ */
+ public static boolean isQuoted(String s)
+ {
+ return s!=null && s.length()>0 && s.charAt(0)=='"' && s.charAt(s.length()-1)=='"';
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return handle double quotes if true
+ */
+ public boolean getDouble()
+ {
+ return _double;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param d handle double quotes if true
+ */
+ public void setDouble(boolean d)
+ {
+ _double=d;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return handle single quotes if true
+ */
+ public boolean getSingle()
+ {
+ return _single;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param single handle single quotes if true
+ */
+ public void setSingle(boolean single)
+ {
+ _single=single;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * ReadLineInputStream
+ *
+ * Read from an input stream, accepting CR/LF, LF or just CR.
+ */
+public class ReadLineInputStream extends BufferedInputStream
+{
+ boolean _seenCRLF;
+ boolean _skipLF;
+
+ public ReadLineInputStream(InputStream in)
+ {
+ super(in);
+ }
+
+ public ReadLineInputStream(InputStream in, int size)
+ {
+ super(in,size);
+ }
+
+ public String readLine() throws IOException
+ {
+ mark(buf.length);
+
+ while (true)
+ {
+ int b=super.read();
+
+ if (markpos < 0)
+ throw new IOException("Buffer size exceeded: no line terminator");
+
+ if (b==-1)
+ {
+ int m=markpos;
+ markpos=-1;
+ if (pos>m)
+ return new String(buf,m,pos-m, StandardCharsets.UTF_8);
+
+ return null;
+ }
+
+ if (b=='\r')
+ {
+ int p=pos;
+
+ // if we have seen CRLF before, hungrily consume LF
+ if (_seenCRLF && pos<count)
+ {
+ if (buf[pos]=='\n')
+ pos+=1;
+ }
+ else
+ _skipLF=true;
+ int m=markpos;
+ markpos=-1;
+ return new String(buf,m,p-m-1,StandardCharsets.UTF_8);
+ }
+
+ if (b=='\n')
+ {
+ if (_skipLF)
+ {
+ _skipLF=false;
+ _seenCRLF=true;
+ markpos++;
+ continue;
+ }
+ int m=markpos;
+ markpos=-1;
+ return new String(buf,m,pos-m-1,StandardCharsets.UTF_8);
+ }
+ }
+ }
+
+ @Override
+ public synchronized int read() throws IOException
+ {
+ int b = super.read();
+ if (_skipLF)
+ {
+ _skipLF=false;
+ if (_seenCRLF && b=='\n')
+ b=super.read();
+ }
+ return b;
+ }
+
+ @Override
+ public synchronized int read(byte[] buf, int off, int len) throws IOException
+ {
+ if (_skipLF && len>0)
+ {
+ _skipLF=false;
+ if (_seenCRLF)
+ {
+ int b = super.read();
+ if (b==-1)
+ return -1;
+
+ if (b!='\n')
+ {
+ buf[off]=(byte)(0xff&b);
+ return 1+super.read(buf,off+1,len-1);
+ }
+ }
+ }
+
+ return super.read(buf,off,len);
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * RolloverFileOutputStream
+ *
+ * This output stream puts content in a file that is rolled over every 24 hours.
+ * The filename must include the string "yyyy_mm_dd", which is replaced with the
+ * actual date when creating and rolling over the file.
+ *
+ * Old files are retained for a number of days before being deleted.
+ *
+ *
+ */
+public class RolloverFileOutputStream extends FilterOutputStream
+{
+ private static Timer __rollover;
+
+ final static String YYYY_MM_DD="yyyy_mm_dd";
+ final static String ROLLOVER_FILE_DATE_FORMAT = "yyyy_MM_dd";
+ final static String ROLLOVER_FILE_BACKUP_FORMAT = "HHmmssSSS";
+ final static int ROLLOVER_FILE_RETAIN_DAYS = 31;
+
+ private RollTask _rollTask;
+ private SimpleDateFormat _fileBackupFormat;
+ private SimpleDateFormat _fileDateFormat;
+
+ private String _filename;
+ private File _file;
+ private boolean _append;
+ private int _retainDays;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filename The filename must include the string "yyyy_mm_dd",
+ * which is replaced with the actual date when creating and rolling over the file.
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename)
+ throws IOException
+ {
+ this(filename,true,ROLLOVER_FILE_RETAIN_DAYS);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filename The filename must include the string "yyyy_mm_dd",
+ * which is replaced with the actual date when creating and rolling over the file.
+ * @param append If true, existing files will be appended to.
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename, boolean append)
+ throws IOException
+ {
+ this(filename,append,ROLLOVER_FILE_RETAIN_DAYS);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filename The filename must include the string "yyyy_mm_dd",
+ * which is replaced with the actual date when creating and rolling over the file.
+ * @param append If true, existing files will be appended to.
+ * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename,
+ boolean append,
+ int retainDays)
+ throws IOException
+ {
+ this(filename,append,retainDays,TimeZone.getDefault());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filename The filename must include the string "yyyy_mm_dd",
+ * which is replaced with the actual date when creating and rolling over the file.
+ * @param append If true, existing files will be appended to.
+ * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename,
+ boolean append,
+ int retainDays,
+ TimeZone zone)
+ throws IOException
+ {
+
+ this(filename,append,retainDays,zone,null,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param filename The filename must include the string "yyyy_mm_dd",
+ * which is replaced with the actual date when creating and rolling over the file.
+ * @param append If true, existing files will be appended to.
+ * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
+ * @param dateFormat The format for the date file substitution. The default is "yyyy_MM_dd".
+ * @param backupFormat The format for the file extension of backup files. The default is "HHmmssSSS".
+ * @throws IOException
+ */
+ public RolloverFileOutputStream(String filename,
+ boolean append,
+ int retainDays,
+ TimeZone zone,
+ String dateFormat,
+ String backupFormat)
+ throws IOException
+ {
+ super(null);
+
+ if (dateFormat==null)
+ dateFormat=ROLLOVER_FILE_DATE_FORMAT;
+ _fileDateFormat = new SimpleDateFormat(dateFormat);
+
+ if (backupFormat==null)
+ backupFormat=ROLLOVER_FILE_BACKUP_FORMAT;
+ _fileBackupFormat = new SimpleDateFormat(backupFormat);
+
+ _fileBackupFormat.setTimeZone(zone);
+ _fileDateFormat.setTimeZone(zone);
+
+ if (filename!=null)
+ {
+ filename=filename.trim();
+ if (filename.length()==0)
+ filename=null;
+ }
+ if (filename==null)
+ throw new IllegalArgumentException("Invalid filename");
+
+ _filename=filename;
+ _append=append;
+ _retainDays=retainDays;
+ setFile();
+
+ synchronized(RolloverFileOutputStream.class)
+ {
+ if (__rollover==null)
+ __rollover=new Timer(RolloverFileOutputStream.class.getName(),true);
+
+ _rollTask=new RollTask();
+
+ Calendar now = Calendar.getInstance();
+ now.setTimeZone(zone);
+
+ GregorianCalendar midnight =
+ new GregorianCalendar(now.get(Calendar.YEAR),
+ now.get(Calendar.MONTH),
+ now.get(Calendar.DAY_OF_MONTH),
+ 23,0);
+ midnight.setTimeZone(zone);
+ midnight.add(Calendar.HOUR,1);
+ __rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getFilename()
+ {
+ return _filename;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getDatedFilename()
+ {
+ if (_file==null)
+ return null;
+ return _file.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getRetainDays()
+ {
+ return _retainDays;
+ }
+
+ /* ------------------------------------------------------------ */
+ private synchronized void setFile()
+ throws IOException
+ {
+ // Check directory
+ File file = new File(_filename);
+ _filename=file.getCanonicalPath();
+ file=new File(_filename);
+ File dir= new File(file.getParent());
+ if (!dir.isDirectory() || !dir.canWrite())
+ throw new IOException("Cannot write log directory "+dir);
+
+ Date now=new Date();
+
+ // Is this a rollover file?
+ String filename=file.getName();
+ int i=filename.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+ if (i>=0)
+ {
+ file=new File(dir,
+ filename.substring(0,i)+
+ _fileDateFormat.format(now)+
+ filename.substring(i+YYYY_MM_DD.length()));
+ }
+
+ if (file.exists()&&!file.canWrite())
+ throw new IOException("Cannot write log file "+file);
+
+ // Do we need to change the output stream?
+ if (out==null || !file.equals(_file))
+ {
+ // Yep
+ _file=file;
+ if (!_append && file.exists())
+ file.renameTo(new File(file.toString()+"."+_fileBackupFormat.format(now)));
+ OutputStream oldOut=out;
+ out=new FileOutputStream(file.toString(),_append);
+ if (oldOut!=null)
+ oldOut.close();
+ //if(log.isDebugEnabled())log.debug("Opened "+_file);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private void removeOldFiles()
+ {
+ if (_retainDays>0)
+ {
+ long now = System.currentTimeMillis();
+
+ File file= new File(_filename);
+ File dir = new File(file.getParent());
+ String fn=file.getName();
+ int s=fn.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+ if (s<0)
+ return;
+ String prefix=fn.substring(0,s);
+ String suffix=fn.substring(s+YYYY_MM_DD.length());
+
+ String[] logList=dir.list();
+ for (int i=0;i<logList.length;i++)
+ {
+ fn = logList[i];
+ if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0)
+ {
+ File f = new File(dir,fn);
+ long date = f.lastModified();
+ if ( ((now-date)/(1000*60*60*24))>_retainDays)
+ f.delete();
+ }
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (byte[] buf)
+ throws IOException
+ {
+ out.write (buf);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void write (byte[] buf, int off, int len)
+ throws IOException
+ {
+ out.write (buf, off, len);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ @Override
+ public void close()
+ throws IOException
+ {
+ synchronized(RolloverFileOutputStream.class)
+ {
+ try{super.close();}
+ finally
+ {
+ out=null;
+ _file=null;
+ }
+
+ _rollTask.cancel();
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ private class RollTask extends TimerTask
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ RolloverFileOutputStream.this.setFile();
+ RolloverFileOutputStream.this.removeOldFiles();
+
+ }
+ catch(IOException e)
+ {
+ // Cannot log this exception to a LOG, as RolloverFOS can be used by logging
+ e.printStackTrace();
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * Scanner
+ *
+ * Utility for scanning a directory for added, removed and changed
+ * files and reporting these events via registered Listeners.
+ *
+ */
+public class Scanner extends AbstractLifeCycle
+{
+ private static final Logger LOG = Log.getLogger(Scanner.class);
+ private static int __scannerId=0;
+ private int _scanInterval;
+ private int _scanCount = 0;
+ private final List<Listener> _listeners = new ArrayList<Listener>();
+ private final Map<String,TimeNSize> _prevScan = new HashMap<String,TimeNSize> ();
+ private final Map<String,TimeNSize> _currentScan = new HashMap<String,TimeNSize> ();
+ private FilenameFilter _filter;
+ private final List<File> _scanDirs = new ArrayList<File>();
+ private volatile boolean _running = false;
+ private boolean _reportExisting = true;
+ private boolean _reportDirs = true;
+ private Timer _timer;
+ private TimerTask _task;
+ private int _scanDepth=0;
+
+ public enum Notification { ADDED, CHANGED, REMOVED };
+ private final Map<String,Notification> _notifications = new HashMap<String,Notification>();
+
+ static class TimeNSize
+ {
+ final long _lastModified;
+ final long _size;
+
+ public TimeNSize(long lastModified, long size)
+ {
+ _lastModified = lastModified;
+ _size = size;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (int)_lastModified^(int)_size;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o instanceof TimeNSize)
+ {
+ TimeNSize tns = (TimeNSize)o;
+ return tns._lastModified==_lastModified && tns._size==_size;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "[lm="+_lastModified+",s="+_size+"]";
+ }
+ }
+
+ /**
+ * Listener
+ *
+ * Marker for notifications re file changes.
+ */
+ public interface Listener
+ {
+ }
+
+ public interface ScanListener extends Listener
+ {
+ public void scan();
+ }
+
+ public interface DiscreteListener extends Listener
+ {
+ public void fileChanged (String filename) throws Exception;
+ public void fileAdded (String filename) throws Exception;
+ public void fileRemoved (String filename) throws Exception;
+ }
+
+
+ public interface BulkListener extends Listener
+ {
+ public void filesChanged (List<String> filenames) throws Exception;
+ }
+
+ /**
+ * Listener that notifies when a scan has started and when it has ended.
+ */
+ public interface ScanCycleListener extends Listener
+ {
+ public void scanStarted(int cycle) throws Exception;
+ public void scanEnded(int cycle) throws Exception;
+ }
+
+ /**
+ *
+ */
+ public Scanner ()
+ {
+ }
+
+ /**
+ * Get the scan interval
+ * @return interval between scans in seconds
+ */
+ public synchronized int getScanInterval()
+ {
+ return _scanInterval;
+ }
+
+ /**
+ * Set the scan interval
+ * @param scanInterval pause between scans in seconds, or 0 for no scan after the initial scan.
+ */
+ public synchronized void setScanInterval(int scanInterval)
+ {
+ _scanInterval = scanInterval;
+ schedule();
+ }
+
+ public void setScanDirs (List<File> dirs)
+ {
+ _scanDirs.clear();
+ _scanDirs.addAll(dirs);
+ }
+
+ public synchronized void addScanDir( File dir )
+ {
+ _scanDirs.add( dir );
+ }
+
+ public List<File> getScanDirs ()
+ {
+ return Collections.unmodifiableList(_scanDirs);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param recursive True if scanning is recursive
+ * @see #setScanDepth(int)
+ */
+ public void setRecursive (boolean recursive)
+ {
+ _scanDepth=recursive?-1:0;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if scanning is fully recursive (scandepth==-1)
+ * @see #getScanDepth()
+ */
+ public boolean getRecursive ()
+ {
+ return _scanDepth==-1;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the scanDepth.
+ * @return the scanDepth
+ */
+ public int getScanDepth()
+ {
+ return _scanDepth;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the scanDepth.
+ * @param scanDepth the scanDepth to set
+ */
+ public void setScanDepth(int scanDepth)
+ {
+ _scanDepth = scanDepth;
+ }
+
+ /**
+ * Apply a filter to files found in the scan directory.
+ * Only files matching the filter will be reported as added/changed/removed.
+ * @param filter
+ */
+ public void setFilenameFilter (FilenameFilter filter)
+ {
+ _filter = filter;
+ }
+
+ /**
+ * Get any filter applied to files in the scan dir.
+ * @return the filename filter
+ */
+ public FilenameFilter getFilenameFilter ()
+ {
+ return _filter;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Whether or not an initial scan will report all files as being
+ * added.
+ * @param reportExisting if true, all files found on initial scan will be
+ * reported as being added, otherwise not
+ */
+ public void setReportExistingFilesOnStartup (boolean reportExisting)
+ {
+ _reportExisting = reportExisting;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getReportExistingFilesOnStartup()
+ {
+ return _reportExisting;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set if found directories should be reported.
+ * @param dirs
+ */
+ public void setReportDirs(boolean dirs)
+ {
+ _reportDirs=dirs;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getReportDirs()
+ {
+ return _reportDirs;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Add an added/removed/changed listener
+ * @param listener
+ */
+ public synchronized void addListener (Listener listener)
+ {
+ if (listener == null)
+ return;
+ _listeners.add(listener);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Remove a registered listener
+ * @param listener the Listener to be removed
+ */
+ public synchronized void removeListener (Listener listener)
+ {
+ if (listener == null)
+ return;
+ _listeners.remove(listener);
+ }
+
+
+ /**
+ * Start the scanning action.
+ */
+ @Override
+ public synchronized void doStart()
+ {
+ if (_running)
+ return;
+
+ _running = true;
+
+ if (_reportExisting)
+ {
+ // if files exist at startup, report them
+ scan();
+ scan(); // scan twice so files reported as stable
+ }
+ else
+ {
+ //just register the list of existing files and only report changes
+ scanFiles();
+ _prevScan.putAll(_currentScan);
+ }
+ schedule();
+ }
+
+ public TimerTask newTimerTask ()
+ {
+ return new TimerTask()
+ {
+ @Override
+ public void run() { scan(); }
+ };
+ }
+
+ public Timer newTimer ()
+ {
+ return new Timer("Scanner-"+__scannerId++, true);
+ }
+
+ public void schedule ()
+ {
+ if (_running)
+ {
+ if (_timer!=null)
+ _timer.cancel();
+ if (_task!=null)
+ _task.cancel();
+ if (getScanInterval() > 0)
+ {
+ _timer = newTimer();
+ _task = newTimerTask();
+ _timer.schedule(_task, 1010L*getScanInterval(),1010L*getScanInterval());
+ }
+ }
+ }
+ /**
+ * Stop the scanning.
+ */
+ @Override
+ public synchronized void doStop()
+ {
+ if (_running)
+ {
+ _running = false;
+ if (_timer!=null)
+ _timer.cancel();
+ if (_task!=null)
+ _task.cancel();
+ _task=null;
+ _timer=null;
+ }
+ }
+
+ /**
+ * @return true if the path exists in one of the scandirs
+ */
+ public boolean exists(String path)
+ {
+ for (File dir : _scanDirs)
+ if (new File(dir,path).exists())
+ return true;
+ return false;
+ }
+
+
+ /**
+ * Perform a pass of the scanner and report changes
+ */
+ public synchronized void scan ()
+ {
+ reportScanStart(++_scanCount);
+ scanFiles();
+ reportDifferences(_currentScan, _prevScan);
+ _prevScan.clear();
+ _prevScan.putAll(_currentScan);
+ reportScanEnd(_scanCount);
+
+ for (Listener l : _listeners)
+ {
+ try
+ {
+ if (l instanceof ScanListener)
+ ((ScanListener)l).scan();
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ catch (Error e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+
+ /**
+ * Recursively scan all files in the designated directories.
+ */
+ public synchronized void scanFiles ()
+ {
+ if (_scanDirs==null)
+ return;
+
+ _currentScan.clear();
+ Iterator<File> itor = _scanDirs.iterator();
+ while (itor.hasNext())
+ {
+ File dir = itor.next();
+
+ if ((dir != null) && (dir.exists()))
+ try
+ {
+ scanFile(dir.getCanonicalFile(), _currentScan,0);
+ }
+ catch (IOException e)
+ {
+ LOG.warn("Error scanning files.", e);
+ }
+ }
+ }
+
+
+ /**
+ * Report the adds/changes/removes to the registered listeners
+ *
+ * @param currentScan the info from the most recent pass
+ * @param oldScan info from the previous pass
+ */
+ public synchronized void reportDifferences (Map<String,TimeNSize> currentScan, Map<String,TimeNSize> oldScan)
+ {
+ // scan the differences and add what was found to the map of notifications:
+
+ Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet());
+
+ // Look for new and changed files
+ for (Map.Entry<String, TimeNSize> entry: currentScan.entrySet())
+ {
+ String file = entry.getKey();
+ if (!oldScanKeys.contains(file))
+ {
+ Notification old=_notifications.put(file,Notification.ADDED);
+ if (old!=null)
+ {
+ switch(old)
+ {
+ case REMOVED:
+ case CHANGED:
+ _notifications.put(file,Notification.CHANGED);
+ }
+ }
+ }
+ else if (!oldScan.get(file).equals(currentScan.get(file)))
+ {
+ Notification old=_notifications.put(file,Notification.CHANGED);
+ if (old!=null)
+ {
+ switch(old)
+ {
+ case ADDED:
+ _notifications.put(file,Notification.ADDED);
+ }
+ }
+ }
+ }
+
+ // Look for deleted files
+ for (String file : oldScan.keySet())
+ {
+ if (!currentScan.containsKey(file))
+ {
+ Notification old=_notifications.put(file,Notification.REMOVED);
+ if (old!=null)
+ {
+ switch(old)
+ {
+ case ADDED:
+ _notifications.remove(file);
+ }
+ }
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("scanned "+_scanDirs+": "+_notifications);
+
+ // Process notifications
+ // Only process notifications that are for stable files (ie same in old and current scan).
+ List<String> bulkChanges = new ArrayList<String>();
+ for (Iterator<Entry<String,Notification>> iter = _notifications.entrySet().iterator();iter.hasNext();)
+ {
+ Entry<String,Notification> entry=iter.next();
+ String file=entry.getKey();
+
+ // Is the file stable?
+ if (oldScan.containsKey(file))
+ {
+ if (!oldScan.get(file).equals(currentScan.get(file)))
+ continue;
+ }
+ else if (currentScan.containsKey(file))
+ continue;
+
+ // File is stable so notify
+ Notification notification=entry.getValue();
+ iter.remove();
+ bulkChanges.add(file);
+ switch(notification)
+ {
+ case ADDED:
+ reportAddition(file);
+ break;
+ case CHANGED:
+ reportChange(file);
+ break;
+ case REMOVED:
+ reportRemoval(file);
+ break;
+ }
+ }
+ if (!bulkChanges.isEmpty())
+ reportBulkChanges(bulkChanges);
+ }
+
+
+ /**
+ * Get last modified time on a single file or recurse if
+ * the file is a directory.
+ * @param f file or directory
+ * @param scanInfoMap map of filenames to last modified times
+ */
+ private void scanFile (File f, Map<String,TimeNSize> scanInfoMap, int depth)
+ {
+ try
+ {
+ if (!f.exists())
+ return;
+
+ if (f.isFile() || depth>0&& _reportDirs && f.isDirectory())
+ {
+ if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
+ {
+ LOG.debug("scan accepted {}",f);
+ String name = f.getCanonicalPath();
+ scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.length()));
+ }
+ else
+ LOG.debug("scan rejected {}",f);
+ }
+
+ // If it is a directory, scan if it is a known directory or the depth is OK.
+ if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f)))
+ {
+ File[] files = f.listFiles();
+ if (files != null)
+ {
+ for (int i=0;i<files.length;i++)
+ scanFile(files[i], scanInfoMap,depth+1);
+ }
+ else
+ LOG.warn("Error listing files in directory {}", f);
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.warn("Error scanning watched files", e);
+ }
+ }
+
+ private void warn(Object listener,String filename,Throwable th)
+ {
+ LOG.warn(listener+" failed on '"+filename, th);
+ }
+
+ /**
+ * Report a file addition to the registered FileAddedListeners
+ * @param filename
+ */
+ private void reportAddition (String filename)
+ {
+ Iterator<Listener> itor = _listeners.iterator();
+ while (itor.hasNext())
+ {
+ Listener l = itor.next();
+ try
+ {
+ if (l instanceof DiscreteListener)
+ ((DiscreteListener)l).fileAdded(filename);
+ }
+ catch (Exception e)
+ {
+ warn(l,filename,e);
+ }
+ catch (Error e)
+ {
+ warn(l,filename,e);
+ }
+ }
+ }
+
+
+ /**
+ * Report a file removal to the FileRemovedListeners
+ * @param filename
+ */
+ private void reportRemoval (String filename)
+ {
+ Iterator<Listener> itor = _listeners.iterator();
+ while (itor.hasNext())
+ {
+ Object l = itor.next();
+ try
+ {
+ if (l instanceof DiscreteListener)
+ ((DiscreteListener)l).fileRemoved(filename);
+ }
+ catch (Exception e)
+ {
+ warn(l,filename,e);
+ }
+ catch (Error e)
+ {
+ warn(l,filename,e);
+ }
+ }
+ }
+
+
+ /**
+ * Report a file change to the FileChangedListeners
+ * @param filename
+ */
+ private void reportChange (String filename)
+ {
+ Iterator<Listener> itor = _listeners.iterator();
+ while (itor.hasNext())
+ {
+ Listener l = itor.next();
+ try
+ {
+ if (l instanceof DiscreteListener)
+ ((DiscreteListener)l).fileChanged(filename);
+ }
+ catch (Exception e)
+ {
+ warn(l,filename,e);
+ }
+ catch (Error e)
+ {
+ warn(l,filename,e);
+ }
+ }
+ }
+
+ private void reportBulkChanges (List<String> filenames)
+ {
+ Iterator<Listener> itor = _listeners.iterator();
+ while (itor.hasNext())
+ {
+ Listener l = itor.next();
+ try
+ {
+ if (l instanceof BulkListener)
+ ((BulkListener)l).filesChanged(filenames);
+ }
+ catch (Exception e)
+ {
+ warn(l,filenames.toString(),e);
+ }
+ catch (Error e)
+ {
+ warn(l,filenames.toString(),e);
+ }
+ }
+ }
+
+ /**
+ * signal any scan cycle listeners that a scan has started
+ */
+ private void reportScanStart(int cycle)
+ {
+ for (Listener listener : _listeners)
+ {
+ try
+ {
+ if (listener instanceof ScanCycleListener)
+ {
+ ((ScanCycleListener)listener).scanStarted(cycle);
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn(listener + " failed on scan start for cycle " + cycle, e);
+ }
+ }
+ }
+
+ /**
+ * sign
+ */
+ private void reportScanEnd(int cycle)
+ {
+ for (Listener listener : _listeners)
+ {
+ try
+ {
+ if (listener instanceof ScanCycleListener)
+ {
+ ((ScanCycleListener)listener).scanEnded(cycle);
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn(listener + " failed on scan end for cycle " + cycle, e);
+ }
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.NonBlockingThread;
+
+
+/* ------------------------------------------------------------ */
+/** Provides a reusable BlockingCallback.
+ * A typical usage pattern is:
+ * <pre>
+ * void someBlockingCall(Object... args) throws IOException
+ * {
+ * try(Blocker blocker=sharedBlockingCallback.acquire())
+ * {
+ * someAsyncCall(args,blocker);
+ * blocker.block();
+ * }
+ * }
+ * </pre>
+ */
+public class SharedBlockingCallback
+{
+ private static final Logger LOG = Log.getLogger(SharedBlockingCallback.class);
+
+
+ private static Throwable IDLE = new Throwable()
+ {
+ @Override
+ public String toString()
+ {
+ return "IDLE";
+ }
+ };
+
+ private static Throwable SUCCEEDED = new Throwable()
+ {
+ @Override
+ public String toString()
+ {
+ return "SUCCEEDED";
+ }
+ };
+
+ private static Throwable FAILED = new Throwable()
+ {
+ @Override
+ public String toString()
+ {
+ return "FAILED";
+ }
+ };
+
+ final Blocker _blocker;
+
+ public SharedBlockingCallback()
+ {
+ this(new Blocker());
+ }
+
+ protected SharedBlockingCallback(Blocker blocker)
+ {
+ _blocker=blocker;
+ }
+
+ public Blocker acquire() throws IOException
+ {
+ _blocker._lock.lock();
+ try
+ {
+ while (_blocker._state != IDLE)
+ _blocker._idle.await();
+ _blocker._state = null;
+ }
+ catch (final InterruptedException e)
+ {
+ throw new InterruptedIOException()
+ {
+ {
+ initCause(e);
+ }
+ };
+ }
+ finally
+ {
+ _blocker._lock.unlock();
+ }
+ return _blocker;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** A Closeable Callback.
+ * Uses the auto close mechanism to check block has been called OK.
+ */
+ public static class Blocker implements Callback, Closeable
+ {
+ final ReentrantLock _lock = new ReentrantLock();
+ final Condition _idle = _lock.newCondition();
+ final Condition _complete = _lock.newCondition();
+ Throwable _state = IDLE;
+
+ public Blocker()
+ {
+ }
+
+ @Override
+ public void succeeded()
+ {
+ _lock.lock();
+ try
+ {
+ if (_state == null)
+ {
+ _state = SUCCEEDED;
+ _complete.signalAll();
+ }
+ else if (_state == IDLE)
+ throw new IllegalStateException("IDLE");
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ @Override
+ public void failed(Throwable cause)
+ {
+ _lock.lock();
+ try
+ {
+ if (_state == null)
+ {
+ // TODO remove when feedback received on 435322
+ if (cause==null)
+ LOG.warn("null failed cause (please report stack trace) ",new Throwable());
+ _state = cause==null?FAILED:cause;
+ _complete.signalAll();
+ }
+ else if (_state == IDLE)
+ throw new IllegalStateException("IDLE");
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ /**
+ * Block until the Callback has succeeded or failed and after the return leave in the state to allow reuse. This is useful for code that wants to
+ * repeatable use a FutureCallback to convert an asynchronous API to a blocking API.
+ *
+ * @throws IOException
+ * if exception was caught during blocking, or callback was cancelled
+ */
+ public void block() throws IOException
+ {
+ if (NonBlockingThread.isNonBlockingThread())
+ LOG.warn("Blocking a NonBlockingThread: ",new Throwable());
+
+ _lock.lock();
+ try
+ {
+ while (_state == null)
+ {
+ // TODO remove this debug timout!
+ // This is here to help debug 435322,
+ if (!_complete.await(10,TimeUnit.MINUTES))
+ {
+ IOException x = new IOException("DEBUG timeout");
+ LOG.warn("Blocked too long (please report!!!) "+this, x);
+ _state=x;
+ }
+ }
+
+ if (_state == SUCCEEDED)
+ return;
+ if (_state == IDLE)
+ throw new IllegalStateException("IDLE");
+ if (_state instanceof IOException)
+ throw (IOException)_state;
+ if (_state instanceof CancellationException)
+ throw (CancellationException)_state;
+ if (_state instanceof RuntimeException)
+ throw (RuntimeException)_state;
+ if (_state instanceof Error)
+ throw (Error)_state;
+ throw new IOException(_state);
+ }
+ catch (final InterruptedException e)
+ {
+ throw new InterruptedIOException()
+ {
+ {
+ initCause(e);
+ }
+ };
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ /**
+ * Check the Callback has succeeded or failed and after the return leave in the state to allow reuse.
+ *
+ * @throws IOException
+ * if exception was caught during blocking, or callback was cancelled
+ */
+ @Override
+ public void close() throws IOException
+ {
+ _lock.lock();
+ try
+ {
+ if (_state == IDLE)
+ throw new IllegalStateException("IDLE");
+ if (_state == null)
+ LOG.debug("Blocker not complete",new Throwable());
+ }
+ finally
+ {
+ _state = IDLE;
+ _idle.signalAll();
+ _lock.unlock();
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ _lock.lock();
+ try
+ {
+ return String.format("%s@%x{%s}",SharedBlockingCallback.class.getSimpleName(),hashCode(),_state);
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.UnresolvedAddressException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * Creates asynchronously {@link SocketAddress} instances, returning them through a {@link Promise},
+ * in order to avoid blocking on DNS lookup.
+ * <p />
+ * {@link InetSocketAddress#InetSocketAddress(String, int)} attempts to perform a DNS resolution of
+ * the host name, and this may block for several seconds.
+ * This class creates the {@link InetSocketAddress} in a separate thread and provides the result
+ * through a {@link Promise}, with the possibility to specify a timeout for the operation.
+ * <p />
+ * Example usage:
+ * <pre>
+ * SocketAddressResolver resolver = new SocketAddressResolver(executor, scheduler);
+ * resolver.resolve("www.google.com", 80, new Promise<SocketAddress>()
+ * {
+ * public void succeeded(SocketAddress result)
+ * {
+ * // The address was resolved
+ * }
+ *
+ * public void failed(Throwable failure)
+ * {
+ * // The address resolution failed
+ * }
+ * });
+ * </pre>
+ */
+public class SocketAddressResolver
+{
+ private static final Logger LOG = Log.getLogger(SocketAddressResolver.class);
+
+ private final Executor executor;
+ private final Scheduler scheduler;
+ private final long timeout;
+
+ /**
+ * Creates a new instance with the given executor (to perform DNS resolution in a separate thread),
+ * the given scheduler (to cancel the operation if it takes too long) and the given timeout, in milliseconds.
+ *
+ * @param executor the thread pool to use to perform DNS resolution in pooled threads
+ * @param scheduler the scheduler to schedule tasks to cancel DNS resolution if it takes too long
+ * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
+ */
+ public SocketAddressResolver(Executor executor, Scheduler scheduler, long timeout)
+ {
+ this.executor = executor;
+ this.scheduler = scheduler;
+ this.timeout = timeout;
+ }
+
+ public Executor getExecutor()
+ {
+ return executor;
+ }
+
+ public Scheduler getScheduler()
+ {
+ return scheduler;
+ }
+
+ public long getTimeout()
+ {
+ return timeout;
+ }
+
+ /**
+ * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
+ * with the default timeout.
+ *
+ * @param host the host to resolve
+ * @param port the port of the resulting socket address
+ * @param promise the callback invoked when the resolution succeeds or fails
+ * @see #resolve(String, int, long, Promise)
+ */
+ public void resolve(String host, int port, Promise<SocketAddress> promise)
+ {
+ resolve(host, port, timeout, promise);
+ }
+
+ /**
+ * Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
+ * with the given timeout.
+ *
+ * @param host the host to resolve
+ * @param port the port of the resulting socket address
+ * @param timeout the timeout, in milliseconds, for the DNS resolution to complete
+ * @param promise the callback invoked when the resolution succeeds or fails
+ */
+ protected void resolve(final String host, final int port, final long timeout, final Promise<SocketAddress> promise)
+ {
+ executor.execute(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ Scheduler.Task task = null;
+ final AtomicBoolean complete = new AtomicBoolean();
+ if (timeout > 0)
+ {
+ final Thread thread = Thread.currentThread();
+ task = scheduler.schedule(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ if (complete.compareAndSet(false, true))
+ {
+ promise.failed(new TimeoutException());
+ thread.interrupt();
+ }
+ }
+ }, timeout, TimeUnit.MILLISECONDS);
+ }
+
+ try
+ {
+ long start = System.nanoTime();
+ InetSocketAddress result = new InetSocketAddress(host, port);
+ long elapsed = System.nanoTime() - start;
+ LOG.debug("Resolved {} in {} ms", host, TimeUnit.NANOSECONDS.toMillis(elapsed));
+ if (complete.compareAndSet(false, true))
+ {
+ if (result.isUnresolved())
+ promise.failed(new UnresolvedAddressException());
+ else
+ promise.succeeded(result);
+ }
+ }
+ catch (Throwable x)
+ {
+ if (complete.compareAndSet(false, true))
+ promise.failed(x);
+ }
+ finally
+ {
+ if (task != null)
+ task.cancel();
+ // Reset the interrupted status before releasing the thread to the pool
+ Thread.interrupted();
+ }
+ }
+ });
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/** Fast String Utilities.
+ *
+ * These string utilities provide both convenience methods and
+ * performance improvements over most standard library versions. The
+ * main aim of the optimizations is to avoid object creation unless
+ * absolutely required.
+ *
+ *
+ */
+public class StringUtil
+{
+ private static final Logger LOG = Log.getLogger(StringUtil.class);
+
+
+ private final static Trie<String> CHARSETS= new ArrayTrie<>(256);
+
+ public static final String ALL_INTERFACES="0.0.0.0";
+ public static final String CRLF="\015\012";
+ public static final String __LINE_SEPARATOR=
+ System.getProperty("line.separator","\n");
+
+ public static final String __ISO_8859_1="ISO-8859-1";
+ public final static String __UTF8="UTF-8";
+ public final static String __UTF16="UTF-16";
+
+ /**
+ * @deprecated Use {@link StandardCharsets#UTF_8}
+ */
+ @Deprecated
+ public final static Charset __UTF8_CHARSET=StandardCharsets.UTF_8;
+ /**
+ * @deprecated Use {@link StandardCharsets#ISO_8859_1}
+ */
+ @Deprecated
+ public final static Charset __ISO_8859_1_CHARSET=StandardCharsets.ISO_8859_1;
+ /**
+ * @deprecated Use {@link StandardCharsets#UTF_16}
+ */
+ @Deprecated
+ public final static Charset __UTF16_CHARSET=StandardCharsets.UTF_16;
+ /**
+ * @deprecated Use {@link StandardCharsets#US_ASCII}
+ */
+ @Deprecated
+ public final static Charset __US_ASCII_CHARSET=StandardCharsets.US_ASCII;
+
+ static
+ {
+ CHARSETS.put("UTF-8",__UTF8);
+ CHARSETS.put("UTF8",__UTF8);
+ CHARSETS.put("UTF-16",__UTF16);
+ CHARSETS.put("UTF16",__UTF16);
+ CHARSETS.put("ISO-8859-1",__ISO_8859_1);
+ CHARSETS.put("ISO_8859_1",__ISO_8859_1);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert alternate charset names (eg utf8) to normalized
+ * name (eg UTF-8).
+ */
+ public static String normalizeCharset(String s)
+ {
+ String n=CHARSETS.get(s);
+ return (n==null)?s:n;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert alternate charset names (eg utf8) to normalized
+ * name (eg UTF-8).
+ */
+ public static String normalizeCharset(String s,int offset,int length)
+ {
+ String n=CHARSETS.get(s,offset,length);
+ return (n==null)?s.substring(offset,offset+length):n;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static final char[] lowercases = {
+ '\000','\001','\002','\003','\004','\005','\006','\007',
+ '\010','\011','\012','\013','\014','\015','\016','\017',
+ '\020','\021','\022','\023','\024','\025','\026','\027',
+ '\030','\031','\032','\033','\034','\035','\036','\037',
+ '\040','\041','\042','\043','\044','\045','\046','\047',
+ '\050','\051','\052','\053','\054','\055','\056','\057',
+ '\060','\061','\062','\063','\064','\065','\066','\067',
+ '\070','\071','\072','\073','\074','\075','\076','\077',
+ '\100','\141','\142','\143','\144','\145','\146','\147',
+ '\150','\151','\152','\153','\154','\155','\156','\157',
+ '\160','\161','\162','\163','\164','\165','\166','\167',
+ '\170','\171','\172','\133','\134','\135','\136','\137',
+ '\140','\141','\142','\143','\144','\145','\146','\147',
+ '\150','\151','\152','\153','\154','\155','\156','\157',
+ '\160','\161','\162','\163','\164','\165','\166','\167',
+ '\170','\171','\172','\173','\174','\175','\176','\177' };
+
+ /* ------------------------------------------------------------ */
+ /**
+ * fast lower case conversion. Only works on ascii (not unicode)
+ * @param s the string to convert
+ * @return a lower case version of s
+ */
+ public static String asciiToLowerCase(String s)
+ {
+ char[] c = null;
+ int i=s.length();
+
+ // look for first conversion
+ while (i-->0)
+ {
+ char c1=s.charAt(i);
+ if (c1<=127)
+ {
+ char c2=lowercases[c1];
+ if (c1!=c2)
+ {
+ c=s.toCharArray();
+ c[i]=c2;
+ break;
+ }
+ }
+ }
+
+ while (i-->0)
+ {
+ if(c[i]<=127)
+ c[i] = lowercases[c[i]];
+ }
+
+ return c==null?s:new String(c);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static boolean startsWithIgnoreCase(String s,String w)
+ {
+ if (w==null)
+ return true;
+
+ if (s==null || s.length()<w.length())
+ return false;
+
+ for (int i=0;i<w.length();i++)
+ {
+ char c1=s.charAt(i);
+ char c2=w.charAt(i);
+ if (c1!=c2)
+ {
+ if (c1<=127)
+ c1=lowercases[c1];
+ if (c2<=127)
+ c2=lowercases[c2];
+ if (c1!=c2)
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static boolean endsWithIgnoreCase(String s,String w)
+ {
+ if (w==null)
+ return true;
+
+ if (s==null)
+ return false;
+
+ int sl=s.length();
+ int wl=w.length();
+
+ if (sl<wl)
+ return false;
+
+ for (int i=wl;i-->0;)
+ {
+ char c1=s.charAt(--sl);
+ char c2=w.charAt(i);
+ if (c1!=c2)
+ {
+ if (c1<=127)
+ c1=lowercases[c1];
+ if (c2<=127)
+ c2=lowercases[c2];
+ if (c1!=c2)
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * returns the next index of a character from the chars string
+ */
+ public static int indexFrom(String s,String chars)
+ {
+ for (int i=0;i<s.length();i++)
+ if (chars.indexOf(s.charAt(i))>=0)
+ return i;
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * replace substrings within string.
+ */
+ public static String replace(String s, String sub, String with)
+ {
+ int c=0;
+ int i=s.indexOf(sub,c);
+ if (i == -1)
+ return s;
+
+ StringBuilder buf = new StringBuilder(s.length()+with.length());
+
+ do
+ {
+ buf.append(s.substring(c,i));
+ buf.append(with);
+ c=i+sub.length();
+ } while ((i=s.indexOf(sub,c))!=-1);
+
+ if (c<s.length())
+ buf.append(s.substring(c,s.length()));
+
+ return buf.toString();
+
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Remove single or double quotes.
+ */
+ public static String unquote(String s)
+ {
+ return QuotedStringTokenizer.unquote(s);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Append substring to StringBuilder
+ * @param buf StringBuilder to append to
+ * @param s String to append from
+ * @param offset The offset of the substring
+ * @param length The length of the substring
+ */
+ public static void append(StringBuilder buf,
+ String s,
+ int offset,
+ int length)
+ {
+ synchronized(buf)
+ {
+ int end=offset+length;
+ for (int i=offset; i<end;i++)
+ {
+ if (i>=s.length())
+ break;
+ buf.append(s.charAt(i));
+ }
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * append hex digit
+ *
+ */
+ public static void append(StringBuilder buf,byte b,int base)
+ {
+ int bi=0xff&b;
+ int c='0'+(bi/base)%base;
+ if (c>'9')
+ c= 'a'+(c-'0'-10);
+ buf.append((char)c);
+ c='0'+bi%base;
+ if (c>'9')
+ c= 'a'+(c-'0'-10);
+ buf.append((char)c);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void append2digits(StringBuffer buf,int i)
+ {
+ if (i<100)
+ {
+ buf.append((char)(i/10+'0'));
+ buf.append((char)(i%10+'0'));
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void append2digits(StringBuilder buf,int i)
+ {
+ if (i<100)
+ {
+ buf.append((char)(i/10+'0'));
+ buf.append((char)(i%10+'0'));
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Return a non null string.
+ * @param s String
+ * @return The string passed in or empty string if it is null.
+ */
+ public static String nonNull(String s)
+ {
+ if (s==null)
+ return "";
+ return s;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static boolean equals(String s,char[] buf, int offset, int length)
+ {
+ if (s.length()!=length)
+ return false;
+ for (int i=0;i<length;i++)
+ if (buf[offset+i]!=s.charAt(i))
+ return false;
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String toUTF8String(byte[] b,int offset,int length)
+ {
+ return new String(b,offset,length,StandardCharsets.UTF_8);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String toString(byte[] b,int offset,int length,String charset)
+ {
+ try
+ {
+ return new String(b,offset,length,charset);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Test if a string is null or only has whitespace characters in it.
+ * <p>
+ * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
+ *
+ * <pre>
+ * isBlank(null) == true
+ * isBlank("") == true
+ * isBlank("\r\n") == true
+ * isBlank("\t") == true
+ * isBlank(" ") == true
+ * isBlank("a") == false
+ * isBlank(".") == false
+ * isBlank(";\n") == false
+ * </pre>
+ *
+ * @param str
+ * the string to test.
+ * @return true if string is null or only whitespace characters, false if non-whitespace characters encountered.
+ */
+ public static boolean isBlank(String str)
+ {
+ if (str == null)
+ {
+ return true;
+ }
+ int len = str.length();
+ for (int i = 0; i < len; i++)
+ {
+ if (!Character.isWhitespace(str.codePointAt(i)))
+ {
+ // found a non-whitespace, we can stop searching now
+ return false;
+ }
+ }
+ // only whitespace
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Test if a string is not null and contains at least 1 non-whitespace characters in it.
+ * <p>
+ * Note: uses codepoint version of {@link Character#isWhitespace(int)} to support Unicode better.
+ *
+ * <pre>
+ * isNotBlank(null) == false
+ * isNotBlank("") == false
+ * isNotBlank("\r\n") == false
+ * isNotBlank("\t") == false
+ * isNotBlank(" ") == false
+ * isNotBlank("a") == true
+ * isNotBlank(".") == true
+ * isNotBlank(";\n") == true
+ * </pre>
+ *
+ * @param str
+ * the string to test.
+ * @return true if string is not null and has at least 1 non-whitespace character, false if null or all-whitespace characters.
+ */
+ public static boolean isNotBlank(String str)
+ {
+ if (str == null)
+ {
+ return false;
+ }
+ int len = str.length();
+ for (int i = 0; i < len; i++)
+ {
+ if (!Character.isWhitespace(str.codePointAt(i)))
+ {
+ // found a non-whitespace, we can stop searching now
+ return true;
+ }
+ }
+ // only whitespace
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static boolean isUTF8(String charset)
+ {
+ return __UTF8.equalsIgnoreCase(charset)||__UTF8.equalsIgnoreCase(normalizeCharset(charset));
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static String printable(String name)
+ {
+ if (name==null)
+ return null;
+ StringBuilder buf = new StringBuilder(name.length());
+ for (int i=0;i<name.length();i++)
+ {
+ char c=name.charAt(i);
+ if (!Character.isISOControl(c))
+ buf.append(c);
+ }
+ return buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String printable(byte[] b)
+ {
+ StringBuilder buf = new StringBuilder();
+ for (int i=0;i<b.length;i++)
+ {
+ char c=(char)b[i];
+ if (Character.isWhitespace(c)|| c>' ' && c<0x7f)
+ buf.append(c);
+ else
+ {
+ buf.append("0x");
+ TypeUtil.toHex(b[i],buf);
+ }
+ }
+ return buf.toString();
+ }
+
+ public static byte[] getBytes(String s)
+ {
+ return s.getBytes(StandardCharsets.ISO_8859_1);
+ }
+
+ public static byte[] getUtf8Bytes(String s)
+ {
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+
+ public static byte[] getBytes(String s,String charset)
+ {
+ try
+ {
+ return s.getBytes(charset);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ return s.getBytes();
+ }
+ }
+
+
+
+ /**
+ * Converts a binary SID to a string SID
+ *
+ * http://en.wikipedia.org/wiki/Security_Identifier
+ *
+ * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
+ */
+ public static String sidBytesToString(byte[] sidBytes)
+ {
+ StringBuilder sidString = new StringBuilder();
+
+ // Identify this as a SID
+ sidString.append("S-");
+
+ // Add SID revision level (expect 1 but may change someday)
+ sidString.append(Byte.toString(sidBytes[0])).append('-');
+
+ StringBuilder tmpBuilder = new StringBuilder();
+
+ // crunch the six bytes of issuing authority value
+ for (int i = 2; i <= 7; ++i)
+ {
+ tmpBuilder.append(Integer.toHexString(sidBytes[i] & 0xFF));
+ }
+
+ sidString.append(Long.parseLong(tmpBuilder.toString(), 16)); // '-' is in the subauth loop
+
+ // the number of subAuthorities we need to attach
+ int subAuthorityCount = sidBytes[1];
+
+ // attach each of the subAuthorities
+ for (int i = 0; i < subAuthorityCount; ++i)
+ {
+ int offset = i * 4;
+ tmpBuilder.setLength(0);
+ // these need to be zero padded hex and little endian
+ tmpBuilder.append(String.format("%02X%02X%02X%02X",
+ (sidBytes[11 + offset] & 0xFF),
+ (sidBytes[10 + offset] & 0xFF),
+ (sidBytes[9 + offset] & 0xFF),
+ (sidBytes[8 + offset] & 0xFF)));
+ sidString.append('-').append(Long.parseLong(tmpBuilder.toString(), 16));
+ }
+
+ return sidString.toString();
+ }
+
+ /**
+ * Converts a string SID to a binary SID
+ *
+ * http://en.wikipedia.org/wiki/Security_Identifier
+ *
+ * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
+ */
+ public static byte[] sidStringToBytes( String sidString )
+ {
+ String[] sidTokens = sidString.split("-");
+
+ int subAuthorityCount = sidTokens.length - 3; // S-Rev-IdAuth-
+
+ int byteCount = 0;
+ byte[] sidBytes = new byte[1 + 1 + 6 + (4 * subAuthorityCount)];
+
+ // the revision byte
+ sidBytes[byteCount++] = (byte)Integer.parseInt(sidTokens[1]);
+
+ // the # of sub authorities byte
+ sidBytes[byteCount++] = (byte)subAuthorityCount;
+
+ // the certAuthority
+ String hexStr = Long.toHexString(Long.parseLong(sidTokens[2]));
+
+ while( hexStr.length() < 12) // pad to 12 characters
+ {
+ hexStr = "0" + hexStr;
+ }
+
+ // place the certAuthority 6 bytes
+ for ( int i = 0 ; i < hexStr.length(); i = i + 2)
+ {
+ sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(i, i + 2),16);
+ }
+
+
+ for ( int i = 3; i < sidTokens.length ; ++i)
+ {
+ hexStr = Long.toHexString(Long.parseLong(sidTokens[i]));
+
+ while( hexStr.length() < 8) // pad to 8 characters
+ {
+ hexStr = "0" + hexStr;
+ }
+
+ // place the inverted sub authorities, 4 bytes each
+ for ( int j = hexStr.length(); j > 0; j = j - 2)
+ {
+ sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(j-2, j),16);
+ }
+ }
+
+ return sidBytes;
+ }
+
+
+ /**
+ * Convert String to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+ *
+ * @param string
+ * A String containing an integer.
+ * @return an int
+ */
+ public static int toInt(String string)
+ {
+ int val = 0;
+ boolean started = false;
+ boolean minus = false;
+
+ for (int i = 0; i < string.length(); i++)
+ {
+ char b = string.charAt(i);
+ if (b <= ' ')
+ {
+ if (started)
+ break;
+ }
+ else if (b >= '0' && b <= '9')
+ {
+ val = val * 10 + (b - '0');
+ started = true;
+ }
+ else if (b == '-' && !started)
+ {
+ minus = true;
+ }
+ else
+ break;
+ }
+
+ if (started)
+ return minus?(-val):val;
+ throw new NumberFormatException(string);
+ }
+
+ /**
+ * Convert String to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
+ *
+ * @param string
+ * A String containing an integer.
+ * @return an int
+ */
+ public static long toLong(String string)
+ {
+ long val = 0;
+ boolean started = false;
+ boolean minus = false;
+
+ for (int i = 0; i < string.length(); i++)
+ {
+ char b = string.charAt(i);
+ if (b <= ' ')
+ {
+ if (started)
+ break;
+ }
+ else if (b >= '0' && b <= '9')
+ {
+ val = val * 10L + (b - '0');
+ started = true;
+ }
+ else if (b == '-' && !started)
+ {
+ minus = true;
+ }
+ else
+ break;
+ }
+
+ if (started)
+ return minus?(-val):val;
+ throw new NumberFormatException(string);
+ }
+
+ /**
+ * Truncate a string to a max size.
+ *
+ * @param str the string to possibly truncate
+ * @param maxSize the maximum size of the string
+ * @return the truncated string. if <code>str</code> param is null, then the returned string will also be null.
+ */
+ public static String truncate(String str, int maxSize)
+ {
+ if (str == null)
+ {
+ return null;
+ }
+
+ if (str.length() <= maxSize)
+ {
+ return str;
+ }
+
+ return str.substring(0,maxSize);
+ }
+
+ public static String[] arrayFromString(String s)
+ {
+ if (s==null)
+ return new String[]{};
+
+ if (!s.startsWith("[") || !s.endsWith("]"))
+ throw new IllegalArgumentException();
+ if (s.length()==2)
+ return new String[]{};
+
+ return s.substring(1,s.length()-1).split(" *, *");
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure using a tree
+ * <p>This implementation is always case insensitive and is optimal for
+ * a variable number of fixed strings with few special characters.
+ * </p>
+ * @param <V>
+ */
+public class TreeTrie<V> extends AbstractTrie<V>
+{
+ private static final int[] __lookup =
+ { // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ /*0*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /*1*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 30,
+ /*2*/31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, 27, -1, -1,
+ /*3*/-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 28, 29, -1, -1, -1, -1,
+ /*4*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ /*5*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ /*6*/-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ /*7*/15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ };
+ private static final int INDEX = 32;
+ private final TreeTrie<V>[] _nextIndex;
+ private final List<TreeTrie<V>> _nextOther=new ArrayList<>();
+ private final char _c;
+ private String _key;
+ private V _value;
+
+ public TreeTrie()
+ {
+ super(true);
+ _nextIndex = new TreeTrie[INDEX];
+ _c=0;
+ }
+
+ private TreeTrie(char c)
+ {
+ super(true);
+ _nextIndex = new TreeTrie[INDEX];
+ this._c=c;
+ }
+
+ @Override
+ public boolean put(String s, V v)
+ {
+ TreeTrie<V> t = this;
+ int limit = s.length();
+ for(int k=0; k < limit; k++)
+ {
+ char c=s.charAt(k);
+
+ int index=c>=0&&c<0x7f?__lookup[c]:-1;
+ if (index>=0)
+ {
+ if (t._nextIndex[index] == null)
+ t._nextIndex[index] = new TreeTrie<V>(c);
+ t = t._nextIndex[index];
+ }
+ else
+ {
+ TreeTrie<V> n=null;
+ for (int i=t._nextOther.size();i-->0;)
+ {
+ n=t._nextOther.get(i);
+ if (n._c==c)
+ break;
+ n=null;
+ }
+ if (n==null)
+ {
+ n=new TreeTrie<V>(c);
+ t._nextOther.add(n);
+ }
+ t=n;
+ }
+ }
+ t._key=v==null?null:s;
+ t._value = v;
+ return true;
+ }
+
+ @Override
+ public V get(String s,int offset, int len)
+ {
+ TreeTrie<V> t = this;
+ for(int i=0; i < len; i++)
+ {
+ char c=s.charAt(offset+i);
+ int index=c>=0&&c<0x7f?__lookup[c]:-1;
+ if (index>=0)
+ {
+ if (t._nextIndex[index] == null)
+ return null;
+ t = t._nextIndex[index];
+ }
+ else
+ {
+ TreeTrie<V> n=null;
+ for (int j=t._nextOther.size();j-->0;)
+ {
+ n=t._nextOther.get(j);
+ if (n._c==c)
+ break;
+ n=null;
+ }
+ if (n==null)
+ return null;
+ t=n;
+ }
+ }
+ return t._value;
+ }
+
+ @Override
+ public V get(ByteBuffer b,int offset,int len)
+ {
+ TreeTrie<V> t = this;
+ for(int i=0; i < len; i++)
+ {
+ byte c=b.get(offset+i);
+ int index=c>=0&&c<0x7f?__lookup[c]:-1;
+ if (index>=0)
+ {
+ if (t._nextIndex[index] == null)
+ return null;
+ t = t._nextIndex[index];
+ }
+ else
+ {
+ TreeTrie<V> n=null;
+ for (int j=t._nextOther.size();j-->0;)
+ {
+ n=t._nextOther.get(j);
+ if (n._c==c)
+ break;
+ n=null;
+ }
+ if (n==null)
+ return null;
+ t=n;
+ }
+ }
+ return t._value;
+ }
+
+ @Override
+ public V getBest(byte[] b,int offset,int len)
+ {
+ TreeTrie<V> t = this;
+ for(int i=0; i < len; i++)
+ {
+ byte c=b[offset+i];
+ int index=c>=0&&c<0x7f?__lookup[c]:-1;
+ if (index>=0)
+ {
+ if (t._nextIndex[index] == null)
+ break;
+ t = t._nextIndex[index];
+ }
+ else
+ {
+ TreeTrie<V> n=null;
+ for (int j=t._nextOther.size();j-->0;)
+ {
+ n=t._nextOther.get(j);
+ if (n._c==c)
+ break;
+ n=null;
+ }
+ if (n==null)
+ break;
+ t=n;
+ }
+
+ // Is the next Trie is a match
+ if (t._key!=null)
+ {
+ // Recurse so we can remember this possibility
+ V best=t.getBest(b,offset+i+1,len-i-1);
+ if (best!=null)
+ return best;
+ break;
+ }
+ }
+ return t._value;
+ }
+
+ @Override
+ public V getBest(String s, int offset, int len)
+ {
+ // TODO inefficient
+ byte[] b=s.substring(offset,offset+len).getBytes(StandardCharsets.ISO_8859_1);
+ return getBest(b,0,b.length);
+ }
+
+ @Override
+ public V getBest(ByteBuffer b,int offset,int len)
+ {
+ if (b.hasArray())
+ return getBest(b.array(),b.arrayOffset()+b.position()+offset,len);
+ return getBestByteBuffer(b,offset,len);
+ }
+
+ private V getBestByteBuffer(ByteBuffer b,int offset,int len)
+ {
+ TreeTrie<V> t = this;
+ int pos=b.position()+offset;
+ for(int i=0; i < len; i++)
+ {
+ byte c=b.get(pos++);
+ int index=c>=0&&c<0x7f?__lookup[c]:-1;
+ if (index>=0)
+ {
+ if (t._nextIndex[index] == null)
+ break;
+ t = t._nextIndex[index];
+ }
+ else
+ {
+ TreeTrie<V> n=null;
+ for (int j=t._nextOther.size();j-->0;)
+ {
+ n=t._nextOther.get(j);
+ if (n._c==c)
+ break;
+ n=null;
+ }
+ if (n==null)
+ break;
+ t=n;
+ }
+
+ // Is the next Trie is a match
+ if (t._key!=null)
+ {
+ // Recurse so we can remember this possibility
+ V best=t.getBest(b,offset+i+1,len-i-1);
+ if (best!=null)
+ return best;
+ break;
+ }
+ }
+ return t._value;
+ }
+
+
+ @Override
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder();
+ toString(buf,this);
+
+ if (buf.length()==0)
+ return "{}";
+
+ buf.setCharAt(0,'{');
+ buf.append('}');
+ return buf.toString();
+ }
+
+ private static <V> void toString(Appendable out, TreeTrie<V> t)
+ {
+ if (t != null)
+ {
+ if (t._value!=null)
+ {
+ try
+ {
+ out.append(',');
+ out.append(t._key);
+ out.append('=');
+ out.append(t._value.toString());
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ for(int i=0; i < INDEX; i++)
+ {
+ if (t._nextIndex[i] != null)
+ toString(out,t._nextIndex[i]);
+ }
+ for (int i=t._nextOther.size();i-->0;)
+ toString(out,t._nextOther.get(i));
+ }
+ }
+
+ @Override
+ public Set<String> keySet()
+ {
+ Set<String> keys = new HashSet<>();
+ keySet(keys,this);
+ return keys;
+ }
+
+ private static <V> void keySet(Set<String> set, TreeTrie<V> t)
+ {
+ if (t != null)
+ {
+ if (t._key!=null)
+ set.add(t._key);
+
+ for(int i=0; i < INDEX; i++)
+ {
+ if (t._nextIndex[i] != null)
+ keySet(set,t._nextIndex[i]);
+ }
+ for (int i=t._nextOther.size();i-->0;)
+ keySet(set,t._nextOther.get(i));
+ }
+ }
+
+ @Override
+ public boolean isFull()
+ {
+ return false;
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.nio.ByteBuffer;
+import java.util.Set;
+
+
+/* ------------------------------------------------------------ */
+/** A Trie String lookup data structure.
+ * @param <V>
+ */
+public interface Trie<V>
+{
+ /* ------------------------------------------------------------ */
+ /** Put and entry into the Trie
+ * @param s The key for the entry
+ * @param v The value of the entry
+ * @return True if the Trie had capacity to add the field.
+ */
+ public boolean put(String s, V v);
+
+ /* ------------------------------------------------------------ */
+ /** Put a value as both a key and a value.
+ * @param v The value and key
+ * @return True if the Trie had capacity to add the field.
+ */
+ public boolean put(V v);
+
+ /* ------------------------------------------------------------ */
+ public V remove(String s);
+
+ /* ------------------------------------------------------------ */
+ /** Get and exact match from a String key
+ * @param s The key
+ */
+ public V get(String s);
+
+ /* ------------------------------------------------------------ */
+ /** Get and exact match from a String key
+ * @param s The key
+ * @param offset The offset within the string of the key
+ * @param len the length of the key
+ */
+ public V get(String s,int offset,int len);
+
+ /* ------------------------------------------------------------ */
+ /** Get and exact match from a segment of a ByteBuufer as key
+ * @param b The buffer
+ * @return The value or null if not found
+ */
+ public V get(ByteBuffer b);
+
+ /* ------------------------------------------------------------ */
+ /** Get and exact match from a segment of a ByteBuufer as key
+ * @param b The buffer
+ * @param offset The offset within the buffer of the key
+ * @param len the length of the key
+ * @return The value or null if not found
+ */
+ public V get(ByteBuffer b,int offset,int len);
+
+ /* ------------------------------------------------------------ */
+ /** Get the best match from key in a String.
+ * @param s The string
+ * @return The value or null if not found
+ */
+ public V getBest(String s);
+
+ /* ------------------------------------------------------------ */
+ /** Get the best match from key in a String.
+ * @param s The string
+ * @param offset The offset within the string of the key
+ * @param len the length of the key
+ * @return The value or null if not found
+ */
+ public V getBest(String s,int offset,int len);
+
+ /* ------------------------------------------------------------ */
+ /** Get the best match from key in a byte array.
+ * The key is assumed to by ISO_8859_1 characters.
+ * @param b The buffer
+ * @param offset The offset within the array of the key
+ * @param len the length of the key
+ * @return The value or null if not found
+ */
+ public V getBest(byte[] b,int offset,int len);
+
+ /* ------------------------------------------------------------ */
+ /** Get the best match from key in a byte buffer.
+ * The key is assumed to by ISO_8859_1 characters.
+ * @param b The buffer
+ * @param offset The offset within the buffer of the key
+ * @param len the length of the key
+ * @return The value or null if not found
+ */
+ public V getBest(ByteBuffer b,int offset,int len);
+
+ /* ------------------------------------------------------------ */
+ public Set<String> keySet();
+
+ /* ------------------------------------------------------------ */
+ public boolean isFull();
+
+ /* ------------------------------------------------------------ */
+ public boolean isCaseInsensitive();
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * TYPE Utilities.
+ * Provides various static utiltiy methods for manipulating types and their
+ * string representations.
+ *
+ * @since Jetty 4.1
+ */
+public class TypeUtil
+{
+ private static final Logger LOG = Log.getLogger(TypeUtil.class);
+ public static final Class<?>[] NO_ARGS = new Class[]{};
+ public static final int CR = '\015';
+ public static final int LF = '\012';
+
+ /* ------------------------------------------------------------ */
+ private static final HashMap<String, Class<?>> name2Class=new HashMap<>();
+ static
+ {
+ name2Class.put("boolean",java.lang.Boolean.TYPE);
+ name2Class.put("byte",java.lang.Byte.TYPE);
+ name2Class.put("char",java.lang.Character.TYPE);
+ name2Class.put("double",java.lang.Double.TYPE);
+ name2Class.put("float",java.lang.Float.TYPE);
+ name2Class.put("int",java.lang.Integer.TYPE);
+ name2Class.put("long",java.lang.Long.TYPE);
+ name2Class.put("short",java.lang.Short.TYPE);
+ name2Class.put("void",java.lang.Void.TYPE);
+
+ name2Class.put("java.lang.Boolean.TYPE",java.lang.Boolean.TYPE);
+ name2Class.put("java.lang.Byte.TYPE",java.lang.Byte.TYPE);
+ name2Class.put("java.lang.Character.TYPE",java.lang.Character.TYPE);
+ name2Class.put("java.lang.Double.TYPE",java.lang.Double.TYPE);
+ name2Class.put("java.lang.Float.TYPE",java.lang.Float.TYPE);
+ name2Class.put("java.lang.Integer.TYPE",java.lang.Integer.TYPE);
+ name2Class.put("java.lang.Long.TYPE",java.lang.Long.TYPE);
+ name2Class.put("java.lang.Short.TYPE",java.lang.Short.TYPE);
+ name2Class.put("java.lang.Void.TYPE",java.lang.Void.TYPE);
+
+ name2Class.put("java.lang.Boolean",java.lang.Boolean.class);
+ name2Class.put("java.lang.Byte",java.lang.Byte.class);
+ name2Class.put("java.lang.Character",java.lang.Character.class);
+ name2Class.put("java.lang.Double",java.lang.Double.class);
+ name2Class.put("java.lang.Float",java.lang.Float.class);
+ name2Class.put("java.lang.Integer",java.lang.Integer.class);
+ name2Class.put("java.lang.Long",java.lang.Long.class);
+ name2Class.put("java.lang.Short",java.lang.Short.class);
+
+ name2Class.put("Boolean",java.lang.Boolean.class);
+ name2Class.put("Byte",java.lang.Byte.class);
+ name2Class.put("Character",java.lang.Character.class);
+ name2Class.put("Double",java.lang.Double.class);
+ name2Class.put("Float",java.lang.Float.class);
+ name2Class.put("Integer",java.lang.Integer.class);
+ name2Class.put("Long",java.lang.Long.class);
+ name2Class.put("Short",java.lang.Short.class);
+
+ name2Class.put(null,java.lang.Void.TYPE);
+ name2Class.put("string",java.lang.String.class);
+ name2Class.put("String",java.lang.String.class);
+ name2Class.put("java.lang.String",java.lang.String.class);
+ }
+
+ /* ------------------------------------------------------------ */
+ private static final HashMap<Class<?>, String> class2Name=new HashMap<>();
+ static
+ {
+ class2Name.put(java.lang.Boolean.TYPE,"boolean");
+ class2Name.put(java.lang.Byte.TYPE,"byte");
+ class2Name.put(java.lang.Character.TYPE,"char");
+ class2Name.put(java.lang.Double.TYPE,"double");
+ class2Name.put(java.lang.Float.TYPE,"float");
+ class2Name.put(java.lang.Integer.TYPE,"int");
+ class2Name.put(java.lang.Long.TYPE,"long");
+ class2Name.put(java.lang.Short.TYPE,"short");
+ class2Name.put(java.lang.Void.TYPE,"void");
+
+ class2Name.put(java.lang.Boolean.class,"java.lang.Boolean");
+ class2Name.put(java.lang.Byte.class,"java.lang.Byte");
+ class2Name.put(java.lang.Character.class,"java.lang.Character");
+ class2Name.put(java.lang.Double.class,"java.lang.Double");
+ class2Name.put(java.lang.Float.class,"java.lang.Float");
+ class2Name.put(java.lang.Integer.class,"java.lang.Integer");
+ class2Name.put(java.lang.Long.class,"java.lang.Long");
+ class2Name.put(java.lang.Short.class,"java.lang.Short");
+
+ class2Name.put(null,"void");
+ class2Name.put(java.lang.String.class,"java.lang.String");
+ }
+
+ /* ------------------------------------------------------------ */
+ private static final HashMap<Class<?>, Method> class2Value=new HashMap<>();
+ static
+ {
+ try
+ {
+ Class<?>[] s ={java.lang.String.class};
+
+ class2Value.put(java.lang.Boolean.TYPE,
+ java.lang.Boolean.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Byte.TYPE,
+ java.lang.Byte.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Double.TYPE,
+ java.lang.Double.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Float.TYPE,
+ java.lang.Float.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Integer.TYPE,
+ java.lang.Integer.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Long.TYPE,
+ java.lang.Long.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Short.TYPE,
+ java.lang.Short.class.getMethod("valueOf",s));
+
+ class2Value.put(java.lang.Boolean.class,
+ java.lang.Boolean.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Byte.class,
+ java.lang.Byte.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Double.class,
+ java.lang.Double.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Float.class,
+ java.lang.Float.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Integer.class,
+ java.lang.Integer.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Long.class,
+ java.lang.Long.class.getMethod("valueOf",s));
+ class2Value.put(java.lang.Short.class,
+ java.lang.Short.class.getMethod("valueOf",s));
+ }
+ catch(Exception e)
+ {
+ throw new Error(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Array to List.
+ * <p>
+ * Works like {@link Arrays#asList(Object...)}, but handles null arrays.
+ * @return a list backed by the array.
+ */
+ public static <T> List<T> asList(T[] a)
+ {
+ if (a==null)
+ return Collections.emptyList();
+ return Arrays.asList(a);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Class from a canonical name for a type.
+ * @param name A class or type name.
+ * @return A class , which may be a primitive TYPE field..
+ */
+ public static Class<?> fromName(String name)
+ {
+ return name2Class.get(name);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Canonical name for a type.
+ * @param type A class , which may be a primitive TYPE field.
+ * @return Canonical name.
+ */
+ public static String toName(Class<?> type)
+ {
+ return class2Name.get(type);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert String value to instance.
+ * @param type The class of the instance, which may be a primitive TYPE field.
+ * @param value The value as a string.
+ * @return The value as an Object.
+ */
+ public static Object valueOf(Class<?> type, String value)
+ {
+ try
+ {
+ if (type.equals(java.lang.String.class))
+ return value;
+
+ Method m = class2Value.get(type);
+ if (m!=null)
+ return m.invoke(null, value);
+
+ if (type.equals(java.lang.Character.TYPE) ||
+ type.equals(java.lang.Character.class))
+ return value.charAt(0);
+
+ Constructor<?> c = type.getConstructor(java.lang.String.class);
+ return c.newInstance(value);
+ }
+ catch (NoSuchMethodException | IllegalAccessException | InstantiationException x)
+ {
+ LOG.ignore(x);
+ }
+ catch (InvocationTargetException x)
+ {
+ if (x.getTargetException() instanceof Error)
+ throw (Error)x.getTargetException();
+ LOG.ignore(x);
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert String value to instance.
+ * @param type classname or type (eg int)
+ * @param value The value as a string.
+ * @return The value as an Object.
+ */
+ public static Object valueOf(String type, String value)
+ {
+ return valueOf(fromName(type),value);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Parse an int from a substring.
+ * Negative numbers are not handled.
+ * @param s String
+ * @param offset Offset within string
+ * @param length Length of integer or -1 for remainder of string
+ * @param base base of the integer
+ * @return the parsed integer
+ * @throws NumberFormatException if the string cannot be parsed
+ */
+ public static int parseInt(String s, int offset, int length, int base)
+ throws NumberFormatException
+ {
+ int value=0;
+
+ if (length<0)
+ length=s.length()-offset;
+
+ for (int i=0;i<length;i++)
+ {
+ char c=s.charAt(offset+i);
+
+ int digit=convertHexDigit((int)c);
+ if (digit<0 || digit>=base)
+ throw new NumberFormatException(s.substring(offset,offset+length));
+ value=value*base+digit;
+ }
+ return value;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Parse an int from a byte array of ascii characters.
+ * Negative numbers are not handled.
+ * @param b byte array
+ * @param offset Offset within string
+ * @param length Length of integer or -1 for remainder of string
+ * @param base base of the integer
+ * @return the parsed integer
+ * @throws NumberFormatException if the array cannot be parsed into an integer
+ */
+ public static int parseInt(byte[] b, int offset, int length, int base)
+ throws NumberFormatException
+ {
+ int value=0;
+
+ if (length<0)
+ length=b.length-offset;
+
+ for (int i=0;i<length;i++)
+ {
+ char c=(char)(0xff&b[offset+i]);
+
+ int digit=c-'0';
+ if (digit<0 || digit>=base || digit>=10)
+ {
+ digit=10+c-'A';
+ if (digit<10 || digit>=base)
+ digit=10+c-'a';
+ }
+ if (digit<0 || digit>=base)
+ throw new NumberFormatException(new String(b,offset,length));
+ value=value*base+digit;
+ }
+ return value;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static byte[] parseBytes(String s, int base)
+ {
+ byte[] bytes=new byte[s.length()/2];
+ for (int i=0;i<s.length();i+=2)
+ bytes[i/2]=(byte)TypeUtil.parseInt(s,i,2,base);
+ return bytes;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String toString(byte[] bytes, int base)
+ {
+ StringBuilder buf = new StringBuilder();
+ for (byte b : bytes)
+ {
+ int bi=0xff&b;
+ int c='0'+(bi/base)%base;
+ if (c>'9')
+ c= 'a'+(c-'0'-10);
+ buf.append((char)c);
+ c='0'+bi%base;
+ if (c>'9')
+ c= 'a'+(c-'0'-10);
+ buf.append((char)c);
+ }
+ return buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param c An ASCII encoded character 0-9 a-f A-F
+ * @return The byte value of the character 0-16.
+ */
+ public static byte convertHexDigit( byte c )
+ {
+ byte b = (byte)((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
+ if (b<0 || b>15)
+ throw new NumberFormatException("!hex "+c);
+ return b;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param c An ASCII encoded character 0-9 a-f A-F
+ * @return The byte value of the character 0-16.
+ */
+ public static int convertHexDigit( int c )
+ {
+ int d= ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
+ if (d<0 || d>15)
+ throw new NumberFormatException("!hex "+c);
+ return d;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void toHex(byte b,Appendable buf)
+ {
+ try
+ {
+ int d=0xf&((0xF0&b)>>4);
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ d=0xf&b;
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static void toHex(int value,Appendable buf) throws IOException
+ {
+ int d=0xf&((0xF0000000&value)>>28);
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ d=0xf&((0x0F000000&value)>>24);
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ d=0xf&((0x00F00000&value)>>20);
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ d=0xf&((0x000F0000&value)>>16);
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ d=0xf&((0x0000F000&value)>>12);
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ d=0xf&((0x00000F00&value)>>8);
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ d=0xf&((0x000000F0&value)>>4);
+ buf.append((char)((d>9?('A'-10):'0')+d));
+ d=0xf&value;
+ buf.append((char)((d>9?('A'-10):'0')+d));
+
+ Integer.toString(0,36);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public static void toHex(long value,Appendable buf) throws IOException
+ {
+ toHex((int)(value>>32),buf);
+ toHex((int)value,buf);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String toHexString(byte b)
+ {
+ return toHexString(new byte[]{b}, 0, 1);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String toHexString(byte[] b)
+ {
+ return toHexString(b, 0, b.length);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String toHexString(byte[] b,int offset,int length)
+ {
+ StringBuilder buf = new StringBuilder();
+ for (int i=offset;i<offset+length;i++)
+ {
+ int bi=0xff&b[i];
+ int c='0'+(bi/16)%16;
+ if (c>'9')
+ c= 'A'+(c-'0'-10);
+ buf.append((char)c);
+ c='0'+bi%16;
+ if (c>'9')
+ c= 'a'+(c-'0'-10);
+ buf.append((char)c);
+ }
+ return buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static byte[] fromHexString(String s)
+ {
+ if (s.length()%2!=0)
+ throw new IllegalArgumentException(s);
+ byte[] array = new byte[s.length()/2];
+ for (int i=0;i<array.length;i++)
+ {
+ int b = Integer.parseInt(s.substring(i*2,i*2+2),16);
+ array[i]=(byte)(0xff&b);
+ }
+ return array;
+ }
+
+
+ public static void dump(Class<?> c)
+ {
+ System.err.println("Dump: "+c);
+ dump(c.getClassLoader());
+ }
+
+ public static void dump(ClassLoader cl)
+ {
+ System.err.println("Dump Loaders:");
+ while(cl!=null)
+ {
+ System.err.println(" loader "+cl);
+ cl = cl.getParent();
+ }
+ }
+
+
+ public static Object call(Class<?> oClass, String methodName, Object obj, Object[] arg)
+ throws InvocationTargetException, NoSuchMethodException
+ {
+ // Lets just try all methods for now
+ for (Method method : oClass.getMethods())
+ {
+ if (!method.getName().equals(methodName))
+ continue;
+ if (method.getParameterTypes().length != arg.length)
+ continue;
+ if (Modifier.isStatic(method.getModifiers()) != (obj == null))
+ continue;
+ if ((obj == null) && method.getDeclaringClass() != oClass)
+ continue;
+
+ try
+ {
+ return method.invoke(obj, arg);
+ }
+ catch (IllegalAccessException | IllegalArgumentException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ // Lets look for a method with optional arguments
+ Object[] args_with_opts=null;
+
+ for (Method method : oClass.getMethods())
+ {
+ if (!method.getName().equals(methodName))
+ continue;
+ if (method.getParameterTypes().length != arg.length+1)
+ continue;
+ if (!method.getParameterTypes()[arg.length].isArray())
+ continue;
+ if (Modifier.isStatic(method.getModifiers()) != (obj == null))
+ continue;
+ if ((obj == null) && method.getDeclaringClass() != oClass)
+ continue;
+
+ if (args_with_opts==null)
+ args_with_opts=ArrayUtil.addToArray(arg,new Object[]{},Object.class);
+ try
+ {
+ return method.invoke(obj, args_with_opts);
+ }
+ catch (IllegalAccessException | IllegalArgumentException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+
+ throw new NoSuchMethodException(methodName);
+ }
+
+ public static Object construct(Class<?> klass, Object[] arguments) throws InvocationTargetException, NoSuchMethodException
+ {
+ for (Constructor<?> constructor : klass.getConstructors())
+ {
+ if (constructor.getParameterTypes().length != arguments.length)
+ continue;
+
+ try
+ {
+ return constructor.newInstance(arguments);
+ }
+ catch (InstantiationException | IllegalAccessException | IllegalArgumentException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ throw new NoSuchMethodException("<init>");
+ }
+
+ public static Object construct(Class<?> klass, Object[] arguments, Map<String, Object> namedArgMap) throws InvocationTargetException, NoSuchMethodException
+ {
+ for (Constructor<?> constructor : klass.getConstructors())
+ {
+ if (constructor.getParameterTypes().length != arguments.length)
+ continue;
+
+ try
+ {
+ Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
+
+ // target has no annotations
+ if ( parameterAnnotations == null || parameterAnnotations.length == 0 )
+ {
+ LOG.debug("Target has no parameter annotations");
+ return constructor.newInstance(arguments);
+ }
+ else
+ {
+ Object[] swizzled = new Object[arguments.length];
+
+ int count = 0;
+ for ( Annotation[] annotations : parameterAnnotations )
+ {
+ for ( Annotation annotation : annotations)
+ {
+ if ( annotation instanceof Name )
+ {
+ Name param = (Name)annotation;
+
+ if (namedArgMap.containsKey(param.value()))
+ {
+ LOG.debug("placing named {} in position {}", param.value(), count);
+ swizzled[count] = namedArgMap.get(param.value());
+ }
+ else
+ {
+ LOG.debug("placing {} in position {}", arguments[count], count);
+ swizzled[count] = arguments[count];
+ }
+ ++count;
+ }
+ else
+ {
+ LOG.debug("passing on annotation {}", annotation);
+ }
+ }
+ }
+
+ return constructor.newInstance(swizzled);
+ }
+
+ }
+ catch (InstantiationException | IllegalAccessException | IllegalArgumentException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ throw new NoSuchMethodException("<init>");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param o Object to test for true
+ * @return True if passed object is not null and is either a Boolean with value true or evaluates to a string that evaluates to true.
+ */
+ public static boolean isTrue(Object o)
+ {
+ if (o==null)
+ return false;
+ if (o instanceof Boolean)
+ return ((Boolean)o).booleanValue();
+ return Boolean.parseBoolean(o.toString());
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param o Object to test for false
+ * @return True if passed object is not null and is either a Boolean with value false or evaluates to a string that evaluates to false.
+ */
+ public static boolean isFalse(Object o)
+ {
+ if (o==null)
+ return false;
+ if (o instanceof Boolean)
+ return !((Boolean)o).booleanValue();
+ return "false".equalsIgnoreCase(o.toString());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+
+
+/* ------------------------------------------------------------ */
+/** URI Holder.
+ * This class assists with the decoding and encoding or HTTP URI's.
+ * It differs from the java.net.URL class as it does not provide
+ * communications ability, but it does assist with query string
+ * formatting.
+ * <P>UTF-8 encoding is used by default for % encoded characters. This
+ * may be overridden with the org.eclipse.jetty.util.URI.charset system property.
+ * @see UrlEncoded
+ *
+ */
+public class URIUtil
+ implements Cloneable
+{
+ public static final String SLASH="/";
+ public static final String HTTP="http";
+ public static final String HTTP_COLON="http:";
+ public static final String HTTPS="https";
+ public static final String HTTPS_COLON="https:";
+
+ // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
+ public static final Charset __CHARSET;
+
+ static
+ {
+ String charset = System.getProperty("org.eclipse.jetty.util.URI.charset");
+ __CHARSET = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
+ }
+
+ private URIUtil()
+ {}
+
+ /* ------------------------------------------------------------ */
+ /** Encode a URI path.
+ * This is the same encoding offered by URLEncoder, except that
+ * the '/' character is not encoded.
+ * @param path The path the encode
+ * @return The encoded path
+ */
+ public static String encodePath(String path)
+ {
+ if (path==null || path.length()==0)
+ return path;
+
+ StringBuilder buf = encodePath(null,path);
+ return buf==null?path:buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Encode a URI path.
+ * @param path The path the encode
+ * @param buf StringBuilder to encode path into (or null)
+ * @return The StringBuilder or null if no substitutions required.
+ */
+ public static StringBuilder encodePath(StringBuilder buf, String path)
+ {
+ byte[] bytes=null;
+ if (buf==null)
+ {
+ loop:
+ for (int i=0;i<path.length();i++)
+ {
+ char c=path.charAt(i);
+ switch(c)
+ {
+ case '%':
+ case '?':
+ case ';':
+ case '#':
+ case '\'':
+ case '"':
+ case '<':
+ case '>':
+ case ' ':
+ buf=new StringBuilder(path.length()*2);
+ break loop;
+ default:
+ if (c>127)
+ {
+ bytes=path.getBytes(URIUtil.__CHARSET);
+ buf=new StringBuilder(path.length()*2);
+ break loop;
+ }
+
+ }
+ }
+ if (buf==null)
+ return null;
+ }
+
+ synchronized(buf)
+ {
+ if (bytes!=null)
+ {
+ for (int i=0;i<bytes.length;i++)
+ {
+ byte c=bytes[i];
+ switch(c)
+ {
+ case '%':
+ buf.append("%25");
+ continue;
+ case '?':
+ buf.append("%3F");
+ continue;
+ case ';':
+ buf.append("%3B");
+ continue;
+ case '#':
+ buf.append("%23");
+ continue;
+ case '"':
+ buf.append("%22");
+ continue;
+ case '\'':
+ buf.append("%27");
+ continue;
+ case '<':
+ buf.append("%3C");
+ continue;
+ case '>':
+ buf.append("%3E");
+ continue;
+ case ' ':
+ buf.append("%20");
+ continue;
+ default:
+ if (c<0)
+ {
+ buf.append('%');
+ TypeUtil.toHex(c,buf);
+ }
+ else
+ buf.append((char)c);
+ continue;
+ }
+ }
+
+ }
+ else
+ {
+ for (int i=0;i<path.length();i++)
+ {
+ char c=path.charAt(i);
+ switch(c)
+ {
+ case '%':
+ buf.append("%25");
+ continue;
+ case '?':
+ buf.append("%3F");
+ continue;
+ case ';':
+ buf.append("%3B");
+ continue;
+ case '#':
+ buf.append("%23");
+ continue;
+ case '"':
+ buf.append("%22");
+ continue;
+ case '\'':
+ buf.append("%27");
+ continue;
+ case '<':
+ buf.append("%3C");
+ continue;
+ case '>':
+ buf.append("%3E");
+ continue;
+ case ' ':
+ buf.append("%20");
+ continue;
+ default:
+ buf.append(c);
+ continue;
+ }
+ }
+ }
+ }
+
+ return buf;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Encode a URI path.
+ * @param path The path the encode
+ * @param buf StringBuilder to encode path into (or null)
+ * @param encode String of characters to encode. % is always encoded.
+ * @return The StringBuilder or null if no substitutions required.
+ */
+ public static StringBuilder encodeString(StringBuilder buf,
+ String path,
+ String encode)
+ {
+ if (buf==null)
+ {
+ loop:
+ for (int i=0;i<path.length();i++)
+ {
+ char c=path.charAt(i);
+ if (c=='%' || encode.indexOf(c)>=0)
+ {
+ buf=new StringBuilder(path.length()<<1);
+ break loop;
+ }
+ }
+ if (buf==null)
+ return null;
+ }
+
+ synchronized(buf)
+ {
+ for (int i=0;i<path.length();i++)
+ {
+ char c=path.charAt(i);
+ if (c=='%' || encode.indexOf(c)>=0)
+ {
+ buf.append('%');
+ StringUtil.append(buf,(byte)(0xff&c),16);
+ }
+ else
+ buf.append(c);
+ }
+ }
+
+ return buf;
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Decode a URI path and strip parameters
+ * @param path The path the encode
+ * @param buf StringBuilder to encode path into
+ */
+ public static String decodePath(String path)
+ {
+ if (path==null)
+ return null;
+ // Array to hold all converted characters
+ char[] chars=null;
+ int n=0;
+ // Array to hold a sequence of %encodings
+ byte[] bytes=null;
+ int b=0;
+
+ int len=path.length();
+
+ for (int i=0;i<len;i++)
+ {
+ char c = path.charAt(i);
+
+ if (c=='%' && (i+2)<len)
+ {
+ if (chars==null)
+ {
+ chars=new char[len];
+ bytes=new byte[len];
+ path.getChars(0,i,chars,0);
+ }
+ bytes[b++]=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16));
+ i+=2;
+ continue;
+ }
+ else if (c==';')
+ {
+ if (chars==null)
+ {
+ chars=new char[len];
+ path.getChars(0,i,chars,0);
+ n=i;
+ }
+ break;
+ }
+ else if (bytes==null)
+ {
+ n++;
+ continue;
+ }
+
+ // Do we have some bytes to convert?
+ if (b>0)
+ {
+ String s=new String(bytes,0,b,__CHARSET);
+ s.getChars(0,s.length(),chars,n);
+ n+=s.length();
+ b=0;
+ }
+
+ chars[n++]=c;
+ }
+
+ if (chars==null)
+ return path;
+
+ // if we have a remaining sequence of bytes
+ if (b>0)
+ {
+ String s=new String(bytes,0,b,__CHARSET);
+ s.getChars(0,s.length(),chars,n);
+ n+=s.length();
+ }
+
+ return new String(chars,0,n);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* Decode a URI path and strip parameters.
+ * @param path The path the encode
+ * @param buf StringBuilder to encode path into
+ */
+ public static String decodePath(byte[] buf, int offset, int length)
+ {
+ byte[] bytes=null;
+ int n=0;
+
+ for (int i=0;i<length;i++)
+ {
+ byte b = buf[i + offset];
+
+ if (b=='%' && (i+2)<length)
+ {
+ b=(byte)(0xff&TypeUtil.parseInt(buf,i+offset+1,2,16));
+ i+=2;
+ }
+ else if (b==';')
+ {
+ length=i;
+ break;
+ }
+ else if (bytes==null)
+ {
+ n++;
+ continue;
+ }
+
+ if (bytes==null)
+ {
+ bytes=new byte[length];
+ for (int j=0;j<n;j++)
+ bytes[j]=buf[j + offset];
+ }
+
+ bytes[n++]=b;
+ }
+
+ if (bytes==null)
+ return new String(buf,offset,length,__CHARSET);
+ return new String(bytes,0,n,__CHARSET);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Add two URI path segments.
+ * Handles null and empty paths, path and query params (eg ?a=b or
+ * ;JSESSIONID=xxx) and avoids duplicate '/'
+ * @param p1 URI path segment (should be encoded)
+ * @param p2 URI path segment (should be encoded)
+ * @return Legally combined path segments.
+ */
+ public static String addPaths(String p1, String p2)
+ {
+ if (p1==null || p1.length()==0)
+ {
+ if (p1!=null && p2==null)
+ return p1;
+ return p2;
+ }
+ if (p2==null || p2.length()==0)
+ return p1;
+
+ int split=p1.indexOf(';');
+ if (split<0)
+ split=p1.indexOf('?');
+ if (split==0)
+ return p2+p1;
+ if (split<0)
+ split=p1.length();
+
+ StringBuilder buf = new StringBuilder(p1.length()+p2.length()+2);
+ buf.append(p1);
+
+ if (buf.charAt(split-1)=='/')
+ {
+ if (p2.startsWith(URIUtil.SLASH))
+ {
+ buf.deleteCharAt(split-1);
+ buf.insert(split-1,p2);
+ }
+ else
+ buf.insert(split,p2);
+ }
+ else
+ {
+ if (p2.startsWith(URIUtil.SLASH))
+ buf.insert(split,p2);
+ else
+ {
+ buf.insert(split,'/');
+ buf.insert(split+1,p2);
+ }
+ }
+
+ return buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Return the parent Path.
+ * Treat a URI like a directory path and return the parent directory.
+ */
+ public static String parentPath(String p)
+ {
+ if (p==null || URIUtil.SLASH.equals(p))
+ return null;
+ int slash=p.lastIndexOf('/',p.length()-2);
+ if (slash>=0)
+ return p.substring(0,slash+1);
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert a path to a cananonical form.
+ * All instances of "." and ".." are factored out. Null is returned
+ * if the path tries to .. above its root.
+ * @param path
+ * @return path or null.
+ */
+ public static String canonicalPath(String path)
+ {
+ if (path==null || path.length()==0)
+ return path;
+
+ int end=path.length();
+ int start = path.lastIndexOf('/', end);
+
+ search:
+ while (end>0)
+ {
+ switch(end-start)
+ {
+ case 2: // possible single dot
+ if (path.charAt(start+1)!='.')
+ break;
+ break search;
+ case 3: // possible double dot
+ if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.')
+ break;
+ break search;
+ }
+
+ end=start;
+ start=path.lastIndexOf('/',end-1);
+ }
+
+ // If we have checked the entire string
+ if (start>=end)
+ return path;
+
+ StringBuilder buf = new StringBuilder(path);
+ int delStart=-1;
+ int delEnd=-1;
+ int skip=0;
+
+ while (end>0)
+ {
+ switch(end-start)
+ {
+ case 2: // possible single dot
+ if (buf.charAt(start+1)!='.')
+ {
+ if (skip>0 && --skip==0)
+ {
+ delStart=start>=0?start:0;
+ if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+ delStart++;
+ }
+ break;
+ }
+
+ if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/')
+ break;
+
+ if(delEnd<0)
+ delEnd=end;
+ delStart=start;
+ if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/')
+ {
+ delStart++;
+ if (delEnd<buf.length() && buf.charAt(delEnd)=='/')
+ delEnd++;
+ break;
+ }
+ if (end==buf.length())
+ delStart++;
+
+ end=start--;
+ while (start>=0 && buf.charAt(start)!='/')
+ start--;
+ continue;
+
+ case 3: // possible double dot
+ if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.')
+ {
+ if (skip>0 && --skip==0)
+ { delStart=start>=0?start:0;
+ if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+ delStart++;
+ }
+ break;
+ }
+
+ delStart=start;
+ if (delEnd<0)
+ delEnd=end;
+
+ skip++;
+ end=start--;
+ while (start>=0 && buf.charAt(start)!='/')
+ start--;
+ continue;
+
+ default:
+ if (skip>0 && --skip==0)
+ {
+ delStart=start>=0?start:0;
+ if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+ delStart++;
+ }
+ }
+
+ // Do the delete
+ if (skip<=0 && delStart>=0 && delEnd>=delStart)
+ {
+ buf.delete(delStart,delEnd);
+ delStart=delEnd=-1;
+ if (skip>0)
+ delEnd=end;
+ }
+
+ end=start--;
+ while (start>=0 && buf.charAt(start)!='/')
+ start--;
+ }
+
+ // Too many ..
+ if (skip>0)
+ return null;
+
+ // Do the delete
+ if (delEnd>=0)
+ buf.delete(delStart,delEnd);
+
+ return buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Convert a path to a compact form.
+ * All instances of "//" and "///" etc. are factored out to single "/"
+ * @param path
+ * @return path
+ */
+ public static String compactPath(String path)
+ {
+ if (path==null || path.length()==0)
+ return path;
+
+ int state=0;
+ int end=path.length();
+ int i=0;
+
+ loop:
+ while (i<end)
+ {
+ char c=path.charAt(i);
+ switch(c)
+ {
+ case '?':
+ return path;
+ case '/':
+ state++;
+ if (state==2)
+ break loop;
+ break;
+ default:
+ state=0;
+ }
+ i++;
+ }
+
+ if (state<2)
+ return path;
+
+ StringBuffer buf = new StringBuffer(path.length());
+ buf.append(path,0,i);
+
+ loop2:
+ while (i<end)
+ {
+ char c=path.charAt(i);
+ switch(c)
+ {
+ case '?':
+ buf.append(path,i,end);
+ break loop2;
+ case '/':
+ if (state++==0)
+ buf.append(c);
+ break;
+ default:
+ state=0;
+ buf.append(c);
+ }
+ i++;
+ }
+
+ return buf.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param uri URI
+ * @return True if the uri has a scheme
+ */
+ public static boolean hasScheme(String uri)
+ {
+ for (int i=0;i<uri.length();i++)
+ {
+ char c=uri.charAt(i);
+ if (c==':')
+ return true;
+ if (!(c>='a'&&c<='z' ||
+ c>='A'&&c<='Z' ||
+ (i>0 &&(c>='0'&&c<='9' ||
+ c=='.' ||
+ c=='+' ||
+ c=='-'))
+ ))
+ break;
+ }
+ return false;
+ }
+
+ public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port)
+ {
+ if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+ url.append(scheme).append("://").append('[').append(server).append(']');
+ else
+ url.append(scheme).append("://").append(server);
+
+ if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+ url.append(':').append(port);
+ }
+
+ public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port)
+ {
+ synchronized (url)
+ {
+ if (server.indexOf(':')>=0&&server.charAt(0)!='[')
+ url.append(scheme).append("://").append('[').append(server).append(']');
+ else
+ url.append(scheme).append("://").append(server);
+
+ if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
+ url.append(':').append(port);
+ }
+ }
+}
+
+
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import static org.eclipse.jetty.util.TypeUtil.convertHexDigit;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Handles coding of MIME "x-www-form-urlencoded".
+ * <p>
+ * This class handles the encoding and decoding for either
+ * the query string of a URL or the _content of a POST HTTP request.
+ *
+ * <h4>Notes</h4>
+ * The UTF-8 charset is assumed, unless otherwise defined by either
+ * passing a parameter or setting the "org.eclipse.jetty.util.UrlEncoding.charset"
+ * System property.
+ * <p>
+ * The hashtable either contains String single values, vectors
+ * of String or arrays of Strings.
+ * <p>
+ * This class is only partially synchronised. In particular, simple
+ * get operations are not protected from concurrent updates.
+ *
+ * @see java.net.URLEncoder
+ */
+@SuppressWarnings("serial")
+public class UrlEncoded extends MultiMap<String> implements Cloneable
+{
+ static final Logger LOG = Log.getLogger(UrlEncoded.class);
+
+ public static final Charset ENCODING;
+ static
+ {
+ Charset encoding;
+ try
+ {
+ String charset = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset");
+ encoding = charset == null ? StandardCharsets.UTF_8 : Charset.forName(charset);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ encoding=StandardCharsets.UTF_8;
+ }
+ ENCODING=encoding;
+ }
+
+ /* ----------------------------------------------------------------- */
+ public UrlEncoded(UrlEncoded url)
+ {
+ super(url);
+ }
+
+ /* ----------------------------------------------------------------- */
+ public UrlEncoded()
+ {
+ }
+
+ public UrlEncoded(String query)
+ {
+ decodeTo(query,this,ENCODING,-1);
+ }
+
+ /* ----------------------------------------------------------------- */
+ public void decode(String query)
+ {
+ decodeTo(query,this,ENCODING,-1);
+ }
+
+ /* ----------------------------------------------------------------- */
+ public void decode(String query,Charset charset)
+ {
+ decodeTo(query,this,charset,-1);
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Encode Hashtable with % encoding.
+ */
+ public String encode()
+ {
+ return encode(ENCODING,false);
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Encode Hashtable with % encoding.
+ */
+ public String encode(Charset charset)
+ {
+ return encode(charset,false);
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Encode Hashtable with % encoding.
+ * @param equalsForNullValue if True, then an '=' is always used, even
+ * for parameters without a value. e.g. "blah?a=&b=&c=".
+ */
+ public synchronized String encode(Charset charset, boolean equalsForNullValue)
+ {
+ return encode(this,charset,equalsForNullValue);
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Encode Hashtable with % encoding.
+ * @param equalsForNullValue if True, then an '=' is always used, even
+ * for parameters without a value. e.g. "blah?a=&b=&c=".
+ */
+ public static String encode(MultiMap<String> map, Charset charset, boolean equalsForNullValue)
+ {
+ if (charset==null)
+ charset=ENCODING;
+
+ StringBuilder result = new StringBuilder(128);
+
+ boolean delim = false;
+ for(Map.Entry<String, List<String>> entry: map.entrySet())
+ {
+ String key = entry.getKey().toString();
+ List<String> list = entry.getValue();
+ int s=list.size();
+
+ if (delim)
+ {
+ result.append('&');
+ }
+
+ if (s==0)
+ {
+ result.append(encodeString(key,charset));
+ if(equalsForNullValue)
+ result.append('=');
+ }
+ else
+ {
+ for (int i=0;i<s;i++)
+ {
+ if (i>0)
+ result.append('&');
+ String val=list.get(i);
+ result.append(encodeString(key,charset));
+
+ if (val!=null)
+ {
+ String str=val.toString();
+ if (str.length()>0)
+ {
+ result.append('=');
+ result.append(encodeString(str,charset));
+ }
+ else if (equalsForNullValue)
+ result.append('=');
+ }
+ else if (equalsForNullValue)
+ result.append('=');
+ }
+ }
+ delim = true;
+ }
+ return result.toString();
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Decoded parameters to Map.
+ * @param content the string containing the encoded parameters
+ */
+ public static void decodeTo(String content, MultiMap<String> map, String charset, int maxKeys)
+ {
+ decodeTo(content,map,charset==null?null:Charset.forName(charset),maxKeys);
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Decoded parameters to Map.
+ * @param content the string containing the encoded parameters
+ */
+ public static void decodeTo(String content, MultiMap<String> map, Charset charset, int maxKeys)
+ {
+ if (charset==null)
+ charset=ENCODING;
+
+ synchronized(map)
+ {
+ String key = null;
+ String value = null;
+ int mark=-1;
+ boolean encoded=false;
+ for (int i=0;i<content.length();i++)
+ {
+ char c = content.charAt(i);
+ switch (c)
+ {
+ case '&':
+ int l=i-mark-1;
+ value = l==0?"":
+ (encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1,i));
+ mark=i;
+ encoded=false;
+ if (key != null)
+ {
+ map.add(key,value);
+ }
+ else if (value!=null&&value.length()>0)
+ {
+ map.add(value,"");
+ }
+ key = null;
+ value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ throw new IllegalStateException("Form too many keys");
+ break;
+ case '=':
+ if (key!=null)
+ break;
+ key = encoded?decodeString(content,mark+1,i-mark-1,charset):content.substring(mark+1,i);
+ mark=i;
+ encoded=false;
+ break;
+ case '+':
+ encoded=true;
+ break;
+ case '%':
+ encoded=true;
+ break;
+ }
+ }
+
+ if (key != null)
+ {
+ int l=content.length()-mark-1;
+ value = l==0?"":(encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1));
+ map.add(key,value);
+ }
+ else if (mark<content.length())
+ {
+ key = encoded
+ ?decodeString(content,mark+1,content.length()-mark-1,charset)
+ :content.substring(mark+1);
+ if (key != null && key.length() > 0)
+ {
+ map.add(key,"");
+ }
+ }
+ }
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Decoded parameters to Map.
+ * @param raw the byte[] containing the encoded parameters
+ * @param offset the offset within raw to decode from
+ * @param length the length of the section to decode
+ * @param map the {@link MultiMap} to populate
+ */
+ public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap<String> map)
+ {
+ Utf8StringBuilder buffer = new Utf8StringBuilder();
+ synchronized(map)
+ {
+ String key = null;
+ String value = null;
+
+ int end=offset+length;
+ for (int i=offset;i<end;i++)
+ {
+ byte b=raw[i];
+ try
+ {
+ switch ((char)(0xff&b))
+ {
+ case '&':
+ value = buffer.toReplacedString();
+ buffer.reset();
+ if (key != null)
+ {
+ map.add(key,value);
+ }
+ else if (value!=null&&value.length()>0)
+ {
+ map.add(value,"");
+ }
+ key = null;
+ value=null;
+ break;
+
+ case '=':
+ if (key!=null)
+ {
+ buffer.append(b);
+ break;
+ }
+ key = buffer.toReplacedString();
+ buffer.reset();
+ break;
+
+ case '+':
+ buffer.append((byte)' ');
+ break;
+
+ case '%':
+ if (i+2<end)
+ {
+ if ('u'==raw[i+1])
+ {
+ i++;
+ if (i+4<end)
+ {
+ byte top=raw[++i];
+ byte hi=raw[++i];
+ byte lo=raw[++i];
+ byte bot=raw[++i];
+ buffer.getStringBuilder().append(Character.toChars((convertHexDigit(top)<<12) +(convertHexDigit(hi)<<8) + (convertHexDigit(lo)<<4) +convertHexDigit(bot)));
+ }
+ else
+ {
+ buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+ i=end;
+ }
+ }
+ else
+ {
+ byte hi=raw[++i];
+ byte lo=raw[++i];
+ buffer.append((byte)((convertHexDigit(hi)<<4) + convertHexDigit(lo)));
+ }
+ }
+ else
+ {
+ buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+ i=end;
+ }
+ break;
+
+ default:
+ buffer.append(b);
+ break;
+ }
+ }
+ catch(NotUtf8Exception e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+ catch(NumberFormatException e)
+ {
+ buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+ }
+
+ if (key != null)
+ {
+ value = buffer.toReplacedString();
+ buffer.reset();
+ map.add(key,value);
+ }
+ else if (buffer.length()>0)
+ {
+ map.add(buffer.toReplacedString(),"");
+ }
+ }
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Decoded parameters to Map.
+ * @param in InputSteam to read
+ * @param map MultiMap to add parameters to
+ * @param maxLength maximum number of keys to read or -1 for no limit
+ */
+ public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
+ throws IOException
+ {
+ synchronized(map)
+ {
+ StringBuffer buffer = new StringBuffer();
+ String key = null;
+ String value = null;
+
+ int b;
+
+ int totalLength=0;
+ while ((b=in.read())>=0)
+ {
+ switch ((char) b)
+ {
+ case '&':
+ value = buffer.length()==0?"":buffer.toString();
+ buffer.setLength(0);
+ if (key != null)
+ {
+ map.add(key,value);
+ }
+ else if (value!=null&&value.length()>0)
+ {
+ map.add(value,"");
+ }
+ key = null;
+ value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ throw new IllegalStateException("Form too many keys");
+ break;
+
+ case '=':
+ if (key!=null)
+ {
+ buffer.append((char)b);
+ break;
+ }
+ key = buffer.toString();
+ buffer.setLength(0);
+ break;
+
+ case '+':
+ buffer.append(' ');
+ break;
+
+ case '%':
+ int code0=in.read();
+ if ('u'==code0)
+ {
+ int code1=in.read();
+ if (code1>=0)
+ {
+ int code2=in.read();
+ if (code2>=0)
+ {
+ int code3=in.read();
+ if (code3>=0)
+ buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+ }
+ }
+ }
+ else if (code0>=0)
+ {
+ int code1=in.read();
+ if (code1>=0)
+ buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
+ }
+ break;
+
+ default:
+ buffer.append((char)b);
+ break;
+ }
+ if (maxLength>=0 && (++totalLength > maxLength))
+ throw new IllegalStateException("Form too large");
+ }
+
+ if (key != null)
+ {
+ value = buffer.length()==0?"":buffer.toString();
+ buffer.setLength(0);
+ map.add(key,value);
+ }
+ else if (buffer.length()>0)
+ {
+ map.add(buffer.toString(), "");
+ }
+ }
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Decoded parameters to Map.
+ * @param in InputSteam to read
+ * @param map MultiMap to add parameters to
+ * @param maxLength maximum number of keys to read or -1 for no limit
+ */
+ public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
+ throws IOException
+ {
+ synchronized(map)
+ {
+ Utf8StringBuilder buffer = new Utf8StringBuilder();
+ String key = null;
+ String value = null;
+
+ int b;
+
+ int totalLength=0;
+ while ((b=in.read())>=0)
+ {
+ try
+ {
+ switch ((char) b)
+ {
+ case '&':
+ value = buffer.toReplacedString();
+ buffer.reset();
+ if (key != null)
+ {
+ map.add(key,value);
+ }
+ else if (value!=null&&value.length()>0)
+ {
+ map.add(value,"");
+ }
+ key = null;
+ value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ throw new IllegalStateException("Form too many keys");
+ break;
+
+ case '=':
+ if (key!=null)
+ {
+ buffer.append((byte)b);
+ break;
+ }
+ key = buffer.toReplacedString();
+ buffer.reset();
+ break;
+
+ case '+':
+ buffer.append((byte)' ');
+ break;
+
+ case '%':
+ int code0=in.read();
+ boolean decoded=false;
+ if ('u'==code0)
+ {
+ code0=in.read(); // XXX: we have to read the next byte, otherwise code0 is always 'u'
+ if (code0>=0)
+ {
+ int code1=in.read();
+ if (code1>=0)
+ {
+ int code2=in.read();
+ if (code2>=0)
+ {
+ int code3=in.read();
+ if (code3>=0)
+ {
+ buffer.getStringBuilder().append(Character.toChars
+ ((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+ decoded=true;
+ }
+ }
+ }
+ }
+ }
+ else if (code0>=0)
+ {
+ int code1=in.read();
+ if (code1>=0)
+ {
+ buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
+ decoded=true;
+ }
+ }
+
+ if (!decoded)
+ buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+
+ break;
+
+ default:
+ buffer.append((byte)b);
+ break;
+ }
+ }
+ catch(NotUtf8Exception e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+ catch(NumberFormatException e)
+ {
+ buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3);
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+ if (maxLength>=0 && (++totalLength > maxLength))
+ throw new IllegalStateException("Form too large");
+ }
+
+ if (key != null)
+ {
+ value = buffer.toReplacedString();
+ buffer.reset();
+ map.add(key,value);
+ }
+ else if (buffer.length()>0)
+ {
+ map.add(buffer.toReplacedString(), "");
+ }
+ }
+ }
+
+ /* -------------------------------------------------------------- */
+ public static void decodeUtf16To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) throws IOException
+ {
+ InputStreamReader input = new InputStreamReader(in,StandardCharsets.UTF_16);
+ StringWriter buf = new StringWriter(8192);
+ IO.copy(input,buf,maxLength);
+
+ decodeTo(buf.getBuffer().toString(),map,StandardCharsets.UTF_16,maxKeys);
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Decoded parameters to Map.
+ * @param in the stream containing the encoded parameters
+ */
+ public static void decodeTo(InputStream in, MultiMap<String> map, String charset, int maxLength, int maxKeys)
+ throws IOException
+ {
+ if (charset==null)
+ {
+ if (ENCODING.equals(StandardCharsets.UTF_8))
+ decodeUtf8To(in,map,maxLength,maxKeys);
+ else
+ decodeTo(in,map,ENCODING,maxLength,maxKeys);
+ }
+ else if (StringUtil.__UTF8.equalsIgnoreCase(charset))
+ decodeUtf8To(in,map,maxLength,maxKeys);
+ else if (StringUtil.__ISO_8859_1.equalsIgnoreCase(charset))
+ decode88591To(in,map,maxLength,maxKeys);
+ else if (StringUtil.__UTF16.equalsIgnoreCase(charset))
+ decodeUtf16To(in,map,maxLength,maxKeys);
+ else
+ decodeTo(in,map,Charset.forName(charset),maxLength,maxKeys);
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Decoded parameters to Map.
+ * @param in the stream containing the encoded parameters
+ */
+ public static void decodeTo(InputStream in, MultiMap<String> map, Charset charset, int maxLength, int maxKeys)
+ throws IOException
+ {
+ //no charset present, use the configured default
+ if (charset==null)
+ charset=ENCODING;
+
+ if (StandardCharsets.UTF_8.equals(charset))
+ {
+ decodeUtf8To(in,map,maxLength,maxKeys);
+ return;
+ }
+
+ if (StandardCharsets.ISO_8859_1.equals(charset))
+ {
+ decode88591To(in,map,maxLength,maxKeys);
+ return;
+ }
+
+ if (StandardCharsets.UTF_16.equals(charset)) // Should be all 2 byte encodings
+ {
+ decodeUtf16To(in,map,maxLength,maxKeys);
+ return;
+ }
+
+ synchronized(map)
+ {
+ String key = null;
+ String value = null;
+
+ int c;
+
+ int totalLength = 0;
+ ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
+
+ int size=0;
+
+ while ((c=in.read())>0)
+ {
+ switch ((char) c)
+ {
+ case '&':
+ size=output.size();
+ value = size==0?"":output.toString(charset);
+ output.setCount(0);
+ if (key != null)
+ {
+ map.add(key,value);
+ }
+ else if (value!=null&&value.length()>0)
+ {
+ map.add(value,"");
+ }
+ key = null;
+ value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ throw new IllegalStateException("Form too many keys");
+ break;
+ case '=':
+ if (key!=null)
+ {
+ output.write(c);
+ break;
+ }
+ size=output.size();
+ key = size==0?"":output.toString(charset);
+ output.setCount(0);
+ break;
+ case '+':
+ output.write(' ');
+ break;
+ case '%':
+ int code0=in.read();
+ if ('u'==code0)
+ {
+ int code1=in.read();
+ if (code1>=0)
+ {
+ int code2=in.read();
+ if (code2>=0)
+ {
+ int code3=in.read();
+ if (code3>=0)
+ output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
+ }
+ }
+
+ }
+ else if (code0>=0)
+ {
+ int code1=in.read();
+ if (code1>=0)
+ output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
+ }
+ break;
+ default:
+ output.write(c);
+ break;
+ }
+
+ totalLength++;
+ if (maxLength>=0 && totalLength > maxLength)
+ throw new IllegalStateException("Form too large");
+ }
+
+ size=output.size();
+ if (key != null)
+ {
+ value = size==0?"":output.toString(charset);
+ output.setCount(0);
+ map.add(key,value);
+ }
+ else if (size>0)
+ map.add(output.toString(charset),"");
+ }
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Decode String with % encoding.
+ * This method makes the assumption that the majority of calls
+ * will need no decoding.
+ */
+ public static String decodeString(String encoded,int offset,int length,Charset charset)
+ {
+ if (charset==null || StandardCharsets.UTF_8.equals(charset))
+ {
+ Utf8StringBuffer buffer=null;
+
+ for (int i=0;i<length;i++)
+ {
+ char c = encoded.charAt(offset+i);
+ if (c<0||c>0xff)
+ {
+ if (buffer==null)
+ {
+ buffer=new Utf8StringBuffer(length);
+ buffer.getStringBuffer().append(encoded,offset,offset+i+1);
+ }
+ else
+ buffer.getStringBuffer().append(c);
+ }
+ else if (c=='+')
+ {
+ if (buffer==null)
+ {
+ buffer=new Utf8StringBuffer(length);
+ buffer.getStringBuffer().append(encoded,offset,offset+i);
+ }
+
+ buffer.getStringBuffer().append(' ');
+ }
+ else if (c=='%')
+ {
+ if (buffer==null)
+ {
+ buffer=new Utf8StringBuffer(length);
+ buffer.getStringBuffer().append(encoded,offset,offset+i);
+ }
+
+ if ((i+2)<length)
+ {
+ try
+ {
+ if ('u'==encoded.charAt(offset+i+1))
+ {
+ if((i+5)<length)
+ {
+ int o=offset+i+2;
+ i+=5;
+ String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+ buffer.getStringBuffer().append(unicode);
+ }
+ else
+ {
+ i=length;
+ buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
+ }
+ }
+ else
+ {
+ int o=offset+i+1;
+ i+=2;
+ byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
+ buffer.append(b);
+ }
+ }
+ catch(NotUtf8Exception e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ }
+ catch(NumberFormatException e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
+ }
+ }
+ else
+ {
+ buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
+ i=length;
+ }
+ }
+ else if (buffer!=null)
+ buffer.getStringBuffer().append(c);
+ }
+
+ if (buffer==null)
+ {
+ if (offset==0 && encoded.length()==length)
+ return encoded;
+ return encoded.substring(offset,offset+length);
+ }
+
+ return buffer.toReplacedString();
+ }
+ else
+ {
+ StringBuffer buffer=null;
+
+ for (int i=0;i<length;i++)
+ {
+ char c = encoded.charAt(offset+i);
+ if (c<0||c>0xff)
+ {
+ if (buffer==null)
+ {
+ buffer=new StringBuffer(length);
+ buffer.append(encoded,offset,offset+i+1);
+ }
+ else
+ buffer.append(c);
+ }
+ else if (c=='+')
+ {
+ if (buffer==null)
+ {
+ buffer=new StringBuffer(length);
+ buffer.append(encoded,offset,offset+i);
+ }
+
+ buffer.append(' ');
+ }
+ else if (c=='%')
+ {
+ if (buffer==null)
+ {
+ buffer=new StringBuffer(length);
+ buffer.append(encoded,offset,offset+i);
+ }
+
+ byte[] ba=new byte[length];
+ int n=0;
+ while(c>=0 && c<=0xff)
+ {
+ if (c=='%')
+ {
+ if(i+2<length)
+ {
+ try
+ {
+ if ('u'==encoded.charAt(offset+i+1))
+ {
+ if (i+6<length)
+ {
+ int o=offset+i+2;
+ i+=6;
+ String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+ byte[] reencoded = unicode.getBytes(charset);
+ System.arraycopy(reencoded,0,ba,n,reencoded.length);
+ n+=reencoded.length;
+ }
+ else
+ {
+ ba[n++] = (byte)'?';
+ i=length;
+ }
+ }
+ else
+ {
+ int o=offset+i+1;
+ i+=3;
+ ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
+ n++;
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(e);
+ ba[n++] = (byte)'?';
+ }
+ }
+ else
+ {
+ ba[n++] = (byte)'?';
+ i=length;
+ }
+ }
+ else if (c=='+')
+ {
+ ba[n++]=(byte)' ';
+ i++;
+ }
+ else
+ {
+ ba[n++]=(byte)c;
+ i++;
+ }
+
+ if (i>=length)
+ break;
+ c = encoded.charAt(offset+i);
+ }
+
+ i--;
+ buffer.append(new String(ba,0,n,charset));
+
+ }
+ else if (buffer!=null)
+ buffer.append(c);
+ }
+
+ if (buffer==null)
+ {
+ if (offset==0 && encoded.length()==length)
+ return encoded;
+ return encoded.substring(offset,offset+length);
+ }
+
+ return buffer.toString();
+ }
+
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Perform URL encoding.
+ * @param string
+ * @return encoded string.
+ */
+ public static String encodeString(String string)
+ {
+ return encodeString(string,ENCODING);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Perform URL encoding.
+ * @param string
+ * @return encoded string.
+ */
+ public static String encodeString(String string,Charset charset)
+ {
+ if (charset==null)
+ charset=ENCODING;
+ byte[] bytes=null;
+ bytes=string.getBytes(charset);
+
+ int len=bytes.length;
+ byte[] encoded= new byte[bytes.length*3];
+ int n=0;
+ boolean noEncode=true;
+
+ for (int i=0;i<len;i++)
+ {
+ byte b = bytes[i];
+
+ if (b==' ')
+ {
+ noEncode=false;
+ encoded[n++]=(byte)'+';
+ }
+ else if (b>='a' && b<='z' ||
+ b>='A' && b<='Z' ||
+ b>='0' && b<='9')
+ {
+ encoded[n++]=b;
+ }
+ else
+ {
+ noEncode=false;
+ encoded[n++]=(byte)'%';
+ byte nibble= (byte) ((b&0xf0)>>4);
+ if (nibble>=10)
+ encoded[n++]=(byte)('A'+nibble-10);
+ else
+ encoded[n++]=(byte)('0'+nibble);
+ nibble= (byte) (b&0xf);
+ if (nibble>=10)
+ encoded[n++]=(byte)('A'+nibble-10);
+ else
+ encoded[n++]=(byte)('0'+nibble);
+ }
+ }
+
+ if (noEncode)
+ return string;
+
+ return new String(encoded,0,n,charset);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ @Override
+ public Object clone()
+ {
+ return new UrlEncoded(this);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Utf8 Appendable abstract base class
+ *
+ * This abstract class wraps a standard {@link java.lang.Appendable} and provides methods to append UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. The UTF-8 code was inspired by
+ * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ * License information for Bjoern Hoehrmann's code:
+ *
+ * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ **/
+public abstract class Utf8Appendable
+{
+ protected static final Logger LOG = Log.getLogger(Utf8Appendable.class);
+ public static final char REPLACEMENT = '\ufffd';
+ public static final byte[] REPLACEMENT_UTF8 = new byte[] {(byte)0xEF,(byte)0xBF,(byte)0xBD };
+ private static final int UTF8_ACCEPT = 0;
+ private static final int UTF8_REJECT = 12;
+
+ protected final Appendable _appendable;
+ protected int _state = UTF8_ACCEPT;
+
+ private static final byte[] BYTE_TABLE =
+ {
+ // The first part of the table maps bytes to character classes that
+ // to reduce the size of the transition table and create bitmasks.
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8
+ };
+
+ private static final byte[] TRANS_TABLE =
+ {
+ // The second part is a transition table that maps a combination
+ // of a state of the automaton and a character class to a state.
+ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+ 12,36,12,12,12,12,12,12,12,12,12,12
+ };
+
+ private int _codep;
+
+ public Utf8Appendable(Appendable appendable)
+ {
+ _appendable = appendable;
+ }
+
+ public abstract int length();
+
+ protected void reset()
+ {
+ _state = UTF8_ACCEPT;
+ }
+
+ public void append(byte b)
+ {
+ try
+ {
+ appendByte(b);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void append(ByteBuffer buf)
+ {
+ try
+ {
+ while (buf.remaining() > 0)
+ {
+ appendByte(buf.get());
+ }
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void append(byte[] b, int offset, int length)
+ {
+ try
+ {
+ int end = offset + length;
+ for (int i = offset; i < end; i++)
+ appendByte(b[i]);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean append(byte[] b, int offset, int length, int maxChars)
+ {
+ try
+ {
+ int end = offset + length;
+ for (int i = offset; i < end; i++)
+ {
+ if (length() > maxChars)
+ return false;
+ appendByte(b[i]);
+ }
+ return true;
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected void appendByte(byte b) throws IOException
+ {
+
+ if (b > 0 && _state == UTF8_ACCEPT)
+ {
+ _appendable.append((char)(b & 0xFF));
+ }
+ else
+ {
+ int i = b & 0xFF;
+ int type = BYTE_TABLE[i];
+ _codep = _state == UTF8_ACCEPT ? (0xFF >> type) & i : (i & 0x3F) | (_codep << 6);
+ int next = TRANS_TABLE[_state + type];
+
+ switch(next)
+ {
+ case UTF8_ACCEPT:
+ _state=next;
+ if (_codep < Character.MIN_HIGH_SURROGATE)
+ {
+ _appendable.append((char)_codep);
+ }
+ else
+ {
+ for (char c : Character.toChars(_codep))
+ _appendable.append(c);
+ }
+ break;
+
+ case UTF8_REJECT:
+ String reason = "byte "+TypeUtil.toHexString(b)+" in state "+(_state/12);
+ _codep=0;
+ _state = UTF8_ACCEPT;
+ _appendable.append(REPLACEMENT);
+ throw new NotUtf8Exception(reason);
+
+ default:
+ _state=next;
+
+ }
+ }
+ }
+
+ public boolean isUtf8SequenceComplete()
+ {
+ return _state == UTF8_ACCEPT;
+ }
+
+ @SuppressWarnings("serial")
+ public static class NotUtf8Exception extends IllegalArgumentException
+ {
+ public NotUtf8Exception(String reason)
+ {
+ super("Not valid UTF8! "+reason);
+ }
+ }
+
+ protected void checkState()
+ {
+ if (!isUtf8SequenceComplete())
+ {
+ _codep=0;
+ _state = UTF8_ACCEPT;
+ try
+ {
+ _appendable.append(REPLACEMENT);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ throw new NotUtf8Exception("incomplete UTF8 sequence");
+ }
+ }
+
+ public String toReplacedString()
+ {
+ if (!isUtf8SequenceComplete())
+ {
+ _codep=0;
+ _state = UTF8_ACCEPT;
+ try
+ {
+ _appendable.append(REPLACEMENT);
+ }
+ catch(IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ Throwable th= new NotUtf8Exception("incomplete UTF8 sequence");
+ LOG.warn(th.toString());
+ LOG.debug(th);
+ }
+ return _appendable.toString();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+
+/**
+ * Stateful parser for lines of UTF8 formatted text, looking for <code>"\n"</code> as a line termination character.
+ * <p>
+ * For use with new IO framework that is based on ByteBuffer parsing.
+ */
+public class Utf8LineParser
+{
+ private enum State
+ {
+ START,
+ PARSE,
+ END;
+ }
+
+ private State state;
+ private Utf8StringBuilder utf;
+
+ public Utf8LineParser()
+ {
+ this.state = State.START;
+ }
+
+ /**
+ * Parse a ByteBuffer (could be a partial buffer), and return once a complete line of UTF8 parsed text has been reached.
+ *
+ * @param buf
+ * the buffer to parse (could be an incomplete buffer)
+ * @return the line of UTF8 parsed text, or null if no line end termination has been reached within the {@link ByteBuffer#remaining() remaining} bytes of
+ * the provided ByteBuffer. (In the case of a null, a subsequent ByteBuffer with a line end termination should be provided)
+ * @throws NotUtf8Exception
+ * if the input buffer has bytes that do not conform to UTF8 validation (validation performed by {@link Utf8StringBuilder}
+ */
+ public String parse(ByteBuffer buf)
+ {
+ byte b;
+ while (buf.remaining() > 0)
+ {
+ b = buf.get();
+ if (parseByte(b))
+ {
+ state = State.START;
+ return utf.toString();
+ }
+ }
+ // have not reached end of line (yet)
+ return null;
+ }
+
+ private boolean parseByte(byte b)
+ {
+ switch (state)
+ {
+ case START:
+ utf = new Utf8StringBuilder();
+ state = State.PARSE;
+ return parseByte(b);
+ case PARSE:
+ // not waiting on more UTF sequence parts.
+ if (utf.isUtf8SequenceComplete() && ((b == '\r') || (b == '\n')))
+ {
+ state = State.END;
+ return parseByte(b);
+ }
+ utf.append(b);
+ break;
+ case END:
+ if (b == '\n')
+ {
+ // we've reached the end
+ state = State.START;
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+/* ------------------------------------------------------------ */
+/**
+ * UTF-8 StringBuffer.
+ *
+ * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append
+ * UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before
+ * state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used.
+ * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ */
+public class Utf8StringBuffer extends Utf8Appendable
+{
+ final StringBuffer _buffer;
+
+ public Utf8StringBuffer()
+ {
+ super(new StringBuffer());
+ _buffer = (StringBuffer)_appendable;
+ }
+
+ public Utf8StringBuffer(int capacity)
+ {
+ super(new StringBuffer(capacity));
+ _buffer = (StringBuffer)_appendable;
+ }
+
+ @Override
+ public int length()
+ {
+ return _buffer.length();
+ }
+
+ @Override
+ public void reset()
+ {
+ super.reset();
+ _buffer.setLength(0);
+ }
+
+ public StringBuffer getStringBuffer()
+ {
+ checkState();
+ return _buffer;
+ }
+
+ @Override
+ public String toString()
+ {
+ checkState();
+ return _buffer.toString();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util;
+
+
+/* ------------------------------------------------------------ */
+/** UTF-8 StringBuilder.
+ *
+ * This class wraps a standard {@link java.lang.StringBuilder} and provides methods to append
+ * UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before
+ * state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used.
+ * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ */
+public class Utf8StringBuilder extends Utf8Appendable
+{
+ final StringBuilder _buffer;
+
+ public Utf8StringBuilder()
+ {
+ super(new StringBuilder());
+ _buffer=(StringBuilder)_appendable;
+ }
+
+ public Utf8StringBuilder(int capacity)
+ {
+ super(new StringBuilder(capacity));
+ _buffer=(StringBuilder)_appendable;
+ }
+
+ @Override
+ public int length()
+ {
+ return _buffer.length();
+ }
+
+ @Override
+ public void reset()
+ {
+ super.reset();
+ _buffer.setLength(0);
+ }
+
+ public StringBuilder getStringBuilder()
+ {
+ checkState();
+ return _buffer;
+ }
+
+ @Override
+ public String toString()
+ {
+ checkState();
+ return _buffer.toString();
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedAttribute annotation is used to indicate that a given method
+ * exposes a JMX attribute. This annotation is placed always on the reader
+ * method of a given attribute. Unless it is marked as read-only in the
+ * configuration of the annotation a corresponding setter is looked for
+ * following normal naming conventions. For example if this annotation is
+ * on a method called getFoo() then a method called setFoo() would be looked
+ * for and if found wired automatically into the jmx attribute.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.METHOD } )
+public @interface ManagedAttribute
+{
+ /**
+ * Description of the Managed Attribute
+ *
+ * @returngit checkout
+ */
+ String value() default "Not Specified";
+
+ /**
+ * name to use for the attribute
+ *
+ * @return the name of the attribute
+ */
+ String name() default "";
+
+ /**
+ * Is the managed field read-only?
+ *
+ * Required only when a setter exists but should not be exposed via JMX
+ *
+ * @return true if readonly
+ */
+ boolean readonly() default false;
+
+ /**
+ * Does the managed field exist on a proxy object?
+ *
+ *
+ * @return true if a proxy object is involved
+ */
+ boolean proxied() default false;
+
+
+ /**
+ * If is a field references a setter that doesn't conform to standards for discovery
+ * it can be set here.
+ *
+ * @return the full name of the setter in question
+ */
+ String setter() default "";
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedObject annotation is used on a class at the top level to
+ * indicate that it should be exposed as an mbean. It has only one attribute
+ * to it which is used as the description of the MBean. Should multiple
+ * @ManagedObject annotations be found in the chain of influence then the
+ * first description is used.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.TYPE } )
+public @interface ManagedObject
+{
+ /**
+ * Description of the Managed Object
+ */
+ String value() default "Not Specified";
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The @ManagedOperation annotation is used to indicate that a given method
+ * should be considered a JMX operation.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.METHOD } )
+public @interface ManagedOperation
+{
+ /**
+ * Description of the Managed Object
+ */
+ String value() default "Not Specified";
+
+ /**
+ * The impact of an operation.
+ *
+ * NOTE: Valid values are UNKNOWN, ACTION, INFO, ACTION_INFO
+ *
+ * NOTE: applies to METHOD
+ *
+ * @return String representing the impact of the operation
+ */
+ String impact() default "UNKNOWN";
+
+ /**
+ * Does the managed field exist on a proxy object?
+ *
+ *
+ * @return true if a proxy object is involved
+ */
+ boolean proxied() default false;
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to describe variables in method
+ * signatures so that when rendered into tools like JConsole
+ * it is clear what the parameters are. For example:
+ *
+ * public void doodle(@Name(value="doodle", description="A description of the argument") String doodle)
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target( { ElementType.PARAMETER } )
+public @interface Name
+{
+ /**
+ * the name of the parameter
+ */
+ String value();
+
+ /**
+ * the description of the parameter
+ */
+ String description() default "";
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Common Utility Annotations
+ */
+package org.eclipse.jetty.util.annotation;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.component;
+
+import java.lang.management.ManagementFactory;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Basic implementation of the life cycle interface for components.
+ */
+@ManagedObject("Abstract Implementation of LifeCycle")
+public abstract class AbstractLifeCycle implements LifeCycle
+{
+ private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class);
+
+ public static final String STOPPED="STOPPED";
+ public static final String FAILED="FAILED";
+ public static final String STARTING="STARTING";
+ public static final String STARTED="STARTED";
+ public static final String STOPPING="STOPPING";
+ public static final String RUNNING="RUNNING";
+
+ private final CopyOnWriteArrayList<LifeCycle.Listener> _listeners=new CopyOnWriteArrayList<LifeCycle.Listener>();
+ private final Object _lock = new Object();
+ private final int __FAILED = -1, __STOPPED = 0, __STARTING = 1, __STARTED = 2, __STOPPING = 3;
+ private volatile int _state = __STOPPED;
+ private long _stopTimeout = 30000;
+
+ protected void doStart() throws Exception
+ {
+ }
+
+ protected void doStop() throws Exception
+ {
+ }
+
+ @Override
+ public final void start() throws Exception
+ {
+ synchronized (_lock)
+ {
+ try
+ {
+ if (_state == __STARTED || _state == __STARTING)
+ return;
+ setStarting();
+ doStart();
+ setStarted();
+ }
+ catch (Throwable e)
+ {
+ setFailed(e);
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public final void stop() throws Exception
+ {
+ synchronized (_lock)
+ {
+ try
+ {
+ if (_state == __STOPPING || _state == __STOPPED)
+ return;
+ setStopping();
+ doStop();
+ setStopped();
+ }
+ catch (Throwable e)
+ {
+ setFailed(e);
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public boolean isRunning()
+ {
+ final int state = _state;
+
+ return state == __STARTED || state == __STARTING;
+ }
+
+ @Override
+ public boolean isStarted()
+ {
+ return _state == __STARTED;
+ }
+
+ @Override
+ public boolean isStarting()
+ {
+ return _state == __STARTING;
+ }
+
+ @Override
+ public boolean isStopping()
+ {
+ return _state == __STOPPING;
+ }
+
+ @Override
+ public boolean isStopped()
+ {
+ return _state == __STOPPED;
+ }
+
+ @Override
+ public boolean isFailed()
+ {
+ return _state == __FAILED;
+ }
+
+ @Override
+ public void addLifeCycleListener(LifeCycle.Listener listener)
+ {
+ _listeners.add(listener);
+ }
+
+ @Override
+ public void removeLifeCycleListener(LifeCycle.Listener listener)
+ {
+ _listeners.remove(listener);
+ }
+
+ @ManagedAttribute(value="Lifecycle State for this instance", readonly=true)
+ public String getState()
+ {
+ switch(_state)
+ {
+ case __FAILED: return FAILED;
+ case __STARTING: return STARTING;
+ case __STARTED: return STARTED;
+ case __STOPPING: return STOPPING;
+ case __STOPPED: return STOPPED;
+ }
+ return null;
+ }
+
+ public static String getState(LifeCycle lc)
+ {
+ if (lc.isStarting()) return STARTING;
+ if (lc.isStarted()) return STARTED;
+ if (lc.isStopping()) return STOPPING;
+ if (lc.isStopped()) return STOPPED;
+ return FAILED;
+ }
+
+ private void setStarted()
+ {
+ _state = __STARTED;
+ if (LOG.isDebugEnabled())
+
+ LOG.debug(STARTED+" @{}ms {}",ManagementFactory.getRuntimeMXBean().getUptime(),this);
+ for (Listener listener : _listeners)
+ listener.lifeCycleStarted(this);
+ }
+
+ private void setStarting()
+ {
+ LOG.debug("starting {}",this);
+ _state = __STARTING;
+ for (Listener listener : _listeners)
+ listener.lifeCycleStarting(this);
+ }
+
+ private void setStopping()
+ {
+ LOG.debug("stopping {}",this);
+ _state = __STOPPING;
+ for (Listener listener : _listeners)
+ listener.lifeCycleStopping(this);
+ }
+
+ private void setStopped()
+ {
+ _state = __STOPPED;
+ LOG.debug("{} {}",STOPPED,this);
+ for (Listener listener : _listeners)
+ listener.lifeCycleStopped(this);
+ }
+
+ private void setFailed(Throwable th)
+ {
+ _state = __FAILED;
+ LOG.warn(FAILED+" " + this+": "+th,th);
+ for (Listener listener : _listeners)
+ listener.lifeCycleFailure(this,th);
+ }
+
+ @ManagedAttribute(value="The stop timeout in milliseconds")
+ public long getStopTimeout()
+ {
+ return _stopTimeout;
+ }
+
+ public void setStopTimeout(long stopTimeout)
+ {
+ this._stopTimeout = stopTimeout;
+ }
+
+ public static abstract class AbstractLifeCycleListener implements LifeCycle.Listener
+ {
+ @Override public void lifeCycleFailure(LifeCycle event, Throwable cause) {}
+ @Override public void lifeCycleStarted(LifeCycle event) {}
+ @Override public void lifeCycleStarting(LifeCycle event) {}
+ @Override public void lifeCycleStopped(LifeCycle event) {}
+ @Override public void lifeCycleStopping(LifeCycle event) {}
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.component;
+
+import java.util.Collection;
+
+public interface Container
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Add a bean. If the bean is-a {@link Listener}, then also do an implicit {@link #addEventListener(Listener)}.
+ * @param o the bean object to add
+ * @return true if the bean was added, false if it was already present
+ */
+ public boolean addBean(Object o);
+
+ /**
+ * @return the list of beans known to this aggregate
+ * @see #getBean(Class)
+ */
+ public Collection<Object> getBeans();
+
+ /**
+ * @param clazz the class of the beans
+ * @return the list of beans of the given class (or subclass)
+ * @see #getBeans()
+ */
+ public <T> Collection<T> getBeans(Class<T> clazz);
+
+ /**
+ * @param clazz the class of the bean
+ * @return the first bean of a specific class (or subclass), or null if no such bean exist
+ */
+ public <T> T getBean(Class<T> clazz);
+
+ /**
+ * Removes the given bean.
+ * If the bean is-a {@link Listener}, then also do an implicit {@link #removeEventListener(Listener)}.
+ * @return whether the bean was removed
+ */
+ public boolean removeBean(Object o);
+
+ /**
+ * Add an event listener.
+ * @see Container#addBean(Object)
+ * @param listener
+ */
+ public void addEventListener(Listener listener);
+
+ /**
+ * Remove an event listener.
+ * @see Container#removeBean(Object)
+ * @param listener
+ */
+ public void removeEventListener(Listener listener);
+
+ /**
+ * A listener for Container events.
+ * If an added bean implements this interface it will receive the events
+ * for this container.
+ */
+ public interface Listener
+ {
+ void beanAdded(Container parent,Object child);
+ void beanRemoved(Container parent,Object child);
+ }
+
+ /**
+ * Inherited Listener.
+ * If an added bean implements this interface, then it will
+ * be added to all contained beans that are themselves Containers
+ */
+ public interface InheritedListener extends Listener
+ {
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.component;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * An ContainerLifeCycle is an {@link LifeCycle} implementation for a collection of contained beans.
+ * <p>
+ * Beans can be added the ContainerLifeCycle either as managed beans or as unmanaged beans. A managed bean is started, stopped and destroyed with the aggregate.
+ * An unmanaged bean is associated with the aggregate for the purposes of {@link #dump()}, but it's lifecycle must be managed externally.
+ * <p>
+ * When a {@link LifeCycle} bean is added without a managed state being specified the state is determined heuristically:
+ * <ul>
+ * <li>If the added bean is running, it will be added as an unmanaged bean.
+ * <li>If the added bean is !running and the container is !running, it will be added as an AUTO bean (see below).
+ * <li>If the added bean is !running and the container is starting, it will be added as an managed bean and will be started (this handles the frequent case of
+ * new beans added during calls to doStart).
+ * <li>If the added bean is !running and the container is started, it will be added as an unmanaged bean.
+ * </ul>
+ * When the container is started, then all contained managed beans will also be started. Any contained Auto beans
+ * will be check for their status and if already started will be switched unmanaged beans, else they will be
+ * started and switched to managed beans. Beans added after a container is started are not started and their state needs to
+ * be explicitly managed.
+ * <p>
+ * When stopping the container, a contained bean will be stopped by this aggregate only if it
+ * is started by this aggregate.
+ * <p>
+ * The methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to
+ * explicitly control the life cycle relationship.
+ * <p>
+ * If adding a bean that is shared between multiple {@link ContainerLifeCycle} instances, then it should be started before being added, so it is unmanaged, or
+ * the API must be used to explicitly set it as unmanaged.
+ * <p>
+ * This class also provides utility methods to dump deep structures of objects. It the dump, the following symbols are used to indicate the type of contained object:
+ * <pre>
+ * SomeContainerLifeCycleInstance
+ * +- contained POJO instance
+ * += contained MANAGED object, started and stopped with this instance
+ * +~ referenced UNMANAGED object, with separate lifecycle
+ * +? referenced AUTO object that could become MANAGED or UNMANAGED.
+ * </pre>
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@ManagedObject("Implementation of Container and LifeCycle")
+public class ContainerLifeCycle extends AbstractLifeCycle implements Container, Destroyable, Dumpable
+{
+ private static final Logger LOG = Log.getLogger(ContainerLifeCycle.class);
+ private final List<Bean> _beans = new CopyOnWriteArrayList<>();
+ private final List<Container.Listener> _listeners = new CopyOnWriteArrayList<>();
+ private boolean _doStarted = false;
+
+
+ public ContainerLifeCycle()
+ {
+ }
+
+ /**
+ * Starts the managed lifecycle beans in the order they were added.
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ // indicate that we are started, so that addBean will start other beans added.
+ _doStarted = true;
+
+ // start our managed and auto beans
+ for (Bean b : _beans)
+ {
+ if (b._bean instanceof LifeCycle)
+ {
+ LifeCycle l = (LifeCycle)b._bean;
+ switch(b._managed)
+ {
+ case MANAGED:
+ if (!l.isRunning())
+ start(l);
+ break;
+ case AUTO:
+ if (l.isRunning())
+ unmanage(b);
+ else
+ {
+ manage(b);
+ start(l);
+ }
+ break;
+ }
+ }
+ }
+
+ super.doStart();
+ }
+
+ /**
+ * Starts the given lifecycle.
+ *
+ * @param l
+ * @throws Exception
+ */
+ protected void start(LifeCycle l) throws Exception
+ {
+ l.start();
+ }
+
+ /**
+ * Stops the given lifecycle.
+ *
+ * @param l
+ * @throws Exception
+ */
+ protected void stop(LifeCycle l) throws Exception
+ {
+ l.stop();
+ }
+
+ /**
+ * Stops the managed lifecycle beans in the reverse order they were added.
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ _doStarted = false;
+ super.doStop();
+ List<Bean> reverse = new ArrayList<>(_beans);
+ Collections.reverse(reverse);
+ for (Bean b : reverse)
+ {
+ if (b._managed==Managed.MANAGED && b._bean instanceof LifeCycle)
+ {
+ LifeCycle l = (LifeCycle)b._bean;
+ if (l.isRunning())
+ stop(l);
+ }
+ }
+ }
+
+ /**
+ * Destroys the managed Destroyable beans in the reverse order they were added.
+ */
+ @Override
+ public void destroy()
+ {
+ List<Bean> reverse = new ArrayList<>(_beans);
+ Collections.reverse(reverse);
+ for (Bean b : reverse)
+ {
+ if (b._bean instanceof Destroyable && (b._managed==Managed.MANAGED || b._managed==Managed.POJO))
+ {
+ Destroyable d = (Destroyable)b._bean;
+ d.destroy();
+ }
+ }
+ _beans.clear();
+ }
+
+
+ /**
+ * @param bean the bean to test
+ * @return whether this aggregate contains the bean
+ */
+ public boolean contains(Object bean)
+ {
+ for (Bean b : _beans)
+ if (b._bean == bean)
+ return true;
+ return false;
+ }
+
+ /**
+ * @param bean the bean to test
+ * @return whether this aggregate contains and manages the bean
+ */
+ public boolean isManaged(Object bean)
+ {
+ for (Bean b : _beans)
+ if (b._bean == bean)
+ return b.isManaged();
+ return false;
+ }
+
+ /**
+ * Adds the given bean, detecting whether to manage it or not.
+ * If the bean is a {@link LifeCycle}, then it will be managed if it is not
+ * already started and not managed if it is already started.
+ * The {@link #addBean(Object, boolean)}
+ * method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)}
+ * methods may be used after an add to change the status.
+ *
+ * @param o the bean object to add
+ * @return true if the bean was added, false if it was already present
+ */
+ @Override
+ public boolean addBean(Object o)
+ {
+ if (o instanceof LifeCycle)
+ {
+ LifeCycle l = (LifeCycle)o;
+ return addBean(o,l.isRunning()?Managed.UNMANAGED:Managed.AUTO);
+ }
+
+ return addBean(o,Managed.POJO);
+ }
+
+ /**
+ * Adds the given bean, explicitly managing it or not.
+ *
+ * @param o The bean object to add
+ * @param managed whether to managed the lifecycle of the bean
+ * @return true if the bean was added, false if it was already present
+ */
+ public boolean addBean(Object o, boolean managed)
+ {
+ if (o instanceof LifeCycle)
+ return addBean(o,managed?Managed.MANAGED:Managed.UNMANAGED);
+ return addBean(o,managed?Managed.POJO:Managed.UNMANAGED);
+ }
+
+ public boolean addBean(Object o, Managed managed)
+ {
+ if (contains(o))
+ return false;
+
+ Bean new_bean = new Bean(o);
+
+ // if the bean is a Listener
+ if (o instanceof Container.Listener)
+ addEventListener((Container.Listener)o);
+
+ // Add the bean
+ _beans.add(new_bean);
+
+ // Tell existing listeners about the new bean
+ for (Container.Listener l:_listeners)
+ l.beanAdded(this,o);
+
+ try
+ {
+ switch (managed)
+ {
+ case UNMANAGED:
+ unmanage(new_bean);
+ break;
+
+ case MANAGED:
+ manage(new_bean);
+
+ if (isStarting() && _doStarted)
+ {
+ LifeCycle l = (LifeCycle)o;
+ if (!l.isRunning())
+ start(l);
+ }
+ break;
+
+ case AUTO:
+ if (o instanceof LifeCycle)
+ {
+ LifeCycle l = (LifeCycle)o;
+ if (isStarting())
+ {
+ if (l.isRunning())
+ unmanage(new_bean);
+ else if (_doStarted)
+ {
+ manage(new_bean);
+ start(l);
+ }
+ else
+ new_bean._managed=Managed.AUTO;
+ }
+ else if (isStarted())
+ unmanage(new_bean);
+ else
+ new_bean._managed=Managed.AUTO;
+ }
+ else
+ new_bean._managed=Managed.POJO;
+ break;
+
+ case POJO:
+ new_bean._managed=Managed.POJO;
+ }
+ }
+ catch (RuntimeException | Error e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ LOG.debug("{} added {}",this,new_bean);
+
+ return true;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Add a managed lifecycle.
+ * <p>This is a conveniance method that uses addBean(lifecycle,true)
+ * and then ensures that the added bean is started iff this container
+ * is running. Exception from nested calls to start are caught and
+ * wrapped as RuntimeExceptions
+ * @param lifecycle
+ */
+ public void addManaged(LifeCycle lifecycle)
+ {
+ addBean(lifecycle,true);
+ try
+ {
+ if (isRunning() && !lifecycle.isRunning())
+ start(lifecycle);
+ }
+ catch (RuntimeException | Error e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void addEventListener(Container.Listener listener)
+ {
+ if (_listeners.contains(listener))
+ return;
+
+ _listeners.add(listener);
+
+ // tell it about existing beans
+ for (Bean b:_beans)
+ {
+ listener.beanAdded(this,b._bean);
+
+ // handle inheritance
+ if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
+ {
+ if (b._bean instanceof ContainerLifeCycle)
+ ((ContainerLifeCycle)b._bean).addBean(listener, false);
+ else
+ ((Container)b._bean).addBean(listener);
+ }
+ }
+ }
+
+ /**
+ * Manages a bean already contained by this aggregate, so that it is started/stopped/destroyed with this
+ * aggregate.
+ *
+ * @param bean The bean to manage (must already have been added).
+ */
+ public void manage(Object bean)
+ {
+ for (Bean b : _beans)
+ {
+ if (b._bean == bean)
+ {
+ manage(b);
+ return;
+ }
+ }
+ throw new IllegalArgumentException("Unknown bean " + bean);
+ }
+
+ private void manage(Bean bean)
+ {
+ if (bean._managed!=Managed.MANAGED)
+ {
+ bean._managed=Managed.MANAGED;
+
+ if (bean._bean instanceof Container)
+ {
+ for (Container.Listener l:_listeners)
+ {
+ if (l instanceof InheritedListener)
+ {
+ if (bean._bean instanceof ContainerLifeCycle)
+ ((ContainerLifeCycle)bean._bean).addBean(l,false);
+ else
+ ((Container)bean._bean).addBean(l);
+ }
+ }
+ }
+
+ if (bean._bean instanceof AbstractLifeCycle)
+ {
+ ((AbstractLifeCycle)bean._bean).setStopTimeout(getStopTimeout());
+ }
+ }
+ }
+
+ /**
+ * Unmanages a bean already contained by this aggregate, so that it is not started/stopped/destroyed with this
+ * aggregate.
+ *
+ * @param bean The bean to unmanage (must already have been added).
+ */
+ public void unmanage(Object bean)
+ {
+ for (Bean b : _beans)
+ {
+ if (b._bean == bean)
+ {
+ unmanage(b);
+ return;
+ }
+ }
+ throw new IllegalArgumentException("Unknown bean " + bean);
+ }
+
+ private void unmanage(Bean bean)
+ {
+ if (bean._managed!=Managed.UNMANAGED)
+ {
+ if (bean._managed==Managed.MANAGED && bean._bean instanceof Container)
+ {
+ for (Container.Listener l:_listeners)
+ {
+ if (l instanceof InheritedListener)
+ ((Container)bean._bean).removeBean(l);
+ }
+ }
+ bean._managed=Managed.UNMANAGED;
+ }
+ }
+
+ @Override
+ public Collection<Object> getBeans()
+ {
+ return getBeans(Object.class);
+ }
+
+ public void setBeans(Collection<Object> beans)
+ {
+ for (Object bean : beans)
+ addBean(bean);
+ }
+
+ @Override
+ public <T> Collection<T> getBeans(Class<T> clazz)
+ {
+ ArrayList<T> beans = new ArrayList<>();
+ for (Bean b : _beans)
+ {
+ if (clazz.isInstance(b._bean))
+ beans.add(clazz.cast(b._bean));
+ }
+ return beans;
+ }
+
+ @Override
+ public <T> T getBean(Class<T> clazz)
+ {
+ for (Bean b : _beans)
+ {
+ if (clazz.isInstance(b._bean))
+ return clazz.cast(b._bean);
+ }
+ return null;
+ }
+
+ /**
+ * Removes all bean
+ */
+ public void removeBeans()
+ {
+ ArrayList<Bean> beans= new ArrayList<>(_beans);
+ for (Bean b : beans)
+ remove(b);
+ }
+
+ private Bean getBean(Object o)
+ {
+ for (Bean b : _beans)
+ {
+ if (b._bean == o)
+ return b;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean removeBean(Object o)
+ {
+ Bean b=getBean(o);
+ return b!=null && remove(b);
+ }
+
+ private boolean remove(Bean bean)
+ {
+ if (_beans.remove(bean))
+ {
+
+ unmanage(bean);
+
+ for (Container.Listener l:_listeners)
+ l.beanRemoved(this,bean._bean);
+
+ if (bean._bean instanceof Container.Listener)
+ removeEventListener((Container.Listener)bean._bean);
+
+ // stop managed beans
+ if (bean._managed==Managed.MANAGED && bean._bean instanceof LifeCycle)
+ {
+ try
+ {
+ stop((LifeCycle)bean._bean);
+ }
+ catch(RuntimeException | Error e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void removeEventListener(Container.Listener listener)
+ {
+ if (_listeners.remove(listener))
+ {
+ // remove existing beans
+ for (Bean b:_beans)
+ {
+ listener.beanRemoved(this,b._bean);
+
+ if (listener instanceof InheritedListener && b.isManaged() && b._bean instanceof Container)
+ ((Container)b._bean).removeBean(listener);
+ }
+ }
+ }
+
+ @Override
+ public void setStopTimeout(long stopTimeout)
+ {
+ super.setStopTimeout(stopTimeout);
+ for (Bean bean : _beans)
+ {
+ if (bean.isManaged() && bean._bean instanceof AbstractLifeCycle)
+ ((AbstractLifeCycle)bean._bean).setStopTimeout(stopTimeout);
+ }
+ }
+
+ /**
+ * Dumps to {@link System#err}.
+ * @see #dump()
+ */
+ @ManagedOperation("Dump the object to stderr")
+ public void dumpStdErr()
+ {
+ try
+ {
+ dump(System.err, "");
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+ @Override
+ @ManagedOperation("Dump the object to a string")
+ public String dump()
+ {
+ return dump(this);
+ }
+
+ public static String dump(Dumpable dumpable)
+ {
+ StringBuilder b = new StringBuilder();
+ try
+ {
+ dumpable.dump(b, "");
+ }
+ catch (IOException e)
+ {
+ LOG.warn(e);
+ }
+ return b.toString();
+ }
+
+ public void dump(Appendable out) throws IOException
+ {
+ dump(out, "");
+ }
+
+ protected void dumpThis(Appendable out) throws IOException
+ {
+ out.append(String.valueOf(this)).append(" - ").append(getState()).append("\n");
+ }
+
+ public static void dumpObject(Appendable out, Object o) throws IOException
+ {
+ try
+ {
+ if (o instanceof LifeCycle)
+ out.append(String.valueOf(o)).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n");
+ else
+ out.append(String.valueOf(o)).append("\n");
+ }
+ catch (Throwable th)
+ {
+ out.append(" => ").append(th.toString()).append('\n');
+ }
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ dumpBeans(out,indent);
+ }
+
+ protected void dumpBeans(Appendable out, String indent, Collection<?>... collections) throws IOException
+ {
+ dumpThis(out);
+ int size = _beans.size();
+ for (Collection<?> c : collections)
+ size += c.size();
+ if (size == 0)
+ return;
+ int i = 0;
+ for (Bean b : _beans)
+ {
+ i++;
+
+ switch(b._managed)
+ {
+ case POJO:
+ out.append(indent).append(" +- ");
+ if (b._bean instanceof Dumpable)
+ ((Dumpable)b._bean).dump(out, indent + (i == size ? " " : " | "));
+ else
+ dumpObject(out, b._bean);
+ break;
+
+ case MANAGED:
+ out.append(indent).append(" += ");
+ if (b._bean instanceof Dumpable)
+ ((Dumpable)b._bean).dump(out, indent + (i == size ? " " : " | "));
+ else
+ dumpObject(out, b._bean);
+ break;
+
+ case UNMANAGED:
+ out.append(indent).append(" +~ ");
+ dumpObject(out, b._bean);
+ break;
+
+ case AUTO:
+ out.append(indent).append(" +? ");
+ if (b._bean instanceof Dumpable)
+ ((Dumpable)b._bean).dump(out, indent + (i == size ? " " : " | "));
+ else
+ dumpObject(out, b._bean);
+ break;
+
+ }
+ }
+
+ if (i<size)
+ out.append(indent).append(" |\n");
+
+ for (Collection<?> c : collections)
+ {
+ for (Object o : c)
+ {
+ i++;
+ out.append(indent).append(" +> ");
+
+ if (o instanceof Dumpable)
+ ((Dumpable)o).dump(out, indent + (i == size ? " " : " | "));
+ else
+ dumpObject(out, o);
+ }
+ }
+ }
+
+ public static void dump(Appendable out, String indent, Collection<?>... collections) throws IOException
+ {
+ if (collections.length == 0)
+ return;
+ int size = 0;
+ for (Collection<?> c : collections)
+ size += c.size();
+ if (size == 0)
+ return;
+
+ int i = 0;
+ for (Collection<?> c : collections)
+ {
+ for (Object o : c)
+ {
+ i++;
+ out.append(indent).append(" +- ");
+
+ if (o instanceof Dumpable)
+ ((Dumpable)o).dump(out, indent + (i == size ? " " : " | "));
+ else
+ dumpObject(out, o);
+ }
+ }
+ }
+
+
+ enum Managed { POJO, MANAGED, UNMANAGED, AUTO };
+
+ private static class Bean
+ {
+ private final Object _bean;
+ private volatile Managed _managed = Managed.POJO;
+
+ private Bean(Object b)
+ {
+ _bean = b;
+ }
+
+ public boolean isManaged()
+ {
+ return _managed==Managed.MANAGED;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("{%s,%s}", _bean, _managed);
+ }
+ }
+
+ public void updateBean(Object oldBean, final Object newBean)
+ {
+ if (newBean!=oldBean)
+ {
+ if (oldBean!=null)
+ removeBean(oldBean);
+ if (newBean!=null)
+ addBean(newBean);
+ }
+ }
+
+ public void updateBeans(Object[] oldBeans, final Object[] newBeans)
+ {
+ // remove oldChildren not in newChildren
+ if (oldBeans!=null)
+ {
+ loop: for (Object o:oldBeans)
+ {
+ if (newBeans!=null)
+ {
+ for (Object n:newBeans)
+ if (o==n)
+ continue loop;
+ }
+ removeBean(o);
+ }
+ }
+
+ // add new beans not in old
+ if (newBeans!=null)
+ {
+ loop: for (Object n:newBeans)
+ {
+ if (oldBeans!=null)
+ {
+ for (Object o:oldBeans)
+ if (o==n)
+ continue loop;
+ }
+ addBean(n);
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.component;
+
+
+/**
+ * <p>A Destroyable is an object which can be destroyed.</p>
+ * <p>Typically a Destroyable is a {@link LifeCycle} component that can hold onto
+ * resources over multiple start/stop cycles. A call to destroy will release all
+ * resources and will prevent any further start/stop cycles from being successful.</p>
+ */
+public interface Destroyable
+{
+ void destroy();
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.component;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
+@ManagedObject("Dumpable Object")
+public interface Dumpable
+{
+ @ManagedOperation(value="Dump the nested Object state as a String", impact="INFO")
+ String dump();
+
+ void dump(Appendable out,String indent) throws IOException;
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.component;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+public class FileDestroyable implements Destroyable
+{
+ private static final Logger LOG = Log.getLogger(FileDestroyable.class);
+ final List<File> _files = new ArrayList<File>();
+
+ public FileDestroyable()
+ {
+ }
+
+ public FileDestroyable(String file) throws IOException
+ {
+ _files.add(Resource.newResource(file).getFile());
+ }
+
+ public FileDestroyable(File file)
+ {
+ _files.add(file);
+ }
+
+ public void addFile(String file) throws IOException
+ {
+ try(Resource r = Resource.newResource(file);)
+ {
+ _files.add(r.getFile());
+ }
+ }
+
+ public void addFile(File file)
+ {
+ _files.add(file);
+ }
+
+ public void addFiles(Collection<File> files)
+ {
+ _files.addAll(files);
+ }
+
+ public void removeFile(String file) throws IOException
+ {
+ try(Resource r = Resource.newResource(file);)
+ {
+ _files.remove(r.getFile());
+ }
+ }
+
+ public void removeFile(File file)
+ {
+ _files.remove(file);
+ }
+
+ @Override
+ public void destroy()
+ {
+ for (File file : _files)
+ {
+ if (file.exists())
+ {
+ LOG.debug("Destroy {}",file);
+ IO.delete(file);
+ }
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.component;
+
+import java.io.FileWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A LifeCycle Listener that writes state changes to a file.
+ * <p>This can be used with the jetty.sh script to wait for successful startup.
+ */
+public class FileNoticeLifeCycleListener implements LifeCycle.Listener
+{
+ private static final Logger LOG = Log.getLogger(FileNoticeLifeCycleListener.class);
+
+ private final String _filename;
+
+ public FileNoticeLifeCycleListener(String filename)
+ {
+ _filename=filename;
+ }
+
+ private void writeState(String action, LifeCycle lifecycle)
+ {
+ try (Writer out = new FileWriter(_filename,true))
+ {
+ out.append(action).append(" ").append(lifecycle.toString()).append("\n");
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+ public void lifeCycleStarting(LifeCycle event)
+ {
+ writeState("STARTING",event);
+ }
+
+ public void lifeCycleStarted(LifeCycle event)
+ {
+ writeState("STARTED",event);
+ }
+
+ public void lifeCycleFailure(LifeCycle event, Throwable cause)
+ {
+ writeState("FAILED",event);
+ }
+
+ public void lifeCycleStopping(LifeCycle event)
+ {
+ writeState("STOPPING",event);
+ }
+
+ public void lifeCycleStopped(LifeCycle event)
+ {
+ writeState("STOPPED",event);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.component;
+
+import java.util.concurrent.Future;
+
+/* ------------------------------------------------------------ */
+/* A Lifecycle that can be gracefully shutdown.
+ */
+public interface Graceful
+{
+ public Future<Void> shutdown();
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.component;
+
+import java.util.EventListener;
+
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+
+/* ------------------------------------------------------------ */
+/**
+ * The lifecycle interface for generic components.
+ * <br />
+ * Classes implementing this interface have a defined life cycle
+ * defined by the methods of this interface.
+ *
+ *
+ */
+@ManagedObject("Lifecycle Interface for startable components")
+public interface LifeCycle
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Starts the component.
+ * @throws Exception If the component fails to start
+ * @see #isStarted()
+ * @see #stop()
+ * @see #isFailed()
+ */
+ @ManagedOperation(value="Starts the instance", impact="ACTION")
+ public void start()
+ throws Exception;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Stops the component.
+ * The component may wait for current activities to complete
+ * normally, but it can be interrupted.
+ * @exception Exception If the component fails to stop
+ * @see #isStopped()
+ * @see #start()
+ * @see #isFailed()
+ */
+ @ManagedOperation(value="Stops the instance", impact="ACTION")
+ public void stop()
+ throws Exception;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component is starting or has been started.
+ */
+ public boolean isRunning();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component has been started.
+ * @see #start()
+ * @see #isStarting()
+ */
+ public boolean isStarted();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component is starting.
+ * @see #isStarted()
+ */
+ public boolean isStarting();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component is stopping.
+ * @see #isStopped()
+ */
+ public boolean isStopping();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component has been stopped.
+ * @see #stop()
+ * @see #isStopping()
+ */
+ public boolean isStopped();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if the component has failed to start or has failed to stop.
+ */
+ public boolean isFailed();
+
+ /* ------------------------------------------------------------ */
+ public void addLifeCycleListener(LifeCycle.Listener listener);
+
+ /* ------------------------------------------------------------ */
+ public void removeLifeCycleListener(LifeCycle.Listener listener);
+
+
+ /* ------------------------------------------------------------ */
+ /** Listener.
+ * A listener for Lifecycle events.
+ */
+ public interface Listener extends EventListener
+ {
+ public void lifeCycleStarting(LifeCycle event);
+ public void lifeCycleStarted(LifeCycle event);
+ public void lifeCycleFailure(LifeCycle event,Throwable cause);
+ public void lifeCycleStopping(LifeCycle event);
+ public void lifeCycleStopped(LifeCycle event);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Jetty Lifecycle Management
+ */
+package org.eclipse.jetty.util.component;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.log;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Logger.
+ * Manages the atomic registration of the logger by name.
+ */
+public abstract class AbstractLogger implements Logger
+{
+ @Override
+ public final Logger getLogger(String name)
+ {
+ if (isBlank(name))
+ return this;
+
+ final String basename = getName();
+ final String fullname = (isBlank(basename) || Log.getRootLogger()==this)?name:(basename + "." + name);
+
+ Logger logger = Log.getLoggers().get(fullname);
+ if (logger == null)
+ {
+ Logger newlog = newLogger(fullname);
+
+ logger = Log.getMutableLoggers().putIfAbsent(fullname,newlog);
+ if (logger == null)
+ logger=newlog;
+ }
+
+ return logger;
+ }
+
+
+ protected abstract Logger newLogger(String fullname);
+
+ /**
+ * A more robust form of name blank test. Will return true for null names, and names that have only whitespace
+ *
+ * @param name
+ * the name to test
+ * @return true for null or blank name, false if any non-whitespace character is found.
+ */
+ private static boolean isBlank(String name)
+ {
+ if (name == null)
+ {
+ return true;
+ }
+ int size = name.length();
+ char c;
+ for (int i = 0; i < size; i++)
+ {
+ c = name.charAt(i);
+ if (!Character.isWhitespace(c))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void debug(String msg, long arg)
+ {
+ if (isDebugEnabled())
+ debug(msg,new Long(arg));
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.log;
+
+import java.util.logging.Level;
+
+/**
+ * <p>
+ * Implementation of Jetty {@link Logger} based on {@link java.util.logging.Logger}.
+ * </p>
+ *
+ * <p>
+ * You can also set the logger level using <a href="http://java.sun.com/j2se/1.5.0/docs/guide/logging/overview.html">
+ * standard java.util.logging configuration</a>.
+ * </p>
+ */
+public class JavaUtilLog extends AbstractLogger
+{
+ private Level configuredLevel;
+ private java.util.logging.Logger _logger;
+
+ public JavaUtilLog()
+ {
+ this("org.eclipse.jetty.util.log");
+ }
+
+ public JavaUtilLog(String name)
+ {
+ _logger = java.util.logging.Logger.getLogger(name);
+ if (Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.DEBUG", "false")))
+ {
+ _logger.setLevel(Level.FINE);
+ }
+ configuredLevel = _logger.getLevel();
+ }
+
+ public String getName()
+ {
+ return _logger.getName();
+ }
+
+ public void warn(String msg, Object... args)
+ {
+ if (_logger.isLoggable(Level.WARNING))
+ _logger.log(Level.WARNING,format(msg,args));
+ }
+
+ public void warn(Throwable thrown)
+ {
+ warn("", thrown);
+ }
+
+ public void warn(String msg, Throwable thrown)
+ {
+ _logger.log(Level.WARNING, msg, thrown);
+ }
+
+ public void info(String msg, Object... args)
+ {
+ if (_logger.isLoggable(Level.INFO))
+ _logger.log(Level.INFO, format(msg, args));
+ }
+
+ public void info(Throwable thrown)
+ {
+ info("", thrown);
+ }
+
+ public void info(String msg, Throwable thrown)
+ {
+ _logger.log(Level.INFO, msg, thrown);
+ }
+
+ public boolean isDebugEnabled()
+ {
+ return _logger.isLoggable(Level.FINE);
+ }
+
+ public void setDebugEnabled(boolean enabled)
+ {
+ if (enabled)
+ {
+ configuredLevel = _logger.getLevel();
+ _logger.setLevel(Level.FINE);
+ }
+ else
+ {
+ _logger.setLevel(configuredLevel);
+ }
+ }
+
+ public void debug(String msg, Object... args)
+ {
+ if (_logger.isLoggable(Level.FINE))
+ _logger.log(Level.FINE,format(msg, args));
+ }
+
+ public void debug(String msg, long arg)
+ {
+ if (_logger.isLoggable(Level.FINE))
+ _logger.log(Level.FINE,format(msg, arg));
+ }
+
+ public void debug(Throwable thrown)
+ {
+ debug("", thrown);
+ }
+
+ public void debug(String msg, Throwable thrown)
+ {
+ _logger.log(Level.FINE, msg, thrown);
+ }
+
+ /**
+ * Create a Child Logger of this Logger.
+ */
+ protected Logger newLogger(String fullname)
+ {
+ return new JavaUtilLog(fullname);
+ }
+
+ public void ignore(Throwable ignored)
+ {
+ if (Log.isIgnored())
+ {
+ warn(Log.IGNORED, ignored);
+ }
+ }
+
+ private String format(String msg, Object... args)
+ {
+ msg = String.valueOf(msg); // Avoids NPE
+ String braces = "{}";
+ StringBuilder builder = new StringBuilder();
+ int start = 0;
+ for (Object arg : args)
+ {
+ int bracesIndex = msg.indexOf(braces, start);
+ if (bracesIndex < 0)
+ {
+ builder.append(msg.substring(start));
+ builder.append(" ");
+ builder.append(arg);
+ start = msg.length();
+ }
+ else
+ {
+ builder.append(msg.substring(start, bracesIndex));
+ builder.append(String.valueOf(arg));
+ start = bracesIndex + braces.length();
+ }
+ }
+ builder.append(msg.substring(start));
+ return builder.toString();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+
+/**
+ * Logging.
+ * This class provides a static logging interface. If an instance of the
+ * org.slf4j.Logger class is found on the classpath, the static log methods
+ * are directed to a slf4j logger for "org.eclipse.log". Otherwise the logs
+ * are directed to stderr.
+ * <p>
+ * The "org.eclipse.jetty.util.log.class" system property can be used
+ * to select a specific logging implementation.
+ * <p>
+ * If the system property org.eclipse.jetty.util.log.IGNORED is set,
+ * then ignored exceptions are logged in detail.
+ *
+ * @see StdErrLog
+ * @see Slf4jLog
+ */
+public class Log
+{
+ public final static String EXCEPTION= "EXCEPTION ";
+ public final static String IGNORED= "IGNORED ";
+
+ /**
+ * Logging Configuration Properties
+ */
+ protected static final Properties __props;
+ /**
+ * The {@link Logger} implementation class name
+ */
+ public static String __logClass;
+ /**
+ * Legacy flag indicating if {@link Logger#ignore(Throwable)} methods produce any output in the {@link Logger}s
+ */
+ public static boolean __ignored;
+
+ /**
+ * Hold loggers only.
+ */
+ private final static ConcurrentMap<String, Logger> __loggers = new ConcurrentHashMap<>();
+
+
+ static
+ {
+ /* Instantiate a default configuration properties (empty)
+ */
+ __props = new Properties();
+
+ AccessController.doPrivileged(new PrivilegedAction<Object>()
+ {
+ public Object run()
+ {
+ /* First see if the jetty-logging.properties object exists in the classpath.
+ * This is an optional feature used by embedded mode use, and test cases to allow for early
+ * configuration of the Log class in situations where access to the System.properties are
+ * either too late or just impossible.
+ */
+ loadProperties("jetty-logging.properties",__props);
+
+ /*
+ * Next see if an OS specific jetty-logging.properties object exists in the classpath.
+ * This really for setting up test specific logging behavior based on OS.
+ */
+ String osName = System.getProperty("os.name");
+ // NOTE: cannot use jetty-util's StringUtil as that initializes logging itself.
+ if (osName != null && osName.length() > 0)
+ {
+ osName = osName.toLowerCase(Locale.ENGLISH).replace(' ','-');
+ loadProperties("jetty-logging-" + osName + ".properties",__props);
+ }
+
+ /* Now load the System.properties as-is into the __props, these values will override
+ * any key conflicts in __props.
+ */
+ @SuppressWarnings("unchecked")
+ Enumeration<String> systemKeyEnum = (Enumeration<String>)System.getProperties().propertyNames();
+ while (systemKeyEnum.hasMoreElements())
+ {
+ String key = systemKeyEnum.nextElement();
+ String val = System.getProperty(key);
+ // protect against application code insertion of non-String values (returned as null)
+ if (val != null)
+ {
+ __props.setProperty(key,val);
+ }
+ }
+
+ /* Now use the configuration properties to configure the Log statics
+ */
+ __logClass = __props.getProperty("org.eclipse.jetty.util.log.class","org.eclipse.jetty.util.log.Slf4jLog");
+ __ignored = Boolean.parseBoolean(__props.getProperty("org.eclipse.jetty.util.log.IGNORED","false"));
+ return null;
+ }
+ });
+ }
+
+ private static void loadProperties(String resourceName, Properties props)
+ {
+ URL testProps = Loader.getResource(Log.class,resourceName);
+ if (testProps != null)
+ {
+ try (InputStream in = testProps.openStream())
+ {
+ Properties p = new Properties();
+ p.load(in);
+ for (Object key : p.keySet())
+ {
+ Object value = p.get(key);
+ if (value != null)
+ {
+ props.put(key,value);
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ System.err.println("[WARN] Error loading logging config: " + testProps);
+ e.printStackTrace(System.err);
+ }
+ }
+ }
+
+ private static Logger LOG;
+ private static boolean __initialized=false;
+
+ public static void initialized()
+ {
+ synchronized (Log.class)
+ {
+ if (__initialized)
+ return;
+ __initialized = true;
+
+ final long uptime=ManagementFactory.getRuntimeMXBean().getUptime();
+
+ try
+ {
+ Class<?> log_class = Loader.loadClass(Log.class, __logClass);
+ if (LOG == null || !LOG.getClass().equals(log_class))
+ {
+ LOG = (Logger)log_class.newInstance();
+ LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+ }
+ }
+ catch(Throwable e)
+ {
+ // Unable to load specified Logger implementation, default to standard logging.
+ initStandardLogging(e);
+ }
+
+ if (LOG!=null)
+ LOG.info(String.format("Logging initialized @%dms",uptime));
+ }
+ }
+
+ private static void initStandardLogging(Throwable e)
+ {
+ Class<?> log_class;
+ if(e != null && __ignored)
+ {
+ e.printStackTrace(System.err);
+ }
+
+ if (LOG == null)
+ {
+ log_class = StdErrLog.class;
+ LOG = new StdErrLog();
+ LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+ }
+ }
+
+ public static Logger getLog()
+ {
+ initialized();
+ return LOG;
+ }
+
+ public static void setLog(Logger log)
+ {
+ Log.LOG = log;
+ }
+
+ /**
+ * Get the root logger.
+ * @return the root logger
+ */
+ public static Logger getRootLogger() {
+ initialized();
+ return LOG;
+ }
+
+ static boolean isIgnored()
+ {
+ return __ignored;
+ }
+
+ /**
+ * Set Log to parent Logger.
+ * <p>
+ * If there is a different Log class available from a parent classloader,
+ * call {@link #getLogger(String)} on it and construct a {@link LoggerLog} instance
+ * as this Log's Logger, so that logging is delegated to the parent Log.
+ * <p>
+ * This should be used if a webapp is using Log, but wishes the logging to be
+ * directed to the containers log.
+ * <p>
+ * If there is not parent Log, then this call is equivalent to<pre>
+ * Log.setLog(Log.getLogger(name));
+ * </pre>
+ * @param name Logger name
+ */
+ public static void setLogToParent(String name)
+ {
+ ClassLoader loader = Log.class.getClassLoader();
+ if (loader!=null && loader.getParent()!=null)
+ {
+ try
+ {
+ Class<?> uberlog = loader.getParent().loadClass("org.eclipse.jetty.util.log.Log");
+ Method getLogger = uberlog.getMethod("getLogger", new Class[]{String.class});
+ Object logger = getLogger.invoke(null,name);
+ setLog(new LoggerLog(logger));
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ else
+ {
+ setLog(getLogger(name));
+ }
+ }
+
+ /**
+ * Obtain a named Logger based on the fully qualified class name.
+ *
+ * @param clazz
+ * the class to base the Logger name off of
+ * @return the Logger with the given name
+ */
+ public static Logger getLogger(Class<?> clazz)
+ {
+ return getLogger(clazz.getName());
+ }
+
+ /**
+ * Obtain a named Logger or the default Logger if null is passed.
+ * @param name the Logger name
+ * @return the Logger with the given name
+ */
+ public static Logger getLogger(String name)
+ {
+ initialized();
+
+ if(name==null)
+ return LOG;
+
+ Logger logger = __loggers.get(name);
+ if(logger==null)
+ logger = LOG.getLogger(name);
+
+ return logger;
+ }
+
+ static ConcurrentMap<String, Logger> getMutableLoggers()
+ {
+ return __loggers;
+ }
+
+ /**
+ * Get a map of all configured {@link Logger} instances.
+ *
+ * @return a map of all configured {@link Logger} instances
+ */
+ @ManagedAttribute("list of all instantiated loggers")
+ public static Map<String, Logger> getLoggers()
+ {
+ return Collections.unmodifiableMap(__loggers);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.log;
+
+/**
+ * A simple logging facade that is intended simply to capture the style of logging as used by Jetty.
+ */
+public interface Logger
+{
+ /**
+ * @return the name of this logger
+ */
+ public String getName();
+
+ /**
+ * Formats and logs at warn level.
+ * @param msg the formatting string
+ * @param args the optional arguments
+ */
+ public void warn(String msg, Object... args);
+
+ /**
+ * Logs the given Throwable information at warn level
+ * @param thrown the Throwable to log
+ */
+ public void warn(Throwable thrown);
+
+ /**
+ * Logs the given message at warn level, with Throwable information.
+ * @param msg the message to log
+ * @param thrown the Throwable to log
+ */
+ public void warn(String msg, Throwable thrown);
+
+ /**
+ * Formats and logs at info level.
+ * @param msg the formatting string
+ * @param args the optional arguments
+ */
+ public void info(String msg, Object... args);
+
+ /**
+ * Logs the given Throwable information at info level
+ * @param thrown the Throwable to log
+ */
+ public void info(Throwable thrown);
+
+ /**
+ * Logs the given message at info level, with Throwable information.
+ * @param msg the message to log
+ * @param thrown the Throwable to log
+ */
+ public void info(String msg, Throwable thrown);
+
+ /**
+ * @return whether the debug level is enabled
+ */
+ public boolean isDebugEnabled();
+
+ /**
+ * Mutator used to turn debug on programmatically.
+ * @param enabled whether to enable the debug level
+ */
+ public void setDebugEnabled(boolean enabled);
+
+ /**
+ * Formats and logs at debug level.
+ * @param msg the formatting string
+ * @param args the optional arguments
+ */
+ public void debug(String msg, Object... args);
+
+
+ /**
+ * Formats and logs at debug level.
+ * avoids autoboxing of integers
+ * @param msg the formatting string
+ * @param value long value
+ */
+ public void debug(String msg, long value);
+
+ /**
+ * Logs the given Throwable information at debug level
+ * @param thrown the Throwable to log
+ */
+ public void debug(Throwable thrown);
+
+ /**
+ * Logs the given message at debug level, with Throwable information.
+ * @param msg the message to log
+ * @param thrown the Throwable to log
+ */
+ public void debug(String msg, Throwable thrown);
+
+ /**
+ * @param name the name of the logger
+ * @return a logger with the given name
+ */
+ public Logger getLogger(String name);
+
+ /**
+ * Ignore an exception.
+ * <p>This should be used rather than an empty catch block.
+ */
+ public void ignore(Throwable ignored);
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.log;
+
+import java.lang.reflect.Method;
+
+/**
+ *
+ */
+public class LoggerLog extends AbstractLogger
+{
+ private final Object _logger;
+ private final Method _debugMT;
+ private final Method _debugMAA;
+ private final Method _infoMT;
+ private final Method _infoMAA;
+ private final Method _warnMT;
+ private final Method _warnMAA;
+ private final Method _setDebugEnabledE;
+ private final Method _getLoggerN;
+ private final Method _getName;
+ private volatile boolean _debug;
+
+ public LoggerLog(Object logger)
+ {
+ try
+ {
+ _logger = logger;
+ Class<?> lc = logger.getClass();
+ _debugMT = lc.getMethod("debug", new Class[]{String.class, Throwable.class});
+ _debugMAA = lc.getMethod("debug", new Class[]{String.class, Object[].class});
+ _infoMT = lc.getMethod("info", new Class[]{String.class, Throwable.class});
+ _infoMAA = lc.getMethod("info", new Class[]{String.class, Object[].class});
+ _warnMT = lc.getMethod("warn", new Class[]{String.class, Throwable.class});
+ _warnMAA = lc.getMethod("warn", new Class[]{String.class, Object[].class});
+ Method _isDebugEnabled = lc.getMethod("isDebugEnabled");
+ _setDebugEnabledE = lc.getMethod("setDebugEnabled", new Class[]{Boolean.TYPE});
+ _getLoggerN = lc.getMethod("getLogger", new Class[]{String.class});
+ _getName = lc.getMethod("getName");
+
+ _debug = (Boolean)_isDebugEnabled.invoke(_logger);
+ }
+ catch(Exception x)
+ {
+ throw new IllegalStateException(x);
+ }
+ }
+
+ public String getName()
+ {
+ try
+ {
+ return (String)_getName.invoke(_logger);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public void warn(String msg, Object... args)
+ {
+ try
+ {
+ _warnMAA.invoke(_logger, args);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void warn(Throwable thrown)
+ {
+ warn("", thrown);
+ }
+
+ public void warn(String msg, Throwable thrown)
+ {
+ try
+ {
+ _warnMT.invoke(_logger, msg, thrown);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void info(String msg, Object... args)
+ {
+ try
+ {
+ _infoMAA.invoke(_logger, args);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void info(Throwable thrown)
+ {
+ info("", thrown);
+ }
+
+ public void info(String msg, Throwable thrown)
+ {
+ try
+ {
+ _infoMT.invoke(_logger, msg, thrown);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public boolean isDebugEnabled()
+ {
+ return _debug;
+ }
+
+ public void setDebugEnabled(boolean enabled)
+ {
+ try
+ {
+ _setDebugEnabledE.invoke(_logger, enabled);
+ _debug = enabled;
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+
+ public void debug(String msg, Object... args)
+ {
+ if (!_debug)
+ return;
+
+ try
+ {
+ _debugMAA.invoke(_logger, args);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void debug(Throwable thrown)
+ {
+ debug("", thrown);
+ }
+
+ public void debug(String msg, Throwable th)
+ {
+ if (!_debug)
+ return;
+
+ try
+ {
+ _debugMT.invoke(_logger, msg, th);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void debug(String msg, long value)
+ {
+ if (!_debug)
+ return;
+
+ try
+ {
+ _debugMAA.invoke(_logger, new Object[]{new Long(value)});
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void ignore(Throwable ignored)
+ {
+ if (Log.isIgnored())
+ {
+ warn(Log.IGNORED, ignored);
+ }
+ }
+
+ /**
+ * Create a Child Logger of this Logger.
+ */
+ protected Logger newLogger(String fullname)
+ {
+ try
+ {
+ Object logger=_getLoggerN.invoke(_logger, fullname);
+ return new LoggerLog(logger);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return this;
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.log;
+
+/**
+ * A try-with-resources compatible layer for {@link StdErrLog#setHideStacks(boolean) hiding stacktraces} within the scope of the <code>try</code> block when
+ * logging with {@link StdErrLog} implementation.
+ * <p>
+ * Use of other logging implementation cause no effect when using this class
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * try (StacklessLogging scope = new StacklessLogging(EventDriver.class,Noisy.class))
+ * {
+ * doActionThatCausesStackTraces();
+ * }
+ * </pre>
+ */
+public class StacklessLogging implements AutoCloseable
+{
+ private final Class<?> clazzes[];
+
+ public StacklessLogging(Class<?>... classesToSquelch)
+ {
+ this.clazzes = classesToSquelch;
+ hideStacks(true);
+ }
+
+ @Override
+ public void close() throws Exception
+ {
+ hideStacks(false);
+ }
+
+ private void hideStacks(boolean hide)
+ {
+ for (Class<?> clazz : clazzes)
+ {
+ Logger log = Log.getLogger(clazz);
+ if (log == null)
+ {
+ // not interested in classes without loggers
+ continue;
+ }
+ if (log instanceof StdErrLog)
+ {
+ // only operate on loggers that are of type StdErrLog
+ ((StdErrLog)log).setHideStacks(hide);
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.log;
+
+import java.io.PrintStream;
+import java.security.AccessControlException;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+/**
+ * StdErr Logging implementation.
+ * <p>
+ * A Jetty {@link Logger} that sends all logs to STDERR ({@link System#err}) with basic formatting.
+ * <p>
+ * Supports named loggers, and properties based configuration.
+ * <p>
+ * Configuration Properties:
+ * <dl>
+ * <dt>${name|hierarchy}.LEVEL=(ALL|DEBUG|INFO|WARN|OFF)</dt>
+ * <dd>
+ * Sets the level that the Logger should log at.<br/>
+ * Names can be a package name, or a fully qualified class name.<br/>
+ * Default: INFO<br/>
+ * <br/>
+ * Examples:
+ * <dl>
+ * <dt>org.eclipse.jetty.LEVEL=WARN</dt>
+ * <dd>indicates that all of the jetty specific classes, in any package that
+ * starts with <code>org.eclipse.jetty</code> should log at level WARN.</dd>
+ * <dt>org.eclipse.jetty.io.ChannelEndPoint.LEVEL=ALL</dt>
+ * <dd>indicates that the specific class, ChannelEndPoint, should log all
+ * logging events that it can generate, including DEBUG, INFO, WARN (and even special
+ * internally ignored exception cases).</dd>
+ * </dl>
+ * </dd>
+ *
+ * <dt>${name}.SOURCE=(true|false)</dt>
+ * <dd>
+ * Logger specific, attempt to print the java source file name and line number
+ * where the logging event originated from.<br/>
+ * Name must be a fully qualified class name (package name hierarchy is not supported
+ * by this configurable)<br/>
+ * Warning: this is a slow operation and will have an impact on performance!<br/>
+ * Default: false
+ * </dd>
+ *
+ * <dt>${name}.STACKS=(true|false)</dt>
+ * <dd>
+ * Logger specific, control the display of stacktraces.<br/>
+ * Name must be a fully qualified class name (package name hierarchy is not supported
+ * by this configurable)<br/>
+ * Default: true
+ * </dd>
+ *
+ * <dt>org.eclipse.jetty.util.log.stderr.SOURCE=(true|false)</dt>
+ * <dd>Special Global Configuration, attempt to print the java source file name and line number
+ * where the logging event originated from.<br/>
+ * Default: false
+ * </dd>
+ *
+ * <dt>org.eclipse.jetty.util.log.stderr.LONG=(true|false)</dt>
+ * <dd>Special Global Configuration, when true, output logging events to STDERR using
+ * long form, fully qualified class names. when false, use abbreviated package names<br/>
+ * Default: false
+ * </dd>
+ * <dt>org.eclipse.jetty.util.log.stderr.ESCAPE=(true|false)</dt>
+ * <dd>Global Configuration, when true output logging events to STDERR are always
+ * escaped so that control characters are replaced with '?"; '\r' with '<' and '\n' replaced '|'<br/>
+ * Default: true
+ * </dd>
+ * </dl>
+ */
+@ManagedObject("Jetty StdErr Logging Implementation")
+public class StdErrLog extends AbstractLogger
+{
+ private static final String EOL = System.getProperty("line.separator");
+ private static DateCache _dateCache;
+ private static final Properties __props = new Properties();
+
+ private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE",
+ Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false")));
+ private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false"));
+ private final static boolean __escape = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.ESCAPE","true"));
+
+ static
+ {
+ __props.putAll(Log.__props);
+
+ String deprecatedProperties[] =
+ { "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" };
+
+ // Toss a message to users about deprecated system properties
+ for (String deprecatedProp : deprecatedProperties)
+ {
+ if (System.getProperty(deprecatedProp) != null)
+ {
+ System.err.printf("System Property [%s] has been deprecated! (Use org.eclipse.jetty.LEVEL=DEBUG instead)%n",deprecatedProp);
+ }
+ }
+
+ try
+ {
+ _dateCache = new DateCache("yyyy-MM-dd HH:mm:ss");
+ }
+ catch (Exception x)
+ {
+ x.printStackTrace(System.err);
+ }
+ }
+
+ public static final int LEVEL_ALL = 0;
+ public static final int LEVEL_DEBUG = 1;
+ public static final int LEVEL_INFO = 2;
+ public static final int LEVEL_WARN = 3;
+ public static final int LEVEL_OFF = 10;
+
+ private int _level = LEVEL_INFO;
+ // Level that this Logger was configured as (remembered in special case of .setDebugEnabled())
+ private int _configuredLevel;
+ private PrintStream _stderr = null;
+ private boolean _source = __source;
+ // Print the long form names, otherwise use abbreviated
+ private boolean _printLongNames = __long;
+ // The full log name, as provided by the system.
+ private final String _name;
+ // The abbreviated log name (used by default, unless _long is specified)
+ private final String _abbrevname;
+ private boolean _hideStacks = false;
+
+ /**
+ * Obtain a StdErrLog reference for the specified class, a convenience method used most often during testing to allow for control over a specific logger.
+ * <p>
+ * Must be actively using StdErrLog as the Logger implementation.
+ *
+ * @param clazz
+ * the Class reference for the logger to use.
+ * @return the StdErrLog logger
+ * @throws RuntimeException
+ * if StdErrLog is not the active Logger implementation.
+ */
+ public static StdErrLog getLogger(Class<?> clazz)
+ {
+ Logger log = Log.getLogger(clazz);
+ if (log instanceof StdErrLog)
+ {
+ return (StdErrLog)log;
+ }
+ throw new RuntimeException("Logger for " + clazz + " is not of type StdErrLog");
+ }
+
+ /**
+ * Construct an anonymous StdErrLog (no name).
+ * <p>
+ * NOTE: Discouraged usage!
+ */
+ public StdErrLog()
+ {
+ this(null);
+ }
+
+ /**
+ * Construct a named StdErrLog using the {@link Log} defined properties
+ *
+ * @param name
+ * the name of the logger
+ */
+ public StdErrLog(String name)
+ {
+ this(name,__props);
+ }
+
+ /**
+ * Construct a named Logger using the provided properties to configure logger.
+ *
+ * @param name
+ * the name of the logger
+ * @param props
+ * the configuration properties
+ */
+ public StdErrLog(String name, Properties props)
+ {
+ if (props!=null && props!=__props)
+ __props.putAll(props);
+ this._name = name == null?"":name;
+ this._abbrevname = condensePackageString(this._name);
+ this._level = getLoggingLevel(props,this._name);
+ this._configuredLevel = this._level;
+
+ try
+ {
+ String source = getLoggingProperty(props,_name,"SOURCE");
+ _source = source==null?__source:Boolean.parseBoolean(source);
+ }
+ catch (AccessControlException ace)
+ {
+ _source = __source;
+ }
+
+ try
+ {
+ // allow stacktrace display to be controlled by properties as well
+ String stacks = getLoggingProperty(props,_name,"STACKS");
+ _hideStacks = stacks==null?false:!Boolean.parseBoolean(stacks);
+ }
+ catch (AccessControlException ignore)
+ {
+ /* ignore */
+ }
+ }
+
+ /**
+ * Get the Logging Level for the provided log name. Using the FQCN first, then each package segment from longest to
+ * shortest.
+ *
+ * @param props
+ * the properties to check
+ * @param name
+ * the name to get log for
+ * @return the logging level
+ */
+ public static int getLoggingLevel(Properties props, final String name)
+ {
+ // Calculate the level this named logger should operate under.
+ // Checking with FQCN first, then each package segment from longest to shortest.
+ String nameSegment = name;
+
+ while ((nameSegment != null) && (nameSegment.length() > 0))
+ {
+ String levelStr = props.getProperty(nameSegment + ".LEVEL");
+ // System.err.printf("[StdErrLog.CONFIG] Checking for property [%s.LEVEL] = %s%n",nameSegment,levelStr);
+ int level = getLevelId(nameSegment + ".LEVEL",levelStr);
+ if (level != (-1))
+ {
+ return level;
+ }
+
+ // Trim and try again.
+ int idx = nameSegment.lastIndexOf('.');
+ if (idx >= 0)
+ {
+ nameSegment = nameSegment.substring(0,idx);
+ }
+ else
+ {
+ nameSegment = null;
+ }
+ }
+
+ // Default Logging Level
+ return getLevelId("log.LEVEL",props.getProperty("log.LEVEL","INFO"));
+ }
+
+ public static String getLoggingProperty(Properties props, String name, String property)
+ {
+ // Calculate the level this named logger should operate under.
+ // Checking with FQCN first, then each package segment from longest to shortest.
+ String nameSegment = name;
+
+ while ((nameSegment != null) && (nameSegment.length() > 0))
+ {
+ String s = props.getProperty(nameSegment+"."+property);
+ if (s!=null)
+ return s;
+
+ // Trim and try again.
+ int idx = nameSegment.lastIndexOf('.');
+ nameSegment = (idx >= 0)?nameSegment.substring(0,idx):null;
+ }
+
+ return null;
+ }
+
+ protected static int getLevelId(String levelSegment, String levelName)
+ {
+ if (levelName == null)
+ {
+ return -1;
+ }
+ String levelStr = levelName.trim();
+ if ("ALL".equalsIgnoreCase(levelStr))
+ {
+ return LEVEL_ALL;
+ }
+ else if ("DEBUG".equalsIgnoreCase(levelStr))
+ {
+ return LEVEL_DEBUG;
+ }
+ else if ("INFO".equalsIgnoreCase(levelStr))
+ {
+ return LEVEL_INFO;
+ }
+ else if ("WARN".equalsIgnoreCase(levelStr))
+ {
+ return LEVEL_WARN;
+ }
+ else if ("OFF".equalsIgnoreCase(levelStr))
+ {
+ return LEVEL_OFF;
+ }
+
+ System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN, OFF] as values.");
+ return -1;
+ }
+
+ /**
+ * Condenses a classname by stripping down the package name to just the first character of each package name
+ * segment.Configured
+ * <p>
+ *
+ * <pre>
+ * Examples:
+ * "org.eclipse.jetty.test.FooTest" = "oejt.FooTest"
+ * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest"
+ * </pre>
+ *
+ * @param classname
+ * the fully qualified class name
+ * @return the condensed name
+ */
+ protected static String condensePackageString(String classname)
+ {
+ String parts[] = classname.split("\\.");
+ StringBuilder dense = new StringBuilder();
+ for (int i = 0; i < (parts.length - 1); i++)
+ {
+ dense.append(parts[i].charAt(0));
+ }
+ if (dense.length() > 0)
+ {
+ dense.append('.');
+ }
+ dense.append(parts[parts.length - 1]);
+ return dense.toString();
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public void setPrintLongNames(boolean printLongNames)
+ {
+ this._printLongNames = printLongNames;
+ }
+
+ public boolean isPrintLongNames()
+ {
+ return this._printLongNames;
+ }
+
+ public boolean isHideStacks()
+ {
+ return _hideStacks;
+ }
+
+ public void setHideStacks(boolean hideStacks)
+ {
+ _hideStacks = hideStacks;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Is the source of a log, logged
+ *
+ * @return true if the class, method, file and line number of a log is logged.
+ */
+ public boolean isSource()
+ {
+ return _source;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set if a log source is logged.
+ *
+ * @param source
+ * true if the class, method, file and line number of a log is logged.
+ */
+ public void setSource(boolean source)
+ {
+ _source = source;
+ }
+
+ public void warn(String msg, Object... args)
+ {
+ if (_level <= LEVEL_WARN)
+ {
+ StringBuilder buffer = new StringBuilder(64);
+ format(buffer,":WARN:",msg,args);
+ (_stderr==null?System.err:_stderr).println(buffer);
+ }
+ }
+
+ public void warn(Throwable thrown)
+ {
+ warn("",thrown);
+ }
+
+ public void warn(String msg, Throwable thrown)
+ {
+ if (_level <= LEVEL_WARN)
+ {
+ StringBuilder buffer = new StringBuilder(64);
+ format(buffer,":WARN:",msg,thrown);
+ (_stderr==null?System.err:_stderr).println(buffer);
+ }
+ }
+
+ public void info(String msg, Object... args)
+ {
+ if (_level <= LEVEL_INFO)
+ {
+ StringBuilder buffer = new StringBuilder(64);
+ format(buffer,":INFO:",msg,args);
+ (_stderr==null?System.err:_stderr).println(buffer);
+ }
+ }
+
+ public void info(Throwable thrown)
+ {
+ info("",thrown);
+ }
+
+ public void info(String msg, Throwable thrown)
+ {
+ if (_level <= LEVEL_INFO)
+ {
+ StringBuilder buffer = new StringBuilder(64);
+ format(buffer,":INFO:",msg,thrown);
+ (_stderr==null?System.err:_stderr).println(buffer);
+ }
+ }
+
+ @ManagedAttribute("is debug enabled for root logger Log.LOG")
+ public boolean isDebugEnabled()
+ {
+ return (_level <= LEVEL_DEBUG);
+ }
+
+ /**
+ * Legacy interface where a programmatic configuration of the logger level
+ * is done as a wholesale approach.
+ */
+ @Override
+ public void setDebugEnabled(boolean enabled)
+ {
+ if (enabled)
+ {
+ this._level = LEVEL_DEBUG;
+
+ for (Logger log : Log.getLoggers().values())
+ {
+ if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
+ ((StdErrLog)log).setLevel(LEVEL_DEBUG);
+ }
+ }
+ else
+ {
+ this._level = this._configuredLevel;
+
+ for (Logger log : Log.getLoggers().values())
+ {
+ if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
+ ((StdErrLog)log).setLevel(((StdErrLog)log)._configuredLevel);
+ }
+ }
+ }
+
+ public int getLevel()
+ {
+ return _level;
+ }
+
+ /**
+ * Set the level for this logger.
+ * <p>
+ * Available values ({@link StdErrLog#LEVEL_ALL}, {@link StdErrLog#LEVEL_DEBUG}, {@link StdErrLog#LEVEL_INFO},
+ * {@link StdErrLog#LEVEL_WARN})
+ *
+ * @param level
+ * the level to set the logger to
+ */
+ public void setLevel(int level)
+ {
+ this._level = level;
+ }
+
+ public void setStdErrStream(PrintStream stream)
+ {
+ this._stderr = stream==System.err?null:stream;
+ }
+
+ public void debug(String msg, Object... args)
+ {
+ if (_level <= LEVEL_DEBUG)
+ {
+ StringBuilder buffer = new StringBuilder(64);
+ format(buffer,":DBUG:",msg,args);
+ (_stderr==null?System.err:_stderr).println(buffer);
+ }
+ }
+
+ public void debug(String msg, long arg)
+ {
+ if (isDebugEnabled())
+ {
+ StringBuilder buffer = new StringBuilder(64);
+ format(buffer,":DBUG:",msg,arg);
+ (_stderr==null?System.err:_stderr).println(buffer);
+ }
+ }
+
+ public void debug(Throwable thrown)
+ {
+ debug("",thrown);
+ }
+
+ public void debug(String msg, Throwable thrown)
+ {
+ if (_level <= LEVEL_DEBUG)
+ {
+ StringBuilder buffer = new StringBuilder(64);
+ format(buffer,":DBUG:",msg,thrown);
+ (_stderr==null?System.err:_stderr).println(buffer);
+ }
+ }
+
+ private void format(StringBuilder buffer, String level, String msg, Object... args)
+ {
+ long now = System.currentTimeMillis();
+ int ms=(int)(now%1000);
+ String d = _dateCache.formatNow(now);
+ tag(buffer,d,ms,level);
+ format(buffer,msg,args);
+ }
+
+ private void format(StringBuilder buffer, String level, String msg, Throwable thrown)
+ {
+ format(buffer,level,msg);
+ if (isHideStacks())
+ {
+ format(buffer,": "+String.valueOf(thrown));
+ }
+ else
+ {
+ format(buffer,thrown);
+ }
+ }
+
+ private void tag(StringBuilder buffer, String d, int ms, String tag)
+ {
+ buffer.setLength(0);
+ buffer.append(d);
+ if (ms > 99)
+ {
+ buffer.append('.');
+ }
+ else if (ms > 9)
+ {
+ buffer.append(".0");
+ }
+ else
+ {
+ buffer.append(".00");
+ }
+ buffer.append(ms).append(tag);
+ if (_printLongNames)
+ {
+ buffer.append(_name);
+ }
+ else
+ {
+ buffer.append(_abbrevname);
+ }
+ buffer.append(':');
+ buffer.append(Thread.currentThread().getName()).append(": ");
+ if (_source)
+ {
+ Throwable source = new Throwable();
+ StackTraceElement[] frames = source.getStackTrace();
+ for (int i = 0; i < frames.length; i++)
+ {
+ final StackTraceElement frame = frames[i];
+ String clazz = frame.getClassName();
+ if (clazz.equals(StdErrLog.class.getName()) || clazz.equals(Log.class.getName()))
+ {
+ continue;
+ }
+ if (!_printLongNames && clazz.startsWith("org.eclipse.jetty."))
+ {
+ buffer.append(condensePackageString(clazz));
+ }
+ else
+ {
+ buffer.append(clazz);
+ }
+ buffer.append('#').append(frame.getMethodName());
+ if (frame.getFileName() != null)
+ {
+ buffer.append('(').append(frame.getFileName()).append(':').append(frame.getLineNumber()).append(')');
+ }
+ buffer.append(':');
+ break;
+ }
+ }
+ }
+
+ private void format(StringBuilder builder, String msg, Object... args)
+ {
+ if (msg == null)
+ {
+ msg = "";
+ for (int i = 0; i < args.length; i++)
+ {
+ msg += "{} ";
+ }
+ }
+ String braces = "{}";
+ int start = 0;
+ for (Object arg : args)
+ {
+ int bracesIndex = msg.indexOf(braces,start);
+ if (bracesIndex < 0)
+ {
+ escape(builder,msg.substring(start));
+ builder.append(" ");
+ builder.append(arg);
+ start = msg.length();
+ }
+ else
+ {
+ escape(builder,msg.substring(start,bracesIndex));
+ builder.append(String.valueOf(arg));
+ start = bracesIndex + braces.length();
+ }
+ }
+ escape(builder,msg.substring(start));
+ }
+
+ private void escape(StringBuilder builder, String string)
+ {
+ if (__escape)
+ {
+ for (int i = 0; i < string.length(); ++i)
+ {
+ char c = string.charAt(i);
+ if (Character.isISOControl(c))
+ {
+ if (c == '\n')
+ {
+ builder.append('|');
+ }
+ else if (c == '\r')
+ {
+ builder.append('<');
+ }
+ else
+ {
+ builder.append('?');
+ }
+ }
+ else
+ {
+ builder.append(c);
+ }
+ }
+ }
+ else
+ builder.append(string);
+ }
+
+ private void format(StringBuilder buffer, Throwable thrown)
+ {
+ if (thrown == null)
+ {
+ buffer.append("null");
+ }
+ else
+ {
+ buffer.append(EOL);
+ format(buffer,thrown.toString());
+ StackTraceElement[] elements = thrown.getStackTrace();
+ for (int i = 0; elements != null && i < elements.length; i++)
+ {
+ buffer.append(EOL).append("\tat ");
+ format(buffer,elements[i].toString());
+ }
+
+ Throwable cause = thrown.getCause();
+ if (cause != null && cause != thrown)
+ {
+ buffer.append(EOL).append("Caused by: ");
+ format(buffer,cause);
+ }
+ }
+ }
+
+
+ /**
+ * Create a Child Logger of this Logger.
+ */
+ @Override
+ protected Logger newLogger(String fullname)
+ {
+ StdErrLog logger = new StdErrLog(fullname);
+ // Preserve configuration for new loggers configuration
+ logger.setPrintLongNames(_printLongNames);
+ logger._stderr = this._stderr;
+
+ // Force the child to have any programmatic configuration
+ if (_level!=_configuredLevel)
+ logger._level=_level;
+
+ return logger;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder s = new StringBuilder();
+ s.append("StdErrLog:");
+ s.append(_name);
+ s.append(":LEVEL=");
+ switch (_level)
+ {
+ case LEVEL_ALL:
+ s.append("ALL");
+ break;
+ case LEVEL_DEBUG:
+ s.append("DEBUG");
+ break;
+ case LEVEL_INFO:
+ s.append("INFO");
+ break;
+ case LEVEL_WARN:
+ s.append("WARN");
+ break;
+ default:
+ s.append("?");
+ break;
+ }
+ return s.toString();
+ }
+
+ public static void setProperties(Properties props)
+ {
+ __props.clear();
+ __props.putAll(props);
+ }
+
+ public void ignore(Throwable ignored)
+ {
+ if (_level <= LEVEL_ALL)
+ {
+ StringBuilder buffer = new StringBuilder(64);
+ format(buffer,":IGNORED:","",ignored);
+ (_stderr==null?System.err:_stderr).println(buffer);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Common Logging Integrations
+ */
+package org.eclipse.jetty.util.log;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Common Utility Classes
+ */
+package org.eclipse.jetty.util;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+import java.awt.Toolkit;
+
+/**
+ * AWTLeakPreventer
+ *
+ * See https://issues.jboss.org/browse/AS7-3733
+ *
+ * The java.awt.Toolkit class has a static field that is the default toolkit.
+ * Creating the default toolkit causes the creation of an EventQueue, which has a
+ * classloader field initialized by the thread context class loader.
+ *
+ */
+public class AWTLeakPreventer extends AbstractLeakPreventer
+{
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+ */
+ @Override
+ public void prevent(ClassLoader loader)
+ {
+ LOG.debug("Pinning classloader for java.awt.EventQueue using "+loader);
+ Toolkit.getDefaultToolkit();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * AbstractLeakPreventer
+ *
+ * Abstract base class for code that seeks to avoid pinning of webapp classloaders by using the jetty classloader to
+ * proactively call the code that pins them (generally pinned as static data members, or as static
+ * data members that are daemon threads (which use the context classloader)).
+ *
+ * Instances of subclasses of this class should be set with Server.addBean(), which will
+ * ensure that they are called when the Server instance starts up, which will have the jetty
+ * classloader in scope.
+ *
+ */
+public abstract class AbstractLeakPreventer extends AbstractLifeCycle
+{
+ protected static final Logger LOG = Log.getLogger(AbstractLeakPreventer.class);
+
+ /* ------------------------------------------------------------ */
+ abstract public void prevent(ClassLoader loader);
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ try
+ {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ prevent(getClass().getClassLoader());
+ super.doStart();
+ }
+ finally
+ {
+ Thread.currentThread().setContextClassLoader(loader);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+import javax.imageio.ImageIO;
+
+/**
+ * AppContextLeakPreventer
+ *
+ * Cause the classloader that is pinned by AppContext.getAppContext() to be
+ * a container or system classloader, not a webapp classloader.
+ *
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ */
+public class AppContextLeakPreventer extends AbstractLeakPreventer
+{
+ /* ------------------------------------------------------------ */
+ @Override
+ public void prevent(ClassLoader loader)
+ {
+ LOG.debug("Pinning classloader for AppContext.getContext() with "+loader);
+ ImageIO.getUseCache();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * DOMLeakPreventer
+ *
+ * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6916498
+ *
+ * Prevent the RuntimeException that is a static member of AbstractDOMParser
+ * from pinning a webapp classloader by causing it to be set here by a non-webapp classloader.
+ *
+ * Note that according to the bug report, a heap dump may not identify the GCRoot, making
+ * it difficult to identify the cause of the leak.
+ *
+ */
+public class DOMLeakPreventer extends AbstractLeakPreventer
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+ */
+ @Override
+ public void prevent(ClassLoader loader)
+ {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ try
+ {
+ factory.newDocumentBuilder();
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+import java.sql.DriverManager;
+
+
+/**
+ * DriverManagerLeakPreventer
+ *
+ * Cause DriverManager.getCallerClassLoader() to be called, which will pin the classloader.
+ *
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ */
+public class DriverManagerLeakPreventer extends AbstractLeakPreventer
+{
+ /* ------------------------------------------------------------ */
+ @Override
+ public void prevent(ClassLoader loader)
+ {
+ LOG.debug("Pinning DriverManager classloader with "+loader);
+ DriverManager.getDrivers();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+import java.lang.reflect.Method;
+
+/**
+ * GCThreadLeakPreventer
+ *
+ * Prevents a call to sun.misc.GC.requestLatency pinning a webapp classloader
+ * by calling it with a non-webapp classloader. The problem appears to be that
+ * when this method is called, a daemon thread is created which takes the
+ * context classloader. A known caller of this method is the RMI impl. See
+ * http://stackoverflow.com/questions/6626680/does-java-garbage-collection-log-entry-full-gc-system-mean-some-class-called
+ *
+ * This preventer will start the thread with the longest possible interval, although
+ * subsequent calls can vary that. Recommend to only use this class if you're doing
+ * RMI.
+ *
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ *
+ */
+public class GCThreadLeakPreventer extends AbstractLeakPreventer
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+ */
+ @Override
+ public void prevent(ClassLoader loader)
+ {
+ try
+ {
+ Class clazz = Class.forName("sun.misc.GC");
+ Method requestLatency = clazz.getMethod("requestLatency", new Class[] {long.class});
+ requestLatency.invoke(null, Long.valueOf(Long.MAX_VALUE-1));
+ }
+ catch (ClassNotFoundException e)
+ {
+ LOG.ignore(e);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+/**
+ * Java2DLeakPreventer
+ *
+ * Prevent pinning of webapp classloader by pre-loading sun.java2d.Disposer class
+ * before webapp classloaders are created.
+ *
+ * See https://issues.apache.org/bugzilla/show_bug.cgi?id=51687
+ *
+ */
+public class Java2DLeakPreventer extends AbstractLeakPreventer
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+ */
+ @Override
+ public void prevent(ClassLoader loader)
+ {
+ try
+ {
+ Class.forName("sun.java2d.Disposer", true, loader);
+ }
+ catch (ClassNotFoundException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+/**
+ * LDAPLeakPreventer
+ *
+ * If com.sun.jndi.LdapPoolManager class is loaded and the system property
+ * com.sun.jndi.ldap.connect.pool.timeout is set to a nonzero value, a daemon
+ * thread is started which can pin a webapp classloader if it is the first to
+ * load the LdapPoolManager.
+ *
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ *
+ */
+public class LDAPLeakPreventer extends AbstractLeakPreventer
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+ */
+ @Override
+ public void prevent(ClassLoader loader)
+ {
+ try
+ {
+ Class.forName("com.sun.jndi.LdapPoolManager", true, loader);
+ }
+ catch (ClassNotFoundException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+/**
+ * LoginConfigurationLeakPreventer
+ *
+ * The javax.security.auth.login.Configuration class keeps a static reference to the
+ * thread context classloader. We prevent a webapp context classloader being used for
+ * that by invoking the classloading here.
+ *
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ */
+public class LoginConfigurationLeakPreventer extends AbstractLeakPreventer
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+ */
+ @Override
+ public void prevent(ClassLoader loader)
+ {
+ try
+ {
+ Class.forName("javax.security.auth.login.Configuration", true, loader);
+ }
+ catch (ClassNotFoundException e)
+ {
+ LOG.warn(e);
+ }
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.preventers;
+
+import java.security.Security;
+
+/**
+ * SecurityProviderLeakPreventer
+ *
+ * Some security providers, such as sun.security.pkcs11.SunPKCS11 start a deamon thread,
+ * which will use the thread context classloader. Load them here to ensure the classloader
+ * is not a webapp classloader.
+ *
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ */
+public class SecurityProviderLeakPreventer extends AbstractLeakPreventer
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+ */
+ @Override
+ public void prevent(ClassLoader loader)
+ {
+ Security.getProviders();
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Common Memory Leak Prevention Tooling
+ */
+package org.eclipse.jetty.util.preventers;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.resource;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+
+/* ------------------------------------------------------------ */
+/** Bad Resource.
+ *
+ * A Resource that is returned for a bade URL. Acts as a resource
+ * that does not exist and throws appropriate exceptions.
+ *
+ *
+ */
+class BadResource extends URLResource
+{
+ /* ------------------------------------------------------------ */
+ private String _message=null;
+
+ /* -------------------------------------------------------- */
+ BadResource(URL url, String message)
+ {
+ super(url,null);
+ _message=message;
+ }
+
+
+ /* -------------------------------------------------------- */
+ @Override
+ public boolean exists()
+ {
+ return false;
+ }
+
+ /* -------------------------------------------------------- */
+ @Override
+ public long lastModified()
+ {
+ return -1;
+ }
+
+ /* -------------------------------------------------------- */
+ @Override
+ public boolean isDirectory()
+ {
+ return false;
+ }
+
+ /* --------------------------------------------------------- */
+ @Override
+ public long length()
+ {
+ return -1;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public File getFile()
+ {
+ return null;
+ }
+
+ /* --------------------------------------------------------- */
+ @Override
+ public InputStream getInputStream() throws IOException
+ {
+ throw new FileNotFoundException(_message);
+ }
+
+ /* --------------------------------------------------------- */
+ @Override
+ public boolean delete()
+ throws SecurityException
+ {
+ throw new SecurityException(_message);
+ }
+
+ /* --------------------------------------------------------- */
+ @Override
+ public boolean renameTo( Resource dest)
+ throws SecurityException
+ {
+ throw new SecurityException(_message);
+ }
+
+ /* --------------------------------------------------------- */
+ @Override
+ public String[] list()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void copyTo(File destination)
+ throws IOException
+ {
+ throw new SecurityException(_message);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return super.toString()+"; BadResource="+_message;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+
+/**
+ * EmptyResource
+ *
+ * Represents a resource that does does not refer to any file, url, jar etc.
+ */
+public class EmptyResource extends Resource
+{
+ public static final Resource INSTANCE = new EmptyResource();
+
+ private EmptyResource()
+ {
+ }
+
+ @Override
+ public boolean isContainedIn(Resource r) throws MalformedURLException
+ {
+ return false;
+ }
+
+ @Override
+ public void close()
+ {
+ }
+
+ @Override
+ public boolean exists()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isDirectory()
+ {
+ return false;
+ }
+
+ @Override
+ public long lastModified()
+ {
+ return 0;
+ }
+
+ @Override
+ public long length()
+ {
+ return 0;
+ }
+
+ @Override
+ public URL getURL()
+ {
+ return null;
+ }
+
+ @Override
+ public File getFile() throws IOException
+ {
+ return null;
+ }
+
+ @Override
+ public String getName()
+ {
+ return null;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException
+ {
+ return null;
+ }
+
+ @Override
+ public ReadableByteChannel getReadableByteChannel() throws IOException
+ {
+ return null;
+ }
+
+ @Override
+ public boolean delete() throws SecurityException
+ {
+ return false;
+ }
+
+ @Override
+ public boolean renameTo(Resource dest) throws SecurityException
+ {
+ return false;
+ }
+
+ @Override
+ public String[] list()
+ {
+ return null;
+ }
+
+ @Override
+ public Resource addPath(String path) throws IOException, MalformedURLException
+ {
+ return null;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.StandardOpenOption;
+import java.security.Permission;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** File Resource.
+ *
+ * Handle resources of implied or explicit file type.
+ * This class can check for aliasing in the filesystem (eg case
+ * insensitivity). By default this is turned on, or it can be controlled
+ * by calling the static method @see FileResource#setCheckAliases(boolean)
+ *
+ */
+public class FileResource extends Resource
+{
+ private static final Logger LOG = Log.getLogger(FileResource.class);
+
+ /* ------------------------------------------------------------ */
+ private final File _file;
+ private final String _uri;
+ private final URI _alias;
+
+ /* -------------------------------------------------------- */
+ public FileResource(URL url)
+ throws IOException, URISyntaxException
+ {
+ File file;
+ try
+ {
+ // Try standard API to convert URL to file.
+ file =new File(url.toURI());
+ }
+ catch (URISyntaxException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ if (!url.toString().startsWith("file:"))
+ throw new IllegalArgumentException("!file:");
+
+ LOG.ignore(e);
+ try
+ {
+ // Assume that File.toURL produced unencoded chars. So try encoding them.
+ String file_url="file:"+URIUtil.encodePath(url.toString().substring(5));
+ URI uri = new URI(file_url);
+ if (uri.getAuthority()==null)
+ file = new File(uri);
+ else
+ file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile()));
+ }
+ catch (Exception e2)
+ {
+ LOG.ignore(e2);
+ // Still can't get the file. Doh! try good old hack!
+ URLConnection connection=url.openConnection();
+ Permission perm = connection.getPermission();
+ file = new File(perm==null?url.getFile():perm.getName());
+ }
+ }
+
+ _file=file;
+ _uri=normalizeURI(_file,url.toURI());
+ _alias=checkAlias(_file);
+ }
+
+ /* -------------------------------------------------------- */
+ public FileResource(URI uri)
+ {
+ File file=new File(uri);
+ _file=file;
+ URI file_uri=_file.toURI();
+ _uri=normalizeURI(_file,uri);
+
+ if (!_uri.equals(file_uri.toString()))
+ {
+ // URI and File URI are different. Is it just an encoding difference?
+ if (!file_uri.toString().equals(URIUtil.decodePath(uri.toString())))
+ _alias=_file.toURI();
+ else
+ _alias=checkAlias(_file);
+ }
+ else
+ _alias=checkAlias(_file);
+ }
+
+ /* -------------------------------------------------------- */
+ FileResource(File file)
+ {
+ _file=file;
+ _uri=normalizeURI(_file,_file.toURI());
+ _alias=checkAlias(_file);
+ }
+
+ /* -------------------------------------------------------- */
+ private static String normalizeURI(File file, URI uri)
+ {
+ String u =uri.toASCIIString();
+ if (file.isDirectory())
+ {
+ if(!u.endsWith("/"))
+ u+="/";
+ }
+ else if (file.exists() && u.endsWith("/"))
+ u=u.substring(0,u.length()-1);
+ return u;
+ }
+
+ /* -------------------------------------------------------- */
+ private static URI checkAlias(File file)
+ {
+ try
+ {
+ String abs=file.getAbsolutePath();
+ String can=file.getCanonicalPath();
+
+ if (!abs.equals(can))
+ {
+ LOG.debug("ALIAS abs={} can={}",abs,can);
+
+ URI alias=new File(can).toURI();
+ // Have to encode the path as File.toURI does not!
+ return new URI("file://"+URIUtil.encodePath(alias.getPath()));
+ }
+ }
+ catch(Exception e)
+ {
+ LOG.warn("bad alias for {}: {}",file,e.toString());
+ LOG.debug(e);
+ try
+ {
+ return new URI("http://eclipse.org/bad/canonical/alias");
+ }
+ catch(Exception e2)
+ {
+ LOG.ignore(e2);
+ throw new RuntimeException(e);
+ }
+ }
+
+ return null;
+ }
+
+ /* -------------------------------------------------------- */
+ @Override
+ public Resource addPath(String path)
+ throws IOException,MalformedURLException
+ {
+ path = org.eclipse.jetty.util.URIUtil.canonicalPath(path);
+
+ if (path==null)
+ throw new MalformedURLException();
+
+ if ("/".equals(path))
+ return this;
+
+ path=URIUtil.encodePath(path);
+ // The encoded path should be a suffix of the resource (give or take a directory / )
+ URI uri;
+ try
+ {
+ if (_file.isDirectory())
+ {
+ // treat all paths being added as relative
+ uri=new URI(URIUtil.addPaths(_uri,path));
+ }
+ else
+ {
+ uri=new URI(_uri+path);
+ }
+ }
+ catch(final URISyntaxException e)
+ {
+ throw new MalformedURLException(){{initCause(e);}};
+ }
+
+ return new FileResource(uri);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public URI getAlias()
+ {
+ return _alias;
+ }
+
+ /* -------------------------------------------------------- */
+ /**
+ * Returns true if the resource exists.
+ */
+ @Override
+ public boolean exists()
+ {
+ return _file.exists();
+ }
+
+ /* -------------------------------------------------------- */
+ /**
+ * Returns the last modified time
+ */
+ @Override
+ public long lastModified()
+ {
+ return _file.lastModified();
+ }
+
+ /* -------------------------------------------------------- */
+ /**
+ * Returns true if the resource is a container/directory.
+ */
+ @Override
+ public boolean isDirectory()
+ {
+ return _file.exists() && _file.isDirectory() || _uri.endsWith("/");
+ }
+
+ /* --------------------------------------------------------- */
+ /**
+ * Return the length of the resource
+ */
+ @Override
+ public long length()
+ {
+ return _file.length();
+ }
+
+
+ /* --------------------------------------------------------- */
+ /**
+ * Returns the name of the resource
+ */
+ @Override
+ public String getName()
+ {
+ return _file.getAbsolutePath();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an File representing the given resource or NULL if this
+ * is not possible.
+ */
+ @Override
+ public File getFile()
+ {
+ return _file;
+ }
+
+ /* --------------------------------------------------------- */
+ /**
+ * Returns an input stream to the resource
+ */
+ @Override
+ public InputStream getInputStream() throws IOException
+ {
+ return new FileInputStream(_file);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ReadableByteChannel getReadableByteChannel() throws IOException
+ {
+ return FileChannel.open(_file.toPath(),StandardOpenOption.READ);
+ }
+
+ /* --------------------------------------------------------- */
+ /**
+ * Deletes the given resource
+ */
+ @Override
+ public boolean delete()
+ throws SecurityException
+ {
+ return _file.delete();
+ }
+
+ /* --------------------------------------------------------- */
+ /**
+ * Rename the given resource
+ */
+ @Override
+ public boolean renameTo( Resource dest)
+ throws SecurityException
+ {
+ if( dest instanceof FileResource)
+ return _file.renameTo( ((FileResource)dest)._file);
+ else
+ return false;
+ }
+
+ /* --------------------------------------------------------- */
+ /**
+ * Returns a list of resources contained in the given resource
+ */
+ @Override
+ public String[] list()
+ {
+ String[] list =_file.list();
+ if (list==null)
+ return null;
+ for (int i=list.length;i-->0;)
+ {
+ if (new File(_file,list[i]).isDirectory() &&
+ !list[i].endsWith("/"))
+ list[i]+="/";
+ }
+ return list;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param o
+ * @return <code>true</code> of the object <code>o</code> is a {@link FileResource} pointing to the same file as this resource.
+ */
+ @Override
+ public boolean equals( Object o)
+ {
+ if (this == o)
+ return true;
+
+ if (null == o || ! (o instanceof FileResource))
+ return false;
+
+ FileResource f=(FileResource)o;
+ return f._file == _file || (null != _file && _file.equals(f._file));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the hashcode.
+ */
+ @Override
+ public int hashCode()
+ {
+ return null == _file ? super.hashCode() : _file.hashCode();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void copyTo(File destination)
+ throws IOException
+ {
+ if (isDirectory())
+ {
+ IO.copyDir(getFile(),destination);
+ }
+ else
+ {
+ if (destination.exists())
+ throw new IllegalArgumentException(destination+" exists");
+ IO.copy(getFile(),destination);
+ }
+ }
+
+ @Override
+ public boolean isContainedIn(Resource r) throws MalformedURLException
+ {
+ return false;
+ }
+
+ @Override
+ public void close()
+ {
+ }
+
+ @Override
+ public URL getURL()
+ {
+ try
+ {
+ return new URL(_uri);
+ }
+ catch (MalformedURLException e)
+ {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public URI getURI()
+ {
+ return _file.toURI();
+ }
+
+ @Override
+ public String toString()
+ {
+ return _uri;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+class JarFileResource extends JarResource
+{
+ private static final Logger LOG = Log.getLogger(JarFileResource.class);
+ private JarFile _jarFile;
+ private File _file;
+ private String[] _list;
+ private JarEntry _entry;
+ private boolean _directory;
+ private String _jarUrl;
+ private String _path;
+ private boolean _exists;
+
+ /* -------------------------------------------------------- */
+ protected JarFileResource(URL url)
+ {
+ super(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected JarFileResource(URL url, boolean useCaches)
+ {
+ super(url, useCaches);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public synchronized void close()
+ {
+ _list=null;
+ _entry=null;
+ _file=null;
+ //if the jvm is not doing url caching, then the JarFiles will not be cached either,
+ //and so they are safe to close
+ if (!getUseCaches())
+ {
+ if ( _jarFile != null )
+ {
+ try
+ {
+ LOG.debug("Closing JarFile "+_jarFile.getName());
+ _jarFile.close();
+ }
+ catch ( IOException ioe )
+ {
+ LOG.ignore(ioe);
+ }
+ }
+ }
+ _jarFile=null;
+ super.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected synchronized boolean checkConnection()
+ {
+ try
+ {
+ super.checkConnection();
+ }
+ finally
+ {
+ if (_jarConnection==null)
+ {
+ _entry=null;
+ _file=null;
+ _jarFile=null;
+ _list=null;
+ }
+ }
+ return _jarFile!=null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected synchronized void newConnection()
+ throws IOException
+ {
+ super.newConnection();
+
+ _entry=null;
+ _file=null;
+ _jarFile=null;
+ _list=null;
+
+ int sep = _urlString.indexOf("!/");
+ _jarUrl=_urlString.substring(0,sep+2);
+ _path=_urlString.substring(sep+2);
+ if (_path.length()==0)
+ _path=null;
+ _jarFile=_jarConnection.getJarFile();
+ _file=new File(_jarFile.getName());
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns true if the represented resource exists.
+ */
+ @Override
+ public boolean exists()
+ {
+ if (_exists)
+ return true;
+
+ if (_urlString.endsWith("!/"))
+ {
+
+ String file_url=_urlString.substring(4,_urlString.length()-2);
+ try{return newResource(file_url).exists();}
+ catch(Exception e) {LOG.ignore(e); return false;}
+ }
+
+ boolean check=checkConnection();
+
+ // Is this a root URL?
+ if (_jarUrl!=null && _path==null)
+ {
+ // Then if it exists it is a directory
+ _directory=check;
+ return true;
+ }
+ else
+ {
+ // Can we find a file for it?
+ JarFile jarFile=null;
+ if (check)
+ // Yes
+ jarFile=_jarFile;
+ else
+ {
+ // No - so lets look if the root entry exists.
+ try
+ {
+ JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection());
+ c.setUseCaches(getUseCaches());
+ jarFile=c.getJarFile();
+ }
+ catch(Exception e)
+ {
+ LOG.ignore(e);
+ }
+ }
+
+ // Do we need to look more closely?
+ if (jarFile!=null && _entry==null && !_directory)
+ {
+ // OK - we have a JarFile, lets look at the entries for our path
+ Enumeration<JarEntry> e=jarFile.entries();
+ while(e.hasMoreElements())
+ {
+ JarEntry entry = e.nextElement();
+ String name=entry.getName().replace('\\','/');
+
+ // Do we have a match
+ if (name.equals(_path))
+ {
+ _entry=entry;
+ // Is the match a directory
+ _directory=_path.endsWith("/");
+ break;
+ }
+ else if (_path.endsWith("/"))
+ {
+ if (name.startsWith(_path))
+ {
+ _directory=true;
+ break;
+ }
+ }
+ else if (name.startsWith(_path) && name.length()>_path.length() && name.charAt(_path.length())=='/')
+ {
+ _directory=true;
+ break;
+ }
+ }
+ }
+ }
+
+ _exists= ( _directory || _entry!=null);
+ return _exists;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns true if the represented resource is a container/directory.
+ * If the resource is not a file, resources ending with "/" are
+ * considered directories.
+ */
+ @Override
+ public boolean isDirectory()
+ {
+ return _urlString.endsWith("/") || exists() && _directory;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the last modified time
+ */
+ @Override
+ public long lastModified()
+ {
+ if (checkConnection() && _file!=null)
+ {
+ if (exists() && _entry!=null)
+ return _entry.getTime();
+ return _file.lastModified();
+ }
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public synchronized String[] list()
+ {
+ if (isDirectory() && _list==null)
+ {
+ List<String> list = null;
+ try
+ {
+ list = listEntries();
+ }
+ catch (Exception e)
+ {
+ //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if
+ //useCaches == false (eg someone called URLConnection with defaultUseCaches==true).
+ //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in
+ //the situation where the JarFile we have remembered in our _jarFile member has actually been closed
+ //by other code.
+ //So, do one retry to drop a connection and get a fresh JarFile
+ LOG.warn("Retrying list:"+e);
+ LOG.debug(e);
+ release();
+ list = listEntries();
+ }
+
+ if (list != null)
+ {
+ _list=new String[list.size()];
+ list.toArray(_list);
+ }
+ }
+ return _list;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private List<String> listEntries ()
+ {
+ checkConnection();
+
+ ArrayList<String> list = new ArrayList<String>(32);
+ JarFile jarFile=_jarFile;
+ if(jarFile==null)
+ {
+ try
+ {
+ JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection());
+ jc.setUseCaches(getUseCaches());
+ jarFile=jc.getJarFile();
+ }
+ catch(Exception e)
+ {
+
+ e.printStackTrace();
+ LOG.ignore(e);
+ }
+ if(jarFile==null)
+ throw new IllegalStateException();
+ }
+
+ Enumeration<JarEntry> e=jarFile.entries();
+ String dir=_urlString.substring(_urlString.indexOf("!/")+2);
+ while(e.hasMoreElements())
+ {
+ JarEntry entry = e.nextElement();
+ String name=entry.getName().replace('\\','/');
+ if(!name.startsWith(dir) || name.length()==dir.length())
+ {
+ continue;
+ }
+ String listName=name.substring(dir.length());
+ int dash=listName.indexOf('/');
+ if (dash>=0)
+ {
+ //when listing jar:file urls, you get back one
+ //entry for the dir itself, which we ignore
+ if (dash==0 && listName.length()==1)
+ continue;
+ //when listing jar:file urls, all files and
+ //subdirs have a leading /, which we remove
+ if (dash==0)
+ listName=listName.substring(dash+1, listName.length());
+ else
+ listName=listName.substring(0,dash+1);
+
+ if (list.contains(listName))
+ continue;
+ }
+
+ list.add(listName);
+ }
+
+ return list;
+ }
+
+
+
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Return the length of the resource
+ */
+ @Override
+ public long length()
+ {
+ if (isDirectory())
+ return -1;
+
+ if (_entry!=null)
+ return _entry.getSize();
+
+ return -1;
+ }
+
+
+ /**
+ * Take a Resource that possibly might use URLConnection caching
+ * and turn it into one that doesn't.
+ * @param resource
+ * @return the non-caching resource
+ */
+ public static Resource getNonCachingResource (Resource resource)
+ {
+ if (!(resource instanceof JarFileResource))
+ return resource;
+
+ JarFileResource oldResource = (JarFileResource)resource;
+
+ JarFileResource newResource = new JarFileResource(oldResource.getURL(), false);
+ return newResource;
+
+ }
+
+ /**
+ * Check if this jar:file: resource is contained in the
+ * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code>
+ * @param resource
+ * @return true if resource is contained in the named resource
+ * @throws MalformedURLException
+ */
+ @Override
+ public boolean isContainedIn (Resource resource)
+ throws MalformedURLException
+ {
+ String string = _urlString;
+ int index = string.indexOf("!/");
+ if (index > 0)
+ string = string.substring(0,index);
+ if (string.startsWith("jar:"))
+ string = string.substring(4);
+ URL url = new URL(string);
+ return url.sameFile(resource.getURL());
+ }
+}
+
+
+
+
+
+
+
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.resource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+public class JarResource extends URLResource
+{
+ private static final Logger LOG = Log.getLogger(JarResource.class);
+ protected JarURLConnection _jarConnection;
+
+ /* -------------------------------------------------------- */
+ protected JarResource(URL url)
+ {
+ super(url,null);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected JarResource(URL url, boolean useCaches)
+ {
+ super(url, null, useCaches);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public synchronized void close()
+ {
+ _jarConnection=null;
+ super.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected synchronized boolean checkConnection()
+ {
+ super.checkConnection();
+ try
+ {
+ if (_jarConnection!=_connection)
+ newConnection();
+ }
+ catch(IOException e)
+ {
+ LOG.ignore(e);
+ _jarConnection=null;
+ }
+
+ return _jarConnection!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @throws IOException Sub-classes of <code>JarResource</code> may throw an IOException (or subclass)
+ */
+ protected void newConnection() throws IOException
+ {
+ _jarConnection=(JarURLConnection)_connection;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns true if the respresenetd resource exists.
+ */
+ @Override
+ public boolean exists()
+ {
+ if (_urlString.endsWith("!/"))
+ return checkConnection();
+ else
+ return super.exists();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public File getFile()
+ throws IOException
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public InputStream getInputStream()
+ throws java.io.IOException
+ {
+ checkConnection();
+ if (!_urlString.endsWith("!/"))
+ return new FilterInputStream(super.getInputStream())
+ {
+ @Override
+ public void close() throws IOException {this.in=IO.getClosedStream();}
+ };
+
+ URL url = new URL(_urlString.substring(4,_urlString.length()-2));
+ InputStream is = url.openStream();
+ return is;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void copyTo(File directory)
+ throws IOException
+ {
+ if (!exists())
+ return;
+
+ if(LOG.isDebugEnabled())
+ LOG.debug("Extract "+this+" to "+directory);
+
+ String urlString = this.getURL().toExternalForm().trim();
+ int endOfJarUrl = urlString.indexOf("!/");
+ int startOfJarUrl = (endOfJarUrl >= 0?4:0);
+
+ if (endOfJarUrl < 0)
+ throw new IOException("Not a valid jar url: "+urlString);
+
+ URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl));
+ String subEntryName = (endOfJarUrl+2 < urlString.length() ? urlString.substring(endOfJarUrl + 2) : null);
+ boolean subEntryIsDir = (subEntryName != null && subEntryName.endsWith("/")?true:false);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL);
+
+ try (InputStream is = jarFileURL.openConnection().getInputStream();
+ JarInputStream jin = new JarInputStream(is))
+ {
+ JarEntry entry;
+ boolean shouldExtract;
+ while((entry=jin.getNextJarEntry())!=null)
+ {
+ String entryName = entry.getName();
+ if ((subEntryName != null) && (entryName.startsWith(subEntryName)))
+ {
+ // is the subentry really a dir?
+ if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
+ subEntryIsDir=true;
+
+ //if there is a particular subEntry that we are looking for, only
+ //extract it.
+ if (subEntryIsDir)
+ {
+ //if it is a subdirectory we are looking for, then we
+ //are looking to extract its contents into the target
+ //directory. Remove the name of the subdirectory so
+ //that we don't wind up creating it too.
+ entryName = entryName.substring(subEntryName.length());
+ if (!entryName.equals(""))
+ {
+ //the entry is
+ shouldExtract = true;
+ }
+ else
+ shouldExtract = false;
+ }
+ else
+ shouldExtract = true;
+ }
+ else if ((subEntryName != null) && (!entryName.startsWith(subEntryName)))
+ {
+ //there is a particular entry we are looking for, and this one
+ //isn't it
+ shouldExtract = false;
+ }
+ else
+ {
+ //we are extracting everything
+ shouldExtract = true;
+ }
+
+ if (!shouldExtract)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Skipping entry: "+entryName);
+ continue;
+ }
+
+ String dotCheck = entryName.replace('\\', '/');
+ dotCheck = URIUtil.canonicalPath(dotCheck);
+ if (dotCheck == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Invalid entry: "+entryName);
+ continue;
+ }
+
+ File file=new File(directory,entryName);
+
+ if (entry.isDirectory())
+ {
+ // Make directory
+ if (!file.exists())
+ file.mkdirs();
+ }
+ else
+ {
+ // make directory (some jars don't list dirs)
+ File dir = new File(file.getParent());
+ if (!dir.exists())
+ dir.mkdirs();
+
+ // Make file
+ try (OutputStream fout = new FileOutputStream(file))
+ {
+ IO.copy(jin,fout);
+ }
+
+ // touch the file.
+ if (entry.getTime()>=0)
+ file.setLastModified(entry.getTime());
+ }
+ }
+
+ if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF")))
+ {
+ Manifest manifest = jin.getManifest();
+ if (manifest != null)
+ {
+ File metaInf = new File (directory, "META-INF");
+ metaInf.mkdir();
+ File f = new File(metaInf, "MANIFEST.MF");
+ try (OutputStream fout = new FileOutputStream(f))
+ {
+ manifest.write(fout);
+ }
+ }
+ }
+ }
+ }
+
+ public static Resource newJarResource(Resource resource) throws IOException
+ {
+ if (resource instanceof JarResource)
+ return resource;
+ return Resource.newResource("jar:" + resource + "!/");
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.resource;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Abstract resource class.
+ * <p>
+ * This class provides a resource abstraction, where a resource may be
+ * a file, a URL or an entry in a jar file.
+ * </p>
+ */
+public abstract class Resource implements ResourceFactory, Closeable
+{
+ private static final Logger LOG = Log.getLogger(Resource.class);
+ public static boolean __defaultUseCaches = true;
+ volatile Object _associate;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Change the default setting for url connection caches.
+ * Subsequent URLConnections will use this default.
+ * @param useCaches
+ */
+ public static void setDefaultUseCaches (boolean useCaches)
+ {
+ __defaultUseCaches=useCaches;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static boolean getDefaultUseCaches ()
+ {
+ return __defaultUseCaches;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Construct a resource from a uri.
+ * @param uri A URI.
+ * @return A Resource object.
+ * @throws MalformedURLException Problem accessing URI
+ */
+ public static Resource newResource(URI uri)
+ throws MalformedURLException
+ {
+ return newResource(uri.toURL());
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Construct a resource from a url.
+ * @param url A URL.
+ * @return A Resource object.
+ */
+ public static Resource newResource(URL url)
+ {
+ return newResource(url, __defaultUseCaches);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Construct a resource from a url.
+ * @param url the url for which to make the resource
+ * @param useCaches true enables URLConnection caching if applicable to the type of resource
+ * @return
+ */
+ static Resource newResource(URL url, boolean useCaches)
+ {
+ if (url==null)
+ return null;
+
+ String url_string=url.toExternalForm();
+ if( url_string.startsWith( "file:"))
+ {
+ try
+ {
+ FileResource fileResource= new FileResource(url);
+ return fileResource;
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e.toString());
+ LOG.debug(Log.EXCEPTION,e);
+ return new BadResource(url,e.toString());
+ }
+ }
+ else if( url_string.startsWith( "jar:file:"))
+ {
+ return new JarFileResource(url, useCaches);
+ }
+ else if( url_string.startsWith( "jar:"))
+ {
+ return new JarResource(url, useCaches);
+ }
+
+ return new URLResource(url,null,useCaches);
+ }
+
+
+
+ /* ------------------------------------------------------------ */
+ /** Construct a resource from a string.
+ * @param resource A URL or filename.
+ * @throws MalformedURLException Problem accessing URI
+ * @return A Resource object.
+ */
+ public static Resource newResource(String resource)
+ throws MalformedURLException
+ {
+ return newResource(resource, __defaultUseCaches);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Construct a resource from a string.
+ * @param resource A URL or filename.
+ * @param useCaches controls URLConnection caching
+ * @return A Resource object.
+ * @throws MalformedURLException Problem accessing URI
+ */
+ public static Resource newResource(String resource, boolean useCaches)
+ throws MalformedURLException
+ {
+ URL url=null;
+ try
+ {
+ // Try to format as a URL?
+ url = new URL(resource);
+ }
+ catch(MalformedURLException e)
+ {
+ if(!resource.startsWith("ftp:") &&
+ !resource.startsWith("file:") &&
+ !resource.startsWith("jar:"))
+ {
+ try
+ {
+ // It's a file.
+ if (resource.startsWith("./"))
+ resource=resource.substring(2);
+
+ File file=new File(resource).getCanonicalFile();
+ return new FileResource(file);
+ }
+ catch(Exception e2)
+ {
+ LOG.debug(Log.EXCEPTION,e2);
+ throw e;
+ }
+ }
+ else
+ {
+ LOG.warn("Bad Resource: "+resource);
+ throw e;
+ }
+ }
+
+ return newResource(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static Resource newResource(File file)
+ {
+ return new FileResource(file);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Construct a system resource from a string.
+ * The resource is tried as classloader resource before being
+ * treated as a normal resource.
+ * @param resource Resource as string representation
+ * @return The new Resource
+ * @throws IOException Problem accessing resource.
+ */
+ public static Resource newSystemResource(String resource)
+ throws IOException
+ {
+ URL url=null;
+ // Try to format as a URL?
+ ClassLoader loader=Thread.currentThread().getContextClassLoader();
+ if (loader!=null)
+ {
+ try
+ {
+ url = loader.getResource(resource);
+ if (url == null && resource.startsWith("/"))
+ url = loader.getResource(resource.substring(1));
+ }
+ catch (IllegalArgumentException e)
+ {
+ // Catches scenario where a bad Windows path like "C:\dev" is
+ // improperly escaped, which various downstream classloaders
+ // tend to have a problem with
+ url = null;
+ }
+ }
+ if (url==null)
+ {
+ loader=Resource.class.getClassLoader();
+ if (loader!=null)
+ {
+ url=loader.getResource(resource);
+ if (url==null && resource.startsWith("/"))
+ url=loader.getResource(resource.substring(1));
+ }
+ }
+
+ if (url==null)
+ {
+ url=ClassLoader.getSystemResource(resource);
+ if (url==null && resource.startsWith("/"))
+ url=ClassLoader.getSystemResource(resource.substring(1));
+ }
+
+ if (url==null)
+ return null;
+
+ return newResource(url);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Find a classpath resource.
+ */
+ public static Resource newClassPathResource(String resource)
+ {
+ return newClassPathResource(resource,true,false);
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Find a classpath resource.
+ * The {@link java.lang.Class#getResource(String)} method is used to lookup the resource. If it is not
+ * found, then the {@link Loader#getResource(Class, String)} method is used.
+ * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used.
+ * Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources.
+ * @param name The relative name of the resource
+ * @param useCaches True if URL caches are to be used.
+ * @param checkParents True if forced searching of parent Classloaders is performed to work around
+ * loaders with inverted priorities
+ * @return Resource or null
+ */
+ public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents)
+ {
+ URL url=Resource.class.getResource(name);
+
+ if (url==null)
+ url=Loader.getResource(Resource.class,name);
+ if (url==null)
+ return null;
+ return newResource(url,useCaches);
+ }
+
+ /* ------------------------------------------------------------ */
+ public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException
+ {
+ return r.isContainedIn(containingResource);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void finalize()
+ {
+ close();
+ }
+
+ /* ------------------------------------------------------------ */
+ public abstract boolean isContainedIn (Resource r) throws MalformedURLException;
+
+
+ /* ------------------------------------------------------------ */
+ /** Release any temporary resources held by the resource.
+ * @deprecated use {@link #close()}
+ */
+ public final void release()
+ {
+ close();
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Release any temporary resources held by the resource.
+ */
+ @Override
+ public abstract void close();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns true if the respresened resource exists.
+ */
+ public abstract boolean exists();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns true if the respresenetd resource is a container/directory.
+ * If the resource is not a file, resources ending with "/" are
+ * considered directories.
+ */
+ public abstract boolean isDirectory();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the last modified time
+ */
+ public abstract long lastModified();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Return the length of the resource
+ */
+ public abstract long length();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an URL representing the given resource
+ */
+ public abstract URL getURL();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an URI representing the given resource
+ */
+ public URI getURI()
+ {
+ try
+ {
+ return getURL().toURI();
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an File representing the given resource or NULL if this
+ * is not possible.
+ */
+ public abstract File getFile()
+ throws IOException;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the name of the resource
+ */
+ public abstract String getName();
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an input stream to the resource
+ */
+ public abstract InputStream getInputStream()
+ throws java.io.IOException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an readable bytechannel to the resource or null if one is not available.
+ */
+ public abstract ReadableByteChannel getReadableByteChannel()
+ throws java.io.IOException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Deletes the given resource
+ */
+ public abstract boolean delete()
+ throws SecurityException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Rename the given resource
+ */
+ public abstract boolean renameTo( Resource dest)
+ throws SecurityException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns a list of resource names contained in the given resource
+ * The resource names are not URL encoded.
+ */
+ public abstract String[] list();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the resource contained inside the current resource with the
+ * given name.
+ * @param path The path segment to add, which is not encoded
+ */
+ public abstract Resource addPath(String path)
+ throws IOException,MalformedURLException;
+
+ /* ------------------------------------------------------------ */
+ /** Get a resource from within this resource.
+ * <p>
+ * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions.
+ * This method satisfied the {@link ResourceFactory} interface.
+ * @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String)
+ */
+ @Override
+ public Resource getResource(String path)
+ {
+ try
+ {
+ return addPath(path);
+ }
+ catch(Exception e)
+ {
+ LOG.debug(e);
+ return null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @deprecated
+ */
+ public String encode(String uri)
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public Object getAssociate()
+ {
+ return _associate;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setAssociate(Object o)
+ {
+ _associate=o;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The canonical Alias of this resource or null if none.
+ */
+ public URI getAlias()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get the resource list as a HTML directory listing.
+ * @param base The base URL
+ * @param parent True if the parent directory should be included
+ * @return String of HTML
+ */
+ public String getListHTML(String base,boolean parent)
+ throws IOException
+ {
+ base=URIUtil.canonicalPath(base);
+ if (base==null || !isDirectory())
+ return null;
+
+ String[] ls = list();
+ if (ls==null)
+ return null;
+ Arrays.sort(ls);
+
+ String decodedBase = URIUtil.decodePath(base);
+ String title = "Directory: "+deTag(decodedBase);
+
+ StringBuilder buf=new StringBuilder(4096);
+ buf.append("<HTML><HEAD>");
+ buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>");
+ buf.append(title);
+ buf.append("</TITLE></HEAD><BODY>\n<H1>");
+ buf.append(title);
+ buf.append("</H1>\n<TABLE BORDER=0>\n");
+
+ if (parent)
+ {
+ buf.append("<TR><TD><A HREF=\"");
+ buf.append(URIUtil.addPaths(base,"../"));
+ buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
+ }
+
+ String encodedBase = hrefEncodeURI(base);
+
+ DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
+ DateFormat.MEDIUM);
+ for (int i=0 ; i< ls.length ; i++)
+ {
+ Resource item = addPath(ls[i]);
+
+ buf.append("\n<TR><TD><A HREF=\"");
+ String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i]));
+
+ buf.append(path);
+
+ if (item.isDirectory() && !path.endsWith("/"))
+ buf.append(URIUtil.SLASH);
+
+ // URIUtil.encodePath(buf,path);
+ buf.append("\">");
+ buf.append(deTag(ls[i]));
+ buf.append(" ");
+ buf.append("</A></TD><TD ALIGN=right>");
+ buf.append(item.length());
+ buf.append(" bytes </TD><TD>");
+ buf.append(dfmt.format(new Date(item.lastModified())));
+ buf.append("</TD></TR>");
+ }
+ buf.append("</TABLE>\n");
+ buf.append("</BODY></HTML>\n");
+
+ return buf.toString();
+ }
+
+ /**
+ * Encode any characters that could break the URI string in an HREF.
+ * Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
+ *
+ * The above example would parse incorrectly on various browsers as the "<" or '"' characters
+ * would end the href attribute value string prematurely.
+ *
+ * @param raw the raw text to encode.
+ * @return the defanged text.
+ */
+ private static String hrefEncodeURI(String raw)
+ {
+ StringBuffer buf = null;
+
+ loop:
+ for (int i=0;i<raw.length();i++)
+ {
+ char c=raw.charAt(i);
+ switch(c)
+ {
+ case '\'':
+ case '"':
+ case '<':
+ case '>':
+ buf=new StringBuffer(raw.length()<<1);
+ break loop;
+ }
+ }
+ if (buf==null)
+ return raw;
+
+ for (int i=0;i<raw.length();i++)
+ {
+ char c=raw.charAt(i);
+ switch(c)
+ {
+ case '"':
+ buf.append("%22");
+ continue;
+ case '\'':
+ buf.append("%27");
+ continue;
+ case '<':
+ buf.append("%3C");
+ continue;
+ case '>':
+ buf.append("%3E");
+ continue;
+ default:
+ buf.append(c);
+ continue;
+ }
+ }
+
+ return buf.toString();
+ }
+
+ private static String deTag(String raw)
+ {
+ return StringUtil.replace( StringUtil.replace(raw,"<","<"), ">", ">");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param out
+ * @param start First byte to write
+ * @param count Bytes to write or -1 for all of them.
+ */
+ public void writeTo(OutputStream out,long start,long count)
+ throws IOException
+ {
+ try (InputStream in = getInputStream())
+ {
+ in.skip(start);
+ if (count<0)
+ IO.copy(in,out);
+ else
+ IO.copy(in,out,count);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public void copyTo(File destination)
+ throws IOException
+ {
+ if (destination.exists())
+ throw new IllegalArgumentException(destination+" exists");
+ try (OutputStream out = new FileOutputStream(destination))
+ {
+ writeTo(out,0,-1);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getWeakETag()
+ {
+ try
+ {
+ StringBuilder b = new StringBuilder(32);
+ b.append("W/\"");
+
+ String name=getName();
+ int length=name.length();
+ long lhash=0;
+ for (int i=0; i<length;i++)
+ lhash=31*lhash+name.charAt(i);
+
+ B64Code.encode(lastModified()^lhash,b);
+ B64Code.encode(length()^lhash,b);
+ b.append('"');
+ return b.toString();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public Collection<Resource> getAllResources()
+ {
+ try
+ {
+ ArrayList<Resource> deep=new ArrayList<>();
+ {
+ String[] list=list();
+ if (list!=null)
+ {
+ for (String i:list)
+ {
+ Resource r=addPath(i);
+ if (r.isDirectory())
+ deep.addAll(r.getAllResources());
+ else
+ deep.add(r);
+ }
+ }
+ }
+ return deep;
+ }
+ catch(Exception e)
+ {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Generate a properly encoded URL from a {@link File} instance.
+ * @param file Target file.
+ * @return URL of the target file.
+ * @throws MalformedURLException
+ */
+ public static URL toURL(File file) throws MalformedURLException
+ {
+ return file.toURI().toURL();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.channels.ReadableByteChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * A collection of resources (dirs).
+ * Allows webapps to have multiple (static) sources.
+ * The first resource in the collection is the main resource.
+ * If a resource is not found in the main resource, it looks it up in
+ * the order the resources were constructed.
+ *
+ *
+ *
+ */
+public class ResourceCollection extends Resource
+{
+ private static final Logger LOG = Log.getLogger(ResourceCollection.class);
+ private Resource[] _resources;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Instantiates an empty resource collection.
+ *
+ * This constructor is used when configuring jetty-maven-plugin.
+ */
+ public ResourceCollection()
+ {
+ _resources = new Resource[0];
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Instantiates a new resource collection.
+ *
+ * @param resources the resources to be added to collection
+ */
+ public ResourceCollection(Resource... resources)
+ {
+ List<Resource> list = new ArrayList<Resource>();
+ for (Resource r : resources)
+ {
+ if (r==null)
+ continue;
+ if (r instanceof ResourceCollection)
+ {
+ for (Resource r2 : ((ResourceCollection)r).getResources())
+ list.add(r2);
+ }
+ else
+ list.add(r);
+ }
+ _resources = list.toArray(new Resource[list.size()]);
+ for(Resource r : _resources)
+ {
+ if(!r.exists() || !r.isDirectory())
+ throw new IllegalArgumentException(r + " is not an existing directory.");
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Instantiates a new resource collection.
+ *
+ * @param resources the resource strings to be added to collection
+ */
+ public ResourceCollection(String[] resources)
+ {
+ _resources = new Resource[resources.length];
+ try
+ {
+ for(int i=0; i<resources.length; i++)
+ {
+ _resources[i] = Resource.newResource(resources[i]);
+ if(!_resources[i].exists() || !_resources[i].isDirectory())
+ throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
+ }
+ }
+ catch(IllegalArgumentException e)
+ {
+ throw e;
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Instantiates a new resource collection.
+ *
+ * @param csvResources the string containing comma-separated resource strings
+ */
+ public ResourceCollection(String csvResources)
+ {
+ setResourcesAsCSV(csvResources);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Retrieves the resource collection's resources.
+ *
+ * @return the resource array
+ */
+ public Resource[] getResources()
+ {
+ return _resources;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the resource collection's resources.
+ *
+ * @param resources the new resource array
+ */
+ public void setResources(Resource[] resources)
+ {
+ _resources = resources != null ? resources : new Resource[0];
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Sets the resources as string of comma-separated values.
+ * This method should be used when configuring jetty-maven-plugin.
+ *
+ * @param csvResources the comma-separated string containing
+ * one or more resource strings.
+ */
+ public void setResourcesAsCSV(String csvResources)
+ {
+ StringTokenizer tokenizer = new StringTokenizer(csvResources, ",;");
+ int len = tokenizer.countTokens();
+ if(len==0)
+ {
+ throw new IllegalArgumentException("ResourceCollection@setResourcesAsCSV(String) " +
+ " argument must be a string containing one or more comma-separated resource strings.");
+ }
+
+ List<Resource> resources = new ArrayList<>();
+
+ try
+ {
+ while(tokenizer.hasMoreTokens())
+ {
+ Resource resource = Resource.newResource(tokenizer.nextToken().trim());
+ if(!resource.exists() || !resource.isDirectory())
+ LOG.warn(" !exist "+resource);
+ else
+ resources.add(resource);
+ }
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ _resources = resources.toArray(new Resource[resources.size()]);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param path The path segment to add
+ * @return The contained resource (found first) in the collection of resources
+ */
+ @Override
+ public Resource addPath(String path) throws IOException, MalformedURLException
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ if(path==null)
+ throw new MalformedURLException();
+
+ if(path.length()==0 || URIUtil.SLASH.equals(path))
+ return this;
+
+ Resource resource=null;
+ ArrayList<Resource> resources = null;
+ int i=0;
+ for(; i<_resources.length; i++)
+ {
+ resource = _resources[i].addPath(path);
+ if (resource.exists())
+ {
+ if (resource.isDirectory())
+ break;
+ return resource;
+ }
+ }
+
+ for(i++; i<_resources.length; i++)
+ {
+ Resource r = _resources[i].addPath(path);
+ if (r.exists() && r.isDirectory())
+ {
+ if (resources==null)
+ resources = new ArrayList<Resource>();
+
+ if (resource!=null)
+ {
+ resources.add(resource);
+ resource=null;
+ }
+
+ resources.add(r);
+ }
+ }
+
+ if (resource!=null)
+ return resource;
+ if (resources!=null)
+ return new ResourceCollection(resources.toArray(new Resource[resources.size()]));
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param path
+ * @return the resource(file) if found, returns a list of resource dirs if its a dir, else null.
+ * @throws IOException
+ * @throws MalformedURLException
+ */
+ protected Object findResource(String path) throws IOException, MalformedURLException
+ {
+ Resource resource=null;
+ ArrayList<Resource> resources = null;
+ int i=0;
+ for(; i<_resources.length; i++)
+ {
+ resource = _resources[i].addPath(path);
+ if (resource.exists())
+ {
+ if (resource.isDirectory())
+ break;
+
+ return resource;
+ }
+ }
+
+ for(i++; i<_resources.length; i++)
+ {
+ Resource r = _resources[i].addPath(path);
+ if (r.exists() && r.isDirectory())
+ {
+ if (resource!=null)
+ {
+ resources = new ArrayList<Resource>();
+ resources.add(resource);
+ }
+ resources.add(r);
+ }
+ }
+
+ if (resource!=null)
+ return resource;
+ if (resources!=null)
+ return resources;
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean delete() throws SecurityException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean exists()
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public File getFile() throws IOException
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ for(Resource r : _resources)
+ {
+ File f = r.getFile();
+ if(f!=null)
+ return f;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public InputStream getInputStream() throws IOException
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ for(Resource r : _resources)
+ {
+ InputStream is = r.getInputStream();
+ if(is!=null)
+ return is;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ReadableByteChannel getReadableByteChannel() throws IOException
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ for(Resource r : _resources)
+ {
+ ReadableByteChannel channel = r.getReadableByteChannel();
+ if(channel!=null)
+ return channel;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getName()
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ for(Resource r : _resources)
+ {
+ String name = r.getName();
+ if(name!=null)
+ return name;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public URL getURL()
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ for(Resource r : _resources)
+ {
+ URL url = r.getURL();
+ if(url!=null)
+ return url;
+ }
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isDirectory()
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ return true;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public long lastModified()
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ for(Resource r : _resources)
+ {
+ long lm = r.lastModified();
+ if (lm!=-1)
+ return lm;
+ }
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public long length()
+ {
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The list of resource names(merged) contained in the collection of resources.
+ */
+ @Override
+ public String[] list()
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ HashSet<String> set = new HashSet<String>();
+ for(Resource r : _resources)
+ {
+ for(String s : r.list())
+ set.add(s);
+ }
+ String[] result=set.toArray(new String[set.size()]);
+ Arrays.sort(result);
+ return result;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void close()
+ {
+ if(_resources==null)
+ throw new IllegalStateException("*resources* not set.");
+
+ for(Resource r : _resources)
+ r.close();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean renameTo(Resource dest) throws SecurityException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void copyTo(File destination)
+ throws IOException
+ {
+ for (int r=_resources.length;r-->0;)
+ _resources[r].copyTo(destination);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return the list of resources separated by a path separator
+ */
+ @Override
+ public String toString()
+ {
+ if(_resources==null)
+ return "[]";
+
+ return String.valueOf(Arrays.asList(_resources));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isContainedIn(Resource r) throws MalformedURLException
+ {
+ // TODO could look at implementing the semantic of is this collection a subset of the Resource r?
+ return false;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.resource;
+
+
+/* ------------------------------------------------------------ */
+/** ResourceFactory.
+ */
+public interface ResourceFactory
+{
+
+ /* ------------------------------------------------------------ */
+ /** Get a resource for a path.
+ * @param path The path to the resource
+ * @return The resource or null
+ */
+ Resource getResource(String path);
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.channels.ReadableByteChannel;
+import java.security.Permission;
+
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Abstract resource class.
+ */
+public class URLResource extends Resource
+{
+ private static final Logger LOG = Log.getLogger(URLResource.class);
+ protected final URL _url;
+ protected final String _urlString;
+
+ protected URLConnection _connection;
+ protected InputStream _in=null;
+ transient boolean _useCaches = Resource.__defaultUseCaches;
+
+ /* ------------------------------------------------------------ */
+ protected URLResource(URL url, URLConnection connection)
+ {
+ _url = url;
+ _urlString=_url.toExternalForm();
+ _connection=connection;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected URLResource (URL url, URLConnection connection, boolean useCaches)
+ {
+ this (url, connection);
+ _useCaches = useCaches;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected synchronized boolean checkConnection()
+ {
+ if (_connection==null)
+ {
+ try{
+ _connection=_url.openConnection();
+ _connection.setUseCaches(_useCaches);
+ }
+ catch(IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ return _connection!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Release any resources held by the resource.
+ */
+ @Override
+ public synchronized void close()
+ {
+ if (_in!=null)
+ {
+ try{_in.close();}catch(IOException e){LOG.ignore(e);}
+ _in=null;
+ }
+
+ if (_connection!=null)
+ _connection=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns true if the represented resource exists.
+ */
+ @Override
+ public boolean exists()
+ {
+ try
+ {
+ synchronized(this)
+ {
+ if (checkConnection() && _in==null )
+ _in = _connection.getInputStream();
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ return _in!=null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns true if the represented resource is a container/directory.
+ * If the resource is not a file, resources ending with "/" are
+ * considered directories.
+ */
+ @Override
+ public boolean isDirectory()
+ {
+ return exists() && _urlString.endsWith("/");
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the last modified time
+ */
+ @Override
+ public long lastModified()
+ {
+ if (checkConnection())
+ return _connection.getLastModified();
+ return -1;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Return the length of the resource
+ */
+ @Override
+ public long length()
+ {
+ if (checkConnection())
+ return _connection.getContentLength();
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an URL representing the given resource
+ */
+ @Override
+ public URL getURL()
+ {
+ return _url;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an File representing the given resource or NULL if this
+ * is not possible.
+ */
+ @Override
+ public File getFile()
+ throws IOException
+ {
+ // Try the permission hack
+ if (checkConnection())
+ {
+ Permission perm = _connection.getPermission();
+ if (perm instanceof java.io.FilePermission)
+ return new File(perm.getName());
+ }
+
+ // Try the URL file arg
+ try {return new File(_url.getFile());}
+ catch(Exception e) {LOG.ignore(e);}
+
+ // Don't know the file
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the name of the resource
+ */
+ @Override
+ public String getName()
+ {
+ return _url.toExternalForm();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns an input stream to the resource
+ */
+ @Override
+ public synchronized InputStream getInputStream()
+ throws java.io.IOException
+ {
+ if (!checkConnection())
+ throw new IOException( "Invalid resource");
+
+ try
+ {
+ if( _in != null)
+ {
+ InputStream in = _in;
+ _in=null;
+ return in;
+ }
+ return _connection.getInputStream();
+ }
+ finally
+ {
+ _connection=null;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public ReadableByteChannel getReadableByteChannel() throws IOException
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Deletes the given resource
+ */
+ @Override
+ public boolean delete()
+ throws SecurityException
+ {
+ throw new SecurityException( "Delete not supported");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Rename the given resource
+ */
+ @Override
+ public boolean renameTo( Resource dest)
+ throws SecurityException
+ {
+ throw new SecurityException( "RenameTo not supported");
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns a list of resource names contained in the given resource
+ */
+ @Override
+ public String[] list()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the resource contained inside the current resource with the
+ * given name
+ */
+ @Override
+ public Resource addPath(String path)
+ throws IOException,MalformedURLException
+ {
+ if (path==null)
+ return null;
+
+ path = URIUtil.canonicalPath(path);
+
+ return newResource(URIUtil.addPaths(_url.toExternalForm(),path));
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _urlString;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int hashCode()
+ {
+ return _urlString.hashCode();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean equals( Object o)
+ {
+ return o instanceof URLResource && _urlString.equals(((URLResource)o)._urlString);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getUseCaches ()
+ {
+ return _useCaches;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isContainedIn (Resource containingResource) throws MalformedURLException
+ {
+ return false;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Common Resource Utilities
+ */
+package org.eclipse.jetty.util.resource;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.security;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.CRL;
+import java.security.cert.CertificateFactory;
+import java.util.Collection;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+public class CertificateUtils
+{
+ /* ------------------------------------------------------------ */
+ public static KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+ {
+ KeyStore keystore = null;
+
+ if (storeStream != null || storePath != null)
+ {
+ InputStream inStream = storeStream;
+ try
+ {
+ if (inStream == null)
+ {
+ inStream = Resource.newResource(storePath).getInputStream();
+ }
+
+ if (storeProvider != null)
+ {
+ keystore = KeyStore.getInstance(storeType, storeProvider);
+ }
+ else
+ {
+ keystore = KeyStore.getInstance(storeType);
+ }
+
+ keystore.load(inStream, storePassword == null ? null : storePassword.toCharArray());
+ }
+ finally
+ {
+ if (inStream != null)
+ {
+ inStream.close();
+ }
+ }
+ }
+
+ return keystore;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+ {
+ Collection<? extends CRL> crlList = null;
+
+ if (crlPath != null)
+ {
+ InputStream in = null;
+ try
+ {
+ in = Resource.newResource(crlPath).getInputStream();
+ crlList = CertificateFactory.getInstance("X.509").generateCRLs(in);
+ }
+ finally
+ {
+ if (in != null)
+ {
+ in.close();
+ }
+ }
+ }
+
+ return crlList;
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.security;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertPathBuilder;
+import java.security.cert.CertPathBuilderResult;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Convenience class to handle validation of certificates, aliases and keystores
+ *
+ * Allows specifying Certificate Revocation List (CRL), as well as enabling
+ * CRL Distribution Points Protocol (CRLDP) certificate extension support,
+ * and also enabling On-Line Certificate Status Protocol (OCSP) support.
+ *
+ * IMPORTANT: at least one of the above mechanisms *MUST* be configured and
+ * operational, otherwise certificate validation *WILL FAIL* unconditionally.
+ */
+public class CertificateValidator
+{
+ private static final Logger LOG = Log.getLogger(CertificateValidator.class);
+ private static AtomicLong __aliasCount = new AtomicLong();
+
+ private KeyStore _trustStore;
+ private Collection<? extends CRL> _crls;
+
+ /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+ private int _maxCertPathLength = -1;
+ /** CRL Distribution Points (CRLDP) support */
+ private boolean _enableCRLDP = false;
+ /** On-Line Certificate Status Protocol (OCSP) support */
+ private boolean _enableOCSP = false;
+ /** Location of OCSP Responder */
+ private String _ocspResponderURL;
+
+ /**
+ * creates an instance of the certificate validator
+ *
+ * @param trustStore
+ * @param crls
+ */
+ public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls)
+ {
+ if (trustStore == null)
+ {
+ throw new InvalidParameterException("TrustStore must be specified for CertificateValidator.");
+ }
+
+ _trustStore = trustStore;
+ _crls = crls;
+ }
+
+ /**
+ * validates all aliases inside of a given keystore
+ *
+ * @param keyStore
+ * @throws CertificateException
+ */
+ public void validate( KeyStore keyStore ) throws CertificateException
+ {
+ try
+ {
+ Enumeration<String> aliases = keyStore.aliases();
+
+ for ( ; aliases.hasMoreElements(); )
+ {
+ String alias = aliases.nextElement();
+
+ validate(keyStore,alias);
+ }
+
+ }
+ catch ( KeyStoreException kse )
+ {
+ throw new CertificateException("Unable to retrieve aliases from keystore", kse);
+ }
+ }
+
+
+ /**
+ * validates a specific alias inside of the keystore being passed in
+ *
+ * @param keyStore
+ * @param keyAlias
+ * @return the keyAlias if valid
+ * @throws CertificateException
+ */
+ public String validate(KeyStore keyStore, String keyAlias) throws CertificateException
+ {
+ String result = null;
+
+ if (keyAlias != null)
+ {
+ try
+ {
+ validate(keyStore, keyStore.getCertificate(keyAlias));
+ }
+ catch (KeyStoreException kse)
+ {
+ LOG.debug(kse);
+ throw new CertificateException("Unable to validate certificate" +
+ " for alias [" + keyAlias + "]: " + kse.getMessage(), kse);
+ }
+ result = keyAlias;
+ }
+
+ return result;
+ }
+
+ /**
+ * validates a specific certificate inside of the keystore being passed in
+ *
+ * @param keyStore
+ * @param cert
+ * @throws CertificateException
+ */
+ public void validate(KeyStore keyStore, Certificate cert) throws CertificateException
+ {
+ Certificate[] certChain = null;
+
+ if (cert != null && cert instanceof X509Certificate)
+ {
+ ((X509Certificate)cert).checkValidity();
+
+ String certAlias = null;
+ try
+ {
+ if (keyStore == null)
+ {
+ throw new InvalidParameterException("Keystore cannot be null");
+ }
+
+ certAlias = keyStore.getCertificateAlias((X509Certificate)cert);
+ if (certAlias == null)
+ {
+ certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet());
+ keyStore.setCertificateEntry(certAlias, cert);
+ }
+
+ certChain = keyStore.getCertificateChain(certAlias);
+ if (certChain == null || certChain.length == 0)
+ {
+ throw new IllegalStateException("Unable to retrieve certificate chain");
+ }
+ }
+ catch (KeyStoreException kse)
+ {
+ LOG.debug(kse);
+ throw new CertificateException("Unable to validate certificate" +
+ (certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse);
+ }
+
+ validate(certChain);
+ }
+ }
+
+ public void validate(Certificate[] certChain) throws CertificateException
+ {
+ try
+ {
+ ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
+ for (Certificate item : certChain)
+ {
+ if (item == null)
+ continue;
+
+ if (!(item instanceof X509Certificate))
+ {
+ throw new IllegalStateException("Invalid certificate type in chain");
+ }
+
+ certList.add((X509Certificate)item);
+ }
+
+ if (certList.isEmpty())
+ {
+ throw new IllegalStateException("Invalid certificate chain");
+
+ }
+
+ X509CertSelector certSelect = new X509CertSelector();
+ certSelect.setCertificate(certList.get(0));
+
+ // Configure certification path builder parameters
+ PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect);
+ pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList)));
+
+ // Set maximum certification path length
+ pbParams.setMaxPathLength(_maxCertPathLength);
+
+ // Enable revocation checking
+ pbParams.setRevocationEnabled(true);
+
+ // Set static Certificate Revocation List
+ if (_crls != null && !_crls.isEmpty())
+ {
+ pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls)));
+ }
+
+ // Enable On-Line Certificate Status Protocol (OCSP) support
+ if (_enableOCSP)
+ {
+ Security.setProperty("ocsp.enable","true");
+ }
+ // Enable Certificate Revocation List Distribution Points (CRLDP) support
+ if (_enableCRLDP)
+ {
+ System.setProperty("com.sun.security.enableCRLDP","true");
+ }
+
+ // Build certification path
+ CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);
+
+ // Validate certification path
+ CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams);
+ }
+ catch (GeneralSecurityException gse)
+ {
+ LOG.debug(gse);
+ throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse);
+ }
+ }
+
+ public KeyStore getTrustStore()
+ {
+ return _trustStore;
+ }
+
+ public Collection<? extends CRL> getCrls()
+ {
+ return _crls;
+ }
+
+ /**
+ * @return Maximum number of intermediate certificates in
+ * the certification path (-1 for unlimited)
+ */
+ public int getMaxCertPathLength()
+ {
+ return _maxCertPathLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param maxCertPathLength
+ * maximum number of intermediate certificates in
+ * the certification path (-1 for unlimited)
+ */
+ public void setMaxCertPathLength(int maxCertPathLength)
+ {
+ _maxCertPathLength = maxCertPathLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if CRL Distribution Points support is enabled
+ */
+ public boolean isEnableCRLDP()
+ {
+ return _enableCRLDP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Enables CRL Distribution Points Support
+ * @param enableCRLDP true - turn on, false - turns off
+ */
+ public void setEnableCRLDP(boolean enableCRLDP)
+ {
+ _enableCRLDP = enableCRLDP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return true if On-Line Certificate Status Protocol support is enabled
+ */
+ public boolean isEnableOCSP()
+ {
+ return _enableOCSP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Enables On-Line Certificate Status Protocol support
+ * @param enableOCSP true - turn on, false - turn off
+ */
+ public void setEnableOCSP(boolean enableOCSP)
+ {
+ _enableOCSP = enableOCSP;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Location of the OCSP Responder
+ */
+ public String getOcspResponderURL()
+ {
+ return _ocspResponderURL;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the location of the OCSP Responder.
+ * @param ocspResponderURL location of the OCSP Responder
+ */
+ public void setOcspResponderURL(String ocspResponderURL)
+ {
+ _ocspResponderURL = ocspResponderURL;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.security;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/* ------------------------------------------------------------ */
+/**
+ * Constraint
+ *
+ * Describe an auth and/or data constraint.
+ *
+ *
+ */
+public class Constraint implements Cloneable, Serializable
+{
+ /* ------------------------------------------------------------ */
+ public final static String __BASIC_AUTH = "BASIC";
+
+ public final static String __FORM_AUTH = "FORM";
+
+ public final static String __DIGEST_AUTH = "DIGEST";
+
+ public final static String __CERT_AUTH = "CLIENT_CERT";
+
+ public final static String __CERT_AUTH2 = "CLIENT-CERT";
+
+ public final static String __SPNEGO_AUTH = "SPNEGO";
+
+ public final static String __NEGOTIATE_AUTH = "NEGOTIATE";
+
+ public static boolean validateMethod (String method)
+ {
+ if (method == null)
+ return false;
+ method = method.trim();
+ return (method.equals(__FORM_AUTH)
+ || method.equals(__BASIC_AUTH)
+ || method.equals (__DIGEST_AUTH)
+ || method.equals (__CERT_AUTH)
+ || method.equals(__CERT_AUTH2)
+ || method.equals(__SPNEGO_AUTH)
+ || method.equals(__NEGOTIATE_AUTH));
+ }
+
+ /* ------------------------------------------------------------ */
+ public final static int DC_UNSET = -1, DC_NONE = 0, DC_INTEGRAL = 1, DC_CONFIDENTIAL = 2, DC_FORBIDDEN = 3;
+
+ /* ------------------------------------------------------------ */
+ public final static String NONE = "NONE";
+
+ public final static String ANY_ROLE = "*";
+
+ public final static String ANY_AUTH = "**"; //Servlet Spec 3.1 pg 140
+
+ /* ------------------------------------------------------------ */
+ private String _name;
+
+ private String[] _roles;
+
+ private int _dataConstraint = DC_UNSET;
+
+ private boolean _anyRole = false;
+
+ private boolean _anyAuth = false;
+
+ private boolean _authenticate = false;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Constructor.
+ */
+ public Constraint()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Conveniance Constructor.
+ *
+ * @param name
+ * @param role
+ */
+ public Constraint(String name, String role)
+ {
+ setName(name);
+ setRoles(new String[] { role });
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public Object clone() throws CloneNotSupportedException
+ {
+ return super.clone();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param name
+ */
+ public void setName(String name)
+ {
+ _name = name;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getName()
+ {
+ return _name;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setRoles(String[] roles)
+ {
+ _roles = roles;
+ _anyRole = false;
+ _anyAuth = false;
+ if (roles != null)
+ {
+ for (int i = roles.length; i-- > 0;)
+ {
+ _anyRole |= ANY_ROLE.equals(roles[i]);
+ _anyAuth |= ANY_AUTH.equals(roles[i]);
+ }
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if any user role is permitted.
+ */
+ public boolean isAnyRole()
+ {
+ return _anyRole;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /** Servlet Spec 3.1, pg 140
+ * @return True if any authenticated user is permitted (ie a role "**" was specified in the constraint).
+ */
+ public boolean isAnyAuth()
+ {
+ return _anyAuth;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return List of roles for this constraint.
+ */
+ public String[] getRoles()
+ {
+ return _roles;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param role
+ * @return True if the constraint contains the role.
+ */
+ public boolean hasRole(String role)
+ {
+ if (_anyRole) return true;
+ if (_roles != null) for (int i = _roles.length; i-- > 0;)
+ if (role.equals(_roles[i])) return true;
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param authenticate True if users must be authenticated
+ */
+ public void setAuthenticate(boolean authenticate)
+ {
+ _authenticate = authenticate;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if the constraint requires request authentication
+ */
+ public boolean getAuthenticate()
+ {
+ return _authenticate;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if authentication required but no roles set
+ */
+ public boolean isForbidden()
+ {
+ return _authenticate && !_anyRole && (_roles == null || _roles.length == 0);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param c Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL &
+ * 2=DC_CONFIDENTIAL
+ */
+ public void setDataConstraint(int c)
+ {
+ if (c < 0 || c > DC_CONFIDENTIAL) throw new IllegalArgumentException("Constraint out of range");
+ _dataConstraint = c;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL &
+ * 2=DC_CONFIDENTIAL
+ */
+ public int getDataConstraint()
+ {
+ return _dataConstraint;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if a data constraint has been set.
+ */
+ public boolean hasDataConstraint()
+ {
+ return _dataConstraint >= DC_NONE;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return "SC{" + _name
+ + ","
+ + (_anyRole ? "*" : (_roles == null ? "-" : Arrays.asList(_roles).toString()))
+ + ","
+ + (_dataConstraint == DC_UNSET ? "DC_UNSET}" : (_dataConstraint == DC_NONE ? "NONE}" : (_dataConstraint == DC_INTEGRAL ? "INTEGRAL}" : "CONFIDENTIAL}")));
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.security;
+
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Credentials. The Credential class represents an abstract mechanism for
+ * checking authentication credentials. A credential instance either represents
+ * a secret, or some data that could only be derived from knowing the secret.
+ * <p>
+ * Often a Credential is related to a Password via a one way algorithm, so while
+ * a Password itself is a Credential, a UnixCrypt or MD5 digest of a a password
+ * is only a credential that can be checked against the password.
+ * <p>
+ * This class includes an implementation for unix Crypt an MD5 digest.
+ *
+ * @see Password
+ *
+ */
+public abstract class Credential implements Serializable
+{
+ private static final Logger LOG = Log.getLogger(Credential.class);
+
+ private static final long serialVersionUID = -7760551052768181572L;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Check a credential
+ *
+ * @param credentials The credential to check against. This may either be
+ * another Credential object, a Password object or a String
+ * which is interpreted by this credential.
+ * @return True if the credentials indicated that the shared secret is known
+ * to both this Credential and the passed credential.
+ */
+ public abstract boolean check(Object credentials);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get a credential from a String. If the credential String starts with a
+ * known Credential type (eg "CRYPT:" or "MD5:" ) then a Credential of that
+ * type is returned. Else the credential is assumed to be a Password.
+ *
+ * @param credential String representation of the credential
+ * @return A Credential or Password instance.
+ */
+ public static Credential getCredential(String credential)
+ {
+ if (credential.startsWith(Crypt.__TYPE)) return new Crypt(credential);
+ if (credential.startsWith(MD5.__TYPE)) return new MD5(credential);
+
+ return new Password(credential);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Unix Crypt Credentials
+ */
+ public static class Crypt extends Credential
+ {
+ private static final long serialVersionUID = -2027792997664744210L;
+
+ public static final String __TYPE = "CRYPT:";
+
+ private final String _cooked;
+
+ Crypt(String cooked)
+ {
+ _cooked = cooked.startsWith(Crypt.__TYPE) ? cooked.substring(__TYPE.length()) : cooked;
+ }
+
+ @Override
+ public boolean check(Object credentials)
+ {
+ if (credentials instanceof char[])
+ credentials=new String((char[])credentials);
+ if (!(credentials instanceof String) && !(credentials instanceof Password))
+ LOG.warn("Can't check " + credentials.getClass() + " against CRYPT");
+
+ String passwd = credentials.toString();
+ return _cooked.equals(UnixCrypt.crypt(passwd, _cooked));
+ }
+
+ public static String crypt(String user, String pw)
+ {
+ return "CRYPT:" + UnixCrypt.crypt(pw, user);
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * MD5 Credentials
+ */
+ public static class MD5 extends Credential
+ {
+ private static final long serialVersionUID = 5533846540822684240L;
+
+ public static final String __TYPE = "MD5:";
+
+ public static final Object __md5Lock = new Object();
+
+ private static MessageDigest __md;
+
+ private final byte[] _digest;
+
+ /* ------------------------------------------------------------ */
+ MD5(String digest)
+ {
+ digest = digest.startsWith(__TYPE) ? digest.substring(__TYPE.length()) : digest;
+ _digest = TypeUtil.parseBytes(digest, 16);
+ }
+
+ /* ------------------------------------------------------------ */
+ public byte[] getDigest()
+ {
+ return _digest;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean check(Object credentials)
+ {
+ try
+ {
+ byte[] digest = null;
+
+ if (credentials instanceof char[])
+ credentials=new String((char[])credentials);
+ if (credentials instanceof Password || credentials instanceof String)
+ {
+ synchronized (__md5Lock)
+ {
+ if (__md == null) __md = MessageDigest.getInstance("MD5");
+ __md.reset();
+ __md.update(credentials.toString().getBytes(StandardCharsets.ISO_8859_1));
+ digest = __md.digest();
+ }
+ if (digest == null || digest.length != _digest.length) return false;
+ for (int i = 0; i < digest.length; i++)
+ if (digest[i] != _digest[i]) return false;
+ return true;
+ }
+ else if (credentials instanceof MD5)
+ {
+ MD5 md5 = (MD5) credentials;
+ if (_digest.length != md5._digest.length) return false;
+ for (int i = 0; i < _digest.length; i++)
+ if (_digest[i] != md5._digest[i]) return false;
+ return true;
+ }
+ else if (credentials instanceof Credential)
+ {
+ // Allow credential to attempt check - i.e. this'll work
+ // for DigestAuthModule$Digest credentials
+ return ((Credential) credentials).check(this);
+ }
+ else
+ {
+ LOG.warn("Can't check " + credentials.getClass() + " against MD5");
+ return false;
+ }
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ return false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String digest(String password)
+ {
+ try
+ {
+ byte[] digest;
+ synchronized (__md5Lock)
+ {
+ if (__md == null)
+ {
+ try
+ {
+ __md = MessageDigest.getInstance("MD5");
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ return null;
+ }
+ }
+
+ __md.reset();
+ __md.update(password.getBytes(StandardCharsets.ISO_8859_1));
+ digest = __md.digest();
+ }
+
+ return __TYPE + TypeUtil.toString(digest, 16);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ return null;
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.security;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Password utility class.
+ *
+ * This utility class gets a password or pass phrase either by:
+ *
+ * <PRE>
+ * + Password is set as a system property.
+ * + The password is prompted for and read from standard input
+ * + A program is run to get the password.
+ * </pre>
+ *
+ * Passwords that begin with OBF: are de obfuscated. Passwords can be obfuscated
+ * by run org.eclipse.util.Password as a main class. Obfuscated password are
+ * required if a system needs to recover the full password (eg. so that it may
+ * be passed to another system). They are not secure, but prevent casual
+ * observation.
+ * <p>
+ * Passwords that begin with CRYPT: are oneway encrypted with UnixCrypt. The
+ * real password cannot be retrieved, but comparisons can be made to other
+ * passwords. A Crypt can be generated by running org.eclipse.util.UnixCrypt as
+ * a main class, passing password and then the username. Checksum passwords are
+ * a secure(ish) way to store passwords that only need to be checked rather than
+ * recovered. Note that it is not strong security - specially if simple
+ * passwords are used.
+ *
+ *
+ */
+public class Password extends Credential
+{
+ private static final Logger LOG = Log.getLogger(Password.class);
+
+ private static final long serialVersionUID = 5062906681431569445L;
+
+ public static final String __OBFUSCATE = "OBF:";
+
+ private String _pw;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Constructor.
+ *
+ * @param password The String password.
+ */
+ public Password(String password)
+ {
+ _pw = password;
+
+ // expand password
+ while (_pw != null && _pw.startsWith(__OBFUSCATE))
+ _pw = deobfuscate(_pw);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return _pw;
+ }
+
+ /* ------------------------------------------------------------ */
+ public String toStarString()
+ {
+ return "*****************************************************".substring(0, _pw.length());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean check(Object credentials)
+ {
+ if (this == credentials) return true;
+
+ if (credentials instanceof Password) return credentials.equals(_pw);
+
+ if (credentials instanceof String) return credentials.equals(_pw);
+
+ if (credentials instanceof char[]) return Arrays.equals(_pw.toCharArray(), (char[]) credentials);
+
+ if (credentials instanceof Credential) return ((Credential) credentials).check(_pw);
+
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+
+ if (null == o)
+ return false;
+
+ if (o instanceof Password)
+ {
+ Password p = (Password) o;
+ //noinspection StringEquality
+ return p._pw == _pw || (null != _pw && _pw.equals(p._pw));
+ }
+
+ if (o instanceof String)
+ return o.equals(_pw);
+
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public int hashCode()
+ {
+ return null == _pw ? super.hashCode() : _pw.hashCode();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String obfuscate(String s)
+ {
+ StringBuilder buf = new StringBuilder();
+ byte[] b = s.getBytes(StandardCharsets.UTF_8);
+
+ buf.append(__OBFUSCATE);
+ for (int i = 0; i < b.length; i++)
+ {
+ byte b1 = b[i];
+ byte b2 = b[b.length - (i + 1)];
+ if (b1<0 || b2<0)
+ {
+ int i0 = (0xff&b1)*256 + (0xff&b2);
+ String x = Integer.toString(i0, 36).toLowerCase();
+ buf.append("U0000",0,5-x.length());
+ buf.append(x);
+ }
+ else
+ {
+ int i1 = 127 + b1 + b2;
+ int i2 = 127 + b1 - b2;
+ int i0 = i1 * 256 + i2;
+ String x = Integer.toString(i0, 36).toLowerCase();
+
+ int j0 = Integer.parseInt(x, 36);
+ int j1 = (i0 / 256);
+ int j2 = (i0 % 256);
+ byte bx = (byte) ((j1 + j2 - 254) / 2);
+
+ buf.append("000",0,4-x.length());
+ buf.append(x);
+ }
+
+ }
+ return buf.toString();
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public static String deobfuscate(String s)
+ {
+ if (s.startsWith(__OBFUSCATE)) s = s.substring(4);
+
+ byte[] b = new byte[s.length() / 2];
+ int l = 0;
+ for (int i = 0; i < s.length(); i += 4)
+ {
+ if (s.charAt(i)=='U')
+ {
+ i++;
+ String x = s.substring(i, i + 4);
+ int i0 = Integer.parseInt(x, 36);
+ byte bx = (byte)(i0>>8);
+ b[l++] = bx;
+ }
+ else
+ {
+ String x = s.substring(i, i + 4);
+ int i0 = Integer.parseInt(x, 36);
+ int i1 = (i0 / 256);
+ int i2 = (i0 % 256);
+ byte bx = (byte) ((i1 + i2 - 254) / 2);
+ b[l++] = bx;
+ }
+ }
+
+ return new String(b, 0, l,StandardCharsets.UTF_8);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Get a password. A password is obtained by trying
+ * <UL>
+ * <LI>Calling <Code>System.getProperty(realm,dft)</Code>
+ * <LI>Prompting for a password
+ * <LI>Using promptDft if nothing was entered.
+ * </UL>
+ *
+ * @param realm The realm name for the password, used as a SystemProperty
+ * name.
+ * @param dft The default password.
+ * @param promptDft The default to use if prompting for the password.
+ * @return Password
+ */
+ public static Password getPassword(String realm, String dft, String promptDft)
+ {
+ String passwd = System.getProperty(realm, dft);
+ if (passwd == null || passwd.length() == 0)
+ {
+ try
+ {
+ System.out.print(realm + ((promptDft != null && promptDft.length() > 0) ? " [dft]" : "") + " : ");
+ System.out.flush();
+ byte[] buf = new byte[512];
+ int len = System.in.read(buf);
+ if (len > 0) passwd = new String(buf, 0, len).trim();
+ }
+ catch (IOException e)
+ {
+ LOG.warn(Log.EXCEPTION, e);
+ }
+ if (passwd == null || passwd.length() == 0) passwd = promptDft;
+ }
+ return new Password(passwd);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param arg
+ */
+ public static void main(String[] arg)
+ {
+ if (arg.length != 1 && arg.length != 2)
+ {
+ System.err.println("Usage - java org.eclipse.jetty.security.Password [<user>] <password>");
+ System.err.println("If the password is ?, the user will be prompted for the password");
+ System.exit(1);
+ }
+ String p = arg[arg.length == 1 ? 0 : 1];
+ Password pw = new Password(p);
+ System.err.println(pw.toString());
+ System.err.println(obfuscate(pw.toString()));
+ System.err.println(Credential.MD5.digest(p));
+ if (arg.length == 2) System.err.println(Credential.Crypt.crypt(arg[0], pw.toString()));
+ }
+}
--- /dev/null
+/*
+ * @(#)UnixCrypt.java 0.9 96/11/25
+ *
+ * Copyright (c) 1996 Aki Yoshida. All rights reserved.
+ *
+ * Permission to use, copy, modify and distribute this software
+ * for non-commercial or commercial purposes and without fee is
+ * hereby granted provided that this copyright notice appears in
+ * all copies.
+ */
+
+/**
+ * Unix crypt(3C) utility
+ *
+ * @version 0.9, 11/25/96
+ * @author Aki Yoshida
+ */
+
+/**
+ * modified April 2001
+ * by Iris Van den Broeke, Daniel Deville
+ */
+
+package org.eclipse.jetty.util.security;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Unix Crypt. Implements the one way cryptography used by Unix systems for
+ * simple password protection.
+ *
+ * @version $Id: UnixCrypt.java,v 1.1 2005/10/05 14:09:14 janb Exp $
+ * @author Greg Wilkins (gregw)
+ */
+public class UnixCrypt
+{
+
+ /* (mostly) Standard DES Tables from Tom Truscott */
+ private static final byte[] IP = { /* initial permutation */
+ 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1,
+ 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 };
+
+ /* The final permutation is the inverse of IP - no table is necessary */
+ private static final byte[] ExpandTr = { /* expansion operation */
+ 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29,
+ 28, 29, 30, 31, 32, 1 };
+
+ private static final byte[] PC1 = { /* permuted choice table 1 */
+ 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
+
+ 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 };
+
+ private static final byte[] Rotates = { /* PC1 rotation schedule */
+ 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
+
+ private static final byte[] PC2 = { /* permuted choice table 2 */
+ 9, 18, 14, 17, 11, 24, 1, 5, 22, 25, 3, 28, 15, 6, 21, 10, 35, 38, 23, 19, 12, 4, 26, 8, 43, 54, 16, 7, 27, 20, 13, 2,
+
+ 0, 0, 41, 52, 31, 37, 47, 55, 0, 0, 30, 40, 51, 45, 33, 48, 0, 0, 44, 49, 39, 56, 34, 53, 0, 0, 46, 42, 50, 36, 29, 32 };
+
+ private static final byte[][] S = { /* 48->32 bit substitution tables */
+ /* S[1] */
+ { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9,
+ 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 },
+ /* S[2] */
+ { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12,
+ 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 },
+ /* S[3] */
+ { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2,
+ 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 },
+ /* S[4] */
+ { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3,
+ 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 },
+ /* S[5] */
+ { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12,
+ 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 },
+ /* S[6] */
+ { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4,
+ 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 },
+ /* S[7] */
+ { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15,
+ 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 },
+ /* S[8] */
+ { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10,
+ 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } };
+
+ private static final byte[] P32Tr = { /* 32-bit permutation function */
+ 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 };
+
+ private static final byte[] CIFP = { /*
+ * compressed/interleaved
+ * permutation
+ */
+ 1, 2, 3, 4, 17, 18, 19, 20, 5, 6, 7, 8, 21, 22, 23, 24, 9, 10, 11, 12, 25, 26, 27, 28, 13, 14, 15, 16, 29, 30, 31, 32,
+
+ 33, 34, 35, 36, 49, 50, 51, 52, 37, 38, 39, 40, 53, 54, 55, 56, 41, 42, 43, 44, 57, 58, 59, 60, 45, 46, 47, 48, 61, 62, 63, 64 };
+
+ private static final byte[] ITOA64 = { /* 0..63 => ascii-64 */
+ (byte) '.', (byte) '/', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A',
+ (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M',
+ (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y',
+ (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k',
+ (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w',
+ (byte) 'x', (byte) 'y', (byte) 'z' };
+
+ /* ===== Tables that are initialized at run time ==================== */
+
+ private static final byte[] A64TOI = new byte[128]; /* ascii-64 => 0..63 */
+
+ /* Initial key schedule permutation */
+ private static final long[][] PC1ROT = new long[16][16];
+
+ /* Subsequent key schedule rotation permutations */
+ private static final long[][][] PC2ROT = new long[2][16][16];
+
+ /* Initial permutation/expansion table */
+ private static final long[][] IE3264 = new long[8][16];
+
+ /* Table that combines the S, P, and E operations. */
+ private static final long[][] SPE = new long[8][64];
+
+ /* compressed/interleaved => final permutation table */
+ private static final long[][] CF6464 = new long[16][16];
+
+ /* ==================================== */
+
+ static
+ {
+ byte[] perm = new byte[64];
+ byte[] temp = new byte[64];
+
+ // inverse table.
+ for (int i = 0; i < 64; i++)
+ A64TOI[ITOA64[i]] = (byte) i;
+
+ // PC1ROT - bit reverse, then PC1, then Rotate, then PC2
+ for (int i = 0; i < 64; i++)
+ perm[i] = (byte) 0;
+
+ for (int i = 0; i < 64; i++)
+ {
+ int k;
+ if ((k = PC2[i]) == 0) continue;
+ k += Rotates[0] - 1;
+ if ((k % 28) < Rotates[0]) k -= 28;
+ k = PC1[k];
+ if (k > 0)
+ {
+ k--;
+ k = (k | 0x07) - (k & 0x07);
+ k++;
+ }
+ perm[i] = (byte) k;
+ }
+ init_perm(PC1ROT, perm, 8);
+
+ // PC2ROT - PC2 inverse, then Rotate, then PC2
+ for (int j = 0; j < 2; j++)
+ {
+ int k;
+ for (int i = 0; i < 64; i++)
+ perm[i] = temp[i] = 0;
+ for (int i = 0; i < 64; i++)
+ {
+ if ((k = PC2[i]) == 0) continue;
+ temp[k - 1] = (byte) (i + 1);
+ }
+ for (int i = 0; i < 64; i++)
+ {
+ if ((k = PC2[i]) == 0) continue;
+ k += j;
+ if ((k % 28) <= j) k -= 28;
+ perm[i] = temp[k];
+ }
+
+ init_perm(PC2ROT[j], perm, 8);
+ }
+
+ // Bit reverse, intial permupation, expantion
+ for (int i = 0; i < 8; i++)
+ {
+ for (int j = 0; j < 8; j++)
+ {
+ int k = (j < 2) ? 0 : IP[ExpandTr[i * 6 + j - 2] - 1];
+ if (k > 32)
+ k -= 32;
+ else if (k > 0) k--;
+ if (k > 0)
+ {
+ k--;
+ k = (k | 0x07) - (k & 0x07);
+ k++;
+ }
+ perm[i * 8 + j] = (byte) k;
+ }
+ }
+
+ init_perm(IE3264, perm, 8);
+
+ // Compression, final permutation, bit reverse
+ for (int i = 0; i < 64; i++)
+ {
+ int k = IP[CIFP[i] - 1];
+ if (k > 0)
+ {
+ k--;
+ k = (k | 0x07) - (k & 0x07);
+ k++;
+ }
+ perm[k - 1] = (byte) (i + 1);
+ }
+
+ init_perm(CF6464, perm, 8);
+
+ // SPE table
+ for (int i = 0; i < 48; i++)
+ perm[i] = P32Tr[ExpandTr[i] - 1];
+ for (int t = 0; t < 8; t++)
+ {
+ for (int j = 0; j < 64; j++)
+ {
+ int k = (((j >> 0) & 0x01) << 5) | (((j >> 1) & 0x01) << 3)
+ | (((j >> 2) & 0x01) << 2)
+ | (((j >> 3) & 0x01) << 1)
+ | (((j >> 4) & 0x01) << 0)
+ | (((j >> 5) & 0x01) << 4);
+ k = S[t][k];
+ k = (((k >> 3) & 0x01) << 0) | (((k >> 2) & 0x01) << 1) | (((k >> 1) & 0x01) << 2) | (((k >> 0) & 0x01) << 3);
+ for (int i = 0; i < 32; i++)
+ temp[i] = 0;
+ for (int i = 0; i < 4; i++)
+ temp[4 * t + i] = (byte) ((k >> i) & 0x01);
+ long kk = 0;
+ for (int i = 24; --i >= 0;)
+ kk = ((kk << 1) | ((long) temp[perm[i] - 1]) << 32 | (temp[perm[i + 24] - 1]));
+
+ SPE[t][j] = to_six_bit(kk);
+ }
+ }
+ }
+
+ /**
+ * You can't call the constructer.
+ */
+ private UnixCrypt()
+ {
+ }
+
+ /**
+ * Returns the transposed and split code of a 24-bit code into a 4-byte
+ * code, each having 6 bits.
+ */
+ private static int to_six_bit(int num)
+ {
+ return (((num << 26) & 0xfc000000) | ((num << 12) & 0xfc0000) | ((num >> 2) & 0xfc00) | ((num >> 16) & 0xfc));
+ }
+
+ /**
+ * Returns the transposed and split code of two 24-bit code into two 4-byte
+ * code, each having 6 bits.
+ */
+ private static long to_six_bit(long num)
+ {
+ return (((num << 26) & 0xfc000000fc000000L) | ((num << 12) & 0xfc000000fc0000L) | ((num >> 2) & 0xfc000000fc00L) | ((num >> 16) & 0xfc000000fcL));
+ }
+
+ /**
+ * Returns the permutation of the given 64-bit code with the specified
+ * permutataion table.
+ */
+ private static long perm6464(long c, long[][] p)
+ {
+ long out = 0L;
+ for (int i = 8; --i >= 0;)
+ {
+ int t = (int) (0x00ff & c);
+ c >>= 8;
+ long tp = p[i << 1][t & 0x0f];
+ out |= tp;
+ tp = p[(i << 1) + 1][t >> 4];
+ out |= tp;
+ }
+ return out;
+ }
+
+ /**
+ * Returns the permutation of the given 32-bit code with the specified
+ * permutataion table.
+ */
+ private static long perm3264(int c, long[][] p)
+ {
+ long out = 0L;
+ for (int i = 4; --i >= 0;)
+ {
+ int t = (0x00ff & c);
+ c >>= 8;
+ long tp = p[i << 1][t & 0x0f];
+ out |= tp;
+ tp = p[(i << 1) + 1][t >> 4];
+ out |= tp;
+ }
+ return out;
+ }
+
+ /**
+ * Returns the key schedule for the given key.
+ */
+ private static long[] des_setkey(long keyword)
+ {
+ long K = perm6464(keyword, PC1ROT);
+ long[] KS = new long[16];
+ KS[0] = K & ~0x0303030300000000L;
+
+ for (int i = 1; i < 16; i++)
+ {
+ KS[i] = K;
+ K = perm6464(K, PC2ROT[Rotates[i] - 1]);
+
+ KS[i] = K & ~0x0303030300000000L;
+ }
+ return KS;
+ }
+
+ /**
+ * Returns the DES encrypted code of the given word with the specified
+ * environment.
+ */
+ private static long des_cipher(long in, int salt, int num_iter, long[] KS)
+ {
+ salt = to_six_bit(salt);
+ long L = in;
+ long R = L;
+ L &= 0x5555555555555555L;
+ R = (R & 0xaaaaaaaa00000000L) | ((R >> 1) & 0x0000000055555555L);
+ L = ((((L << 1) | (L << 32)) & 0xffffffff00000000L) | ((R | (R >> 32)) & 0x00000000ffffffffL));
+
+ L = perm3264((int) (L >> 32), IE3264);
+ R = perm3264((int) (L & 0xffffffff), IE3264);
+
+ while (--num_iter >= 0)
+ {
+ for (int loop_count = 0; loop_count < 8; loop_count++)
+ {
+ long kp;
+ long B;
+ long k;
+
+ kp = KS[(loop_count << 1)];
+ k = ((R >> 32) ^ R) & salt & 0xffffffffL;
+ k |= (k << 32);
+ B = (k ^ R ^ kp);
+
+ L ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)]
+ ^ SPE[2][(int) ((B >> 42) & 0x3f)]
+ ^ SPE[3][(int) ((B >> 34) & 0x3f)]
+ ^ SPE[4][(int) ((B >> 26) & 0x3f)]
+ ^ SPE[5][(int) ((B >> 18) & 0x3f)]
+ ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]);
+
+ kp = KS[(loop_count << 1) + 1];
+ k = ((L >> 32) ^ L) & salt & 0xffffffffL;
+ k |= (k << 32);
+ B = (k ^ L ^ kp);
+
+ R ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)]
+ ^ SPE[2][(int) ((B >> 42) & 0x3f)]
+ ^ SPE[3][(int) ((B >> 34) & 0x3f)]
+ ^ SPE[4][(int) ((B >> 26) & 0x3f)]
+ ^ SPE[5][(int) ((B >> 18) & 0x3f)]
+ ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]);
+ }
+ // swap L and R
+ L ^= R;
+ R ^= L;
+ L ^= R;
+ }
+ L = ((((L >> 35) & 0x0f0f0f0fL) | (((L & 0xffffffff) << 1) & 0xf0f0f0f0L)) << 32 | (((R >> 35) & 0x0f0f0f0fL) | (((R & 0xffffffff) << 1) & 0xf0f0f0f0L)));
+
+ L = perm6464(L, CF6464);
+
+ return L;
+ }
+
+ /**
+ * Initializes the given permutation table with the mapping table.
+ */
+ private static void init_perm(long[][] perm, byte[] p, int chars_out)
+ {
+ for (int k = 0; k < chars_out * 8; k++)
+ {
+
+ int l = p[k] - 1;
+ if (l < 0) continue;
+ int i = l >> 2;
+ l = 1 << (l & 0x03);
+ for (int j = 0; j < 16; j++)
+ {
+ int s = ((k & 0x07) + ((7 - (k >> 3)) << 3));
+ if ((j & l) != 0x00) perm[i][j] |= (1L << s);
+ }
+ }
+ }
+
+ /**
+ * Encrypts String into crypt (Unix) code.
+ *
+ * @param key the key to be encrypted
+ * @param setting the salt to be used
+ * @return the encrypted String
+ */
+ public static String crypt(String key, String setting)
+ {
+ long constdatablock = 0L; /* encryption constant */
+ byte[] cryptresult = new byte[13]; /* encrypted result */
+ long keyword = 0L;
+ /* invalid parameters! */
+ if (key == null || setting == null) return "*"; // will NOT match under
+ // ANY circumstances!
+
+ int keylen = key.length();
+
+ for (int i = 0; i < 8; i++)
+ {
+ keyword = (keyword << 8) | ((i < keylen) ? 2 * key.charAt(i) : 0);
+ }
+
+ long[] KS = des_setkey(keyword);
+
+ int salt = 0;
+ for (int i = 2; --i >= 0;)
+ {
+ char c = (i < setting.length()) ? setting.charAt(i) : '.';
+ cryptresult[i] = (byte) c;
+ salt = (salt << 6) | (0x00ff & A64TOI[c]);
+ }
+
+ long rsltblock = des_cipher(constdatablock, salt, 25, KS);
+
+ cryptresult[12] = ITOA64[(((int) rsltblock) << 2) & 0x3f];
+ rsltblock >>= 4;
+ for (int i = 12; --i >= 2;)
+ {
+ cryptresult[i] = ITOA64[((int) rsltblock) & 0x3f];
+ rsltblock >>= 6;
+ }
+
+ return new String(cryptresult, 0, 13);
+ }
+
+ public static void main(String[] arg)
+ {
+ if (arg.length != 2)
+ {
+ System.err.println("Usage - java org.eclipse.util.UnixCrypt <key> <salt>");
+ System.exit(1);
+ }
+
+ System.err.println("Crypt=" + crypt(arg[0], arg[1]));
+ }
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Common Security Utilities
+ */
+package org.eclipse.jetty.util.security;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.ssl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * KeyManager to select a key with desired alias
+ * while delegating processing to specified KeyManager
+ * Can be used both with server and client sockets
+ */
+public class AliasedX509ExtendedKeyManager extends X509ExtendedKeyManager
+{
+ private String _keyAlias;
+ private X509KeyManager _keyManager;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Construct KeyManager instance
+ * @param keyAlias Alias of the key to be selected
+ * @param keyManager Instance of KeyManager to be wrapped
+ * @throws Exception
+ */
+ public AliasedX509ExtendedKeyManager(String keyAlias, X509KeyManager keyManager) throws Exception
+ {
+ _keyAlias = keyAlias;
+ _keyManager = keyManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket)
+ */
+ public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
+ {
+ return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
+ */
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
+ {
+ return _keyAlias == null ? _keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[])
+ */
+ public String[] getClientAliases(String keyType, Principal[] issuers)
+ {
+ return _keyManager.getClientAliases(keyType, issuers);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[])
+ */
+ public String[] getServerAliases(String keyType, Principal[] issuers)
+ {
+ return _keyManager.getServerAliases(keyType, issuers);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String)
+ */
+ public X509Certificate[] getCertificateChain(String alias)
+ {
+ return _keyManager.getCertificateChain(alias);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String)
+ */
+ public PrivateKey getPrivateKey(String alias)
+ {
+ return _keyManager.getPrivateKey(alias);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineServerAlias(java.lang.String, java.security.Principal[], javax.net.ssl.SSLEngine)
+ */
+ @Override
+ public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
+ {
+ return _keyAlias == null ? super.chooseEngineServerAlias(keyType,issuers,engine) : _keyAlias;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineClientAlias(String[], Principal[], SSLEngine)
+ */
+ @Override
+ public String chooseEngineClientAlias(String keyType[], Principal[] issuers, SSLEngine engine)
+ {
+ return _keyAlias == null ? super.chooseEngineClientAlias(keyType,issuers,engine) : _keyAlias;
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.ssl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509KeyManager;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * KeyManager to select a key with desired alias
+ * while delegating processing to specified KeyManager
+ * Can be used both with server and client sockets
+ */
+public class AliasedX509KeyManager implements X509KeyManager
+{
+ private String _keyAlias;
+ private X509KeyManager _keyManager;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Construct KeyManager instance
+ * @param keyAlias Alias of the key to be selected
+ * @param keyManager Instance of KeyManager to be wrapped
+ * @throws Exception
+ */
+ public AliasedX509KeyManager(String keyAlias, X509KeyManager keyManager) throws Exception
+ {
+ _keyAlias = keyAlias;
+ _keyManager = keyManager;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket)
+ */
+ public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
+ {
+ return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
+ */
+ public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
+ {
+ return _keyAlias == null ?_keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[])
+ */
+ public String[] getClientAliases(String keyType, Principal[] issuers)
+ {
+ return _keyManager.getClientAliases(keyType, issuers);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[])
+ */
+ public String[] getServerAliases(String keyType, Principal[] issuers)
+ {
+ return _keyManager.getServerAliases(keyType, issuers);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String)
+ */
+ public X509Certificate[] getCertificateChain(String alias)
+ {
+ return _keyManager.getCertificateChain(alias);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String)
+ */
+ public PrivateKey getPrivateKey(String alias)
+ {
+ return _keyManager.getPrivateKey(alias);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.ssl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Password;
+
+
+/**
+ * SslContextFactory is used to configure SSL connectors
+ * as well as HttpClient. It holds all SSL parameters and
+ * creates SSL context based on these parameters to be
+ * used by the SSL connectors.
+ */
+public class SslContextFactory extends AbstractLifeCycle
+{
+ public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager()
+ {
+ public java.security.cert.X509Certificate[] getAcceptedIssuers()
+ {
+ return new java.security.cert.X509Certificate[]{};
+ }
+
+ public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
+ {
+ }
+
+ public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
+ {
+ }
+ }};
+
+ static final Logger LOG = Log.getLogger(SslContextFactory.class);
+
+ public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
+ (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
+ KeyManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
+
+ public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM =
+ (Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ?
+ TrustManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.TrustManagerFactory.algorithm"));
+
+ /** String name of key password property. */
+ public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";
+
+ /** String name of keystore password property. */
+ public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+ /** Excluded protocols. */
+ private final Set<String> _excludeProtocols = new LinkedHashSet<>();
+
+ /** Included protocols. */
+ private Set<String> _includeProtocols = null;
+
+ /** Excluded cipher suites. */
+ private final Set<String> _excludeCipherSuites = new LinkedHashSet<>();
+ /** Included cipher suites. */
+ private Set<String> _includeCipherSuites = null;
+
+ /** Keystore path. */
+ private String _keyStorePath;
+ /** Keystore provider name */
+ private String _keyStoreProvider;
+ /** Keystore type */
+ private String _keyStoreType = "JKS";
+ /** Keystore input stream */
+ private InputStream _keyStoreInputStream;
+
+ /** SSL certificate alias */
+ private String _certAlias;
+
+ /** Truststore path */
+ private String _trustStorePath;
+ /** Truststore provider name */
+ private String _trustStoreProvider;
+ /** Truststore type */
+ private String _trustStoreType = "JKS";
+ /** Truststore input stream */
+ private InputStream _trustStoreInputStream;
+
+ /** Set to true if client certificate authentication is required */
+ private boolean _needClientAuth = false;
+ /** Set to true if client certificate authentication is desired */
+ private boolean _wantClientAuth = false;
+
+ /** Keystore password */
+ private transient Password _keyStorePassword;
+ /** Key manager password */
+ private transient Password _keyManagerPassword;
+ /** Truststore password */
+ private transient Password _trustStorePassword;
+
+ /** SSL provider name */
+ private String _sslProvider;
+ /** SSL protocol name */
+ private String _sslProtocol = "TLS";
+
+ /** SecureRandom algorithm */
+ private String _secureRandomAlgorithm;
+ /** KeyManager factory algorithm */
+ private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM;
+ /** TrustManager factory algorithm */
+ private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM;
+
+ /** Set to true if SSL certificate validation is required */
+ private boolean _validateCerts;
+ /** Set to true if SSL certificate of the peer validation is required */
+ private boolean _validatePeerCerts;
+ /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+ private int _maxCertPathLength = -1;
+ /** Path to file that contains Certificate Revocation List */
+ private String _crlPath;
+ /** Set to true to enable CRL Distribution Points (CRLDP) support */
+ private boolean _enableCRLDP = false;
+ /** Set to true to enable On-Line Certificate Status Protocol (OCSP) support */
+ private boolean _enableOCSP = false;
+ /** Location of OCSP Responder */
+ private String _ocspResponderURL;
+
+ /** SSL keystore */
+ private KeyStore _keyStore;
+ /** SSL truststore */
+ private KeyStore _trustStore;
+ /** Set to true to enable SSL Session caching */
+ private boolean _sessionCachingEnabled = true;
+ /** SSL session cache size */
+ private int _sslSessionCacheSize;
+ /** SSL session timeout */
+ private int _sslSessionTimeout;
+
+ /** SSL context */
+ private SSLContext _context;
+
+ /** EndpointIdentificationAlgorithm - when set to "HTTPS" hostname verification will be enabled */
+ private String _endpointIdentificationAlgorithm = null;
+
+ /** Whether to blindly trust certificates */
+ private boolean _trustAll;
+
+ /** Whether TLS renegotiation is allowed */
+ private boolean _renegotiationAllowed = true;
+
+ /**
+ * Construct an instance of SslContextFactory
+ * Default constructor for use in XmlConfiguration files
+ */
+ public SslContextFactory()
+ {
+ this(false);
+ }
+
+ /**
+ * Construct an instance of SslContextFactory
+ * Default constructor for use in XmlConfiguration files
+ * @param trustAll whether to blindly trust all certificates
+ * @see #setTrustAll(boolean)
+ */
+ public SslContextFactory(boolean trustAll)
+ {
+ setTrustAll(trustAll);
+ }
+
+ /**
+ * Construct an instance of SslContextFactory
+ * @param keyStorePath default keystore location
+ */
+ public SslContextFactory(String keyStorePath)
+ {
+ _keyStorePath = keyStorePath;
+ }
+
+ /**
+ * Create the SSLContext object and start the lifecycle
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+ */
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (_context == null)
+ {
+ if (_keyStore==null && _keyStoreInputStream == null && _keyStorePath == null &&
+ _trustStore==null && _trustStoreInputStream == null && _trustStorePath == null )
+ {
+ TrustManager[] trust_managers=null;
+
+ if (_trustAll)
+ {
+ LOG.debug("No keystore or trust store configured. ACCEPTING UNTRUSTED CERTIFICATES!!!!!");
+ // Create a trust manager that does not validate certificate chains
+ trust_managers = TRUST_ALL_CERTS;
+ }
+
+ SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+ SSLContext context = SSLContext.getInstance(_sslProtocol);
+ context.init(null, trust_managers, secureRandom);
+ _context = context;
+ }
+ else
+ {
+ // verify that keystore and truststore
+ // parameters are set up correctly
+ checkKeyStore();
+
+ KeyStore keyStore = loadKeyStore();
+ KeyStore trustStore = loadTrustStore();
+
+ Collection<? extends CRL> crls = loadCRL(_crlPath);
+
+ if (_validateCerts && keyStore != null)
+ {
+ if (_certAlias == null)
+ {
+ List<String> aliases = Collections.list(keyStore.aliases());
+ _certAlias = aliases.size() == 1 ? aliases.get(0) : null;
+ }
+
+ Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias);
+ if (cert == null)
+ {
+ throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias));
+ }
+
+ CertificateValidator validator = new CertificateValidator(trustStore, crls);
+ validator.setMaxCertPathLength(_maxCertPathLength);
+ validator.setEnableCRLDP(_enableCRLDP);
+ validator.setEnableOCSP(_enableOCSP);
+ validator.setOcspResponderURL(_ocspResponderURL);
+ validator.validate(keyStore, cert);
+ }
+
+ KeyManager[] keyManagers = getKeyManagers(keyStore);
+ TrustManager[] trustManagers = getTrustManagers(trustStore,crls);
+
+ SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+ SSLContext context = _sslProvider == null ? SSLContext.getInstance(_sslProtocol) : SSLContext.getInstance(_sslProtocol,_sslProvider);
+ context.init(keyManagers,trustManagers,secureRandom);
+ _context = context;
+ }
+
+ SSLEngine engine = newSSLEngine();
+ LOG.debug("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols()));
+ if (LOG.isDebugEnabled())
+ LOG.debug("Enabled Ciphers {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites()));
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ _context = null;
+ super.doStop();
+ }
+
+ /**
+ * @return The array of protocol names to exclude from
+ * {@link SSLEngine#setEnabledProtocols(String[])}
+ */
+ public String[] getExcludeProtocols()
+ {
+ return _excludeProtocols.toArray(new String[_excludeProtocols.size()]);
+ }
+
+ /**
+ * @param protocols
+ * The array of protocol names to exclude from
+ * {@link SSLEngine#setEnabledProtocols(String[])}
+ */
+ public void setExcludeProtocols(String... protocols)
+ {
+ checkNotStarted();
+ _excludeProtocols.clear();
+ _excludeProtocols.addAll(Arrays.asList(protocols));
+ }
+
+ /**
+ * @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])}
+ */
+ public void addExcludeProtocols(String... protocol)
+ {
+ checkNotStarted();
+ _excludeProtocols.addAll(Arrays.asList(protocol));
+ }
+
+ /**
+ * @return The array of protocol names to include in
+ * {@link SSLEngine#setEnabledProtocols(String[])}
+ */
+ public String[] getIncludeProtocols()
+ {
+ return _includeProtocols.toArray(new String[_includeProtocols.size()]);
+ }
+
+ /**
+ * @param protocols
+ * The array of protocol names to include in
+ * {@link SSLEngine#setEnabledProtocols(String[])}
+ */
+ public void setIncludeProtocols(String... protocols)
+ {
+ checkNotStarted();
+ _includeProtocols = new LinkedHashSet<>(Arrays.asList(protocols));
+ }
+
+ /**
+ * @return The array of cipher suite names to exclude from
+ * {@link SSLEngine#setEnabledCipherSuites(String[])}
+ */
+ public String[] getExcludeCipherSuites()
+ {
+ return _excludeCipherSuites.toArray(new String[_excludeCipherSuites.size()]);
+ }
+
+ /**
+ * You can either use the exact cipher suite name or a a regular expression.
+ * @param cipherSuites
+ * The array of cipher suite names to exclude from
+ * {@link SSLEngine#setEnabledCipherSuites(String[])}
+ */
+ public void setExcludeCipherSuites(String... cipherSuites)
+ {
+ checkNotStarted();
+ _excludeCipherSuites.clear();
+ _excludeCipherSuites.addAll(Arrays.asList(cipherSuites));
+ }
+
+ /**
+ * @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])}
+ */
+ public void addExcludeCipherSuites(String... cipher)
+ {
+ checkNotStarted();
+ _excludeCipherSuites.addAll(Arrays.asList(cipher));
+ }
+
+ /**
+ * @return The array of cipher suite names to include in
+ * {@link SSLEngine#setEnabledCipherSuites(String[])}
+ */
+ public String[] getIncludeCipherSuites()
+ {
+ return _includeCipherSuites.toArray(new String[_includeCipherSuites.size()]);
+ }
+
+ /**
+ * You can either use the exact cipher suite name or a a regular expression.
+ * @param cipherSuites
+ * The array of cipher suite names to include in
+ * {@link SSLEngine#setEnabledCipherSuites(String[])}
+ */
+ public void setIncludeCipherSuites(String... cipherSuites)
+ {
+ checkNotStarted();
+ _includeCipherSuites = new LinkedHashSet<>(Arrays.asList(cipherSuites));
+ }
+
+ /**
+ * @return The file or URL of the SSL Key store.
+ */
+ public String getKeyStorePath()
+ {
+ return _keyStorePath;
+ }
+
+ /**
+ * @param keyStorePath
+ * The file or URL of the SSL Key store.
+ */
+ public void setKeyStorePath(String keyStorePath)
+ {
+ checkNotStarted();
+ _keyStorePath = keyStorePath;
+ }
+
+ /**
+ * @return The provider of the key store
+ */
+ public String getKeyStoreProvider()
+ {
+ return _keyStoreProvider;
+ }
+
+ /**
+ * @param keyStoreProvider
+ * The provider of the key store
+ */
+ public void setKeyStoreProvider(String keyStoreProvider)
+ {
+ checkNotStarted();
+ _keyStoreProvider = keyStoreProvider;
+ }
+
+ /**
+ * @return The type of the key store (default "JKS")
+ */
+ public String getKeyStoreType()
+ {
+ return (_keyStoreType);
+ }
+
+ /**
+ * @param keyStoreType
+ * The type of the key store (default "JKS")
+ */
+ public void setKeyStoreType(String keyStoreType)
+ {
+ checkNotStarted();
+ _keyStoreType = keyStoreType;
+ }
+
+ /**
+ * @return Alias of SSL certificate for the connector
+ */
+ public String getCertAlias()
+ {
+ return _certAlias;
+ }
+
+ /**
+ * @param certAlias
+ * Alias of SSL certificate for the connector
+ */
+ public void setCertAlias(String certAlias)
+ {
+ checkNotStarted();
+ _certAlias = certAlias;
+ }
+
+ /**
+ * @return The file name or URL of the trust store location
+ */
+ public String getTrustStore()
+ {
+ return _trustStorePath;
+ }
+
+ /**
+ * @param trustStorePath
+ * The file name or URL of the trust store location
+ */
+ public void setTrustStorePath(String trustStorePath)
+ {
+ checkNotStarted();
+ _trustStorePath = trustStorePath;
+ }
+
+ /**
+ * @return The provider of the trust store
+ */
+ public String getTrustStoreProvider()
+ {
+ return _trustStoreProvider;
+ }
+
+ /**
+ * @param trustStoreProvider
+ * The provider of the trust store
+ */
+ public void setTrustStoreProvider(String trustStoreProvider)
+ {
+ checkNotStarted();
+ _trustStoreProvider = trustStoreProvider;
+ }
+
+ /**
+ * @return The type of the trust store (default "JKS")
+ */
+ public String getTrustStoreType()
+ {
+ return _trustStoreType;
+ }
+
+ /**
+ * @param trustStoreType
+ * The type of the trust store (default "JKS")
+ */
+ public void setTrustStoreType(String trustStoreType)
+ {
+ checkNotStarted();
+ _trustStoreType = trustStoreType;
+ }
+
+ /**
+ * @return True if SSL needs client authentication.
+ * @see SSLEngine#getNeedClientAuth()
+ */
+ public boolean getNeedClientAuth()
+ {
+ return _needClientAuth;
+ }
+
+ /**
+ * @param needClientAuth
+ * True if SSL needs client authentication.
+ * @see SSLEngine#getNeedClientAuth()
+ */
+ public void setNeedClientAuth(boolean needClientAuth)
+ {
+ checkNotStarted();
+ _needClientAuth = needClientAuth;
+ }
+
+ /**
+ * @return True if SSL wants client authentication.
+ * @see SSLEngine#getWantClientAuth()
+ */
+ public boolean getWantClientAuth()
+ {
+ return _wantClientAuth;
+ }
+
+ /**
+ * @param wantClientAuth
+ * True if SSL wants client authentication.
+ * @see SSLEngine#getWantClientAuth()
+ */
+ public void setWantClientAuth(boolean wantClientAuth)
+ {
+ checkNotStarted();
+ _wantClientAuth = wantClientAuth;
+ }
+
+ /**
+ * @return true if SSL certificate has to be validated
+ */
+ public boolean isValidateCerts()
+ {
+ return _validateCerts;
+ }
+
+ /**
+ * @param validateCerts
+ * true if SSL certificates have to be validated
+ */
+ public void setValidateCerts(boolean validateCerts)
+ {
+ checkNotStarted();
+ _validateCerts = validateCerts;
+ }
+
+ /**
+ * @return true if SSL certificates of the peer have to be validated
+ */
+ public boolean isValidatePeerCerts()
+ {
+ return _validatePeerCerts;
+ }
+
+ /**
+ * @param validatePeerCerts
+ * true if SSL certificates of the peer have to be validated
+ */
+ public void setValidatePeerCerts(boolean validatePeerCerts)
+ {
+ checkNotStarted();
+ _validatePeerCerts = validatePeerCerts;
+ }
+
+
+ /**
+ * @param password
+ * The password for the key store
+ */
+ public void setKeyStorePassword(String password)
+ {
+ checkNotStarted();
+ _keyStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+ }
+
+ /**
+ * @param password
+ * The password (if any) for the specific key within the key store
+ */
+ public void setKeyManagerPassword(String password)
+ {
+ checkNotStarted();
+ _keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
+ }
+
+ /**
+ * @param password
+ * The password for the trust store
+ */
+ public void setTrustStorePassword(String password)
+ {
+ checkNotStarted();
+ _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+ }
+
+ /**
+ * @return The SSL provider name, which if set is passed to
+ * {@link SSLContext#getInstance(String, String)}
+ */
+ public String getProvider()
+ {
+ return _sslProvider;
+ }
+
+ /**
+ * @param provider
+ * The SSL provider name, which if set is passed to
+ * {@link SSLContext#getInstance(String, String)}
+ */
+ public void setProvider(String provider)
+ {
+ checkNotStarted();
+ _sslProvider = provider;
+ }
+
+ /**
+ * @return The SSL protocol (default "TLS") passed to
+ * {@link SSLContext#getInstance(String, String)}
+ */
+ public String getProtocol()
+ {
+ return _sslProtocol;
+ }
+
+ /**
+ * @param protocol
+ * The SSL protocol (default "TLS") passed to
+ * {@link SSLContext#getInstance(String, String)}
+ */
+ public void setProtocol(String protocol)
+ {
+ checkNotStarted();
+ _sslProtocol = protocol;
+ }
+
+ /**
+ * @return The algorithm name, which if set is passed to
+ * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
+ * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+ */
+ public String getSecureRandomAlgorithm()
+ {
+ return _secureRandomAlgorithm;
+ }
+
+ /**
+ * @param algorithm
+ * The algorithm name, which if set is passed to
+ * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
+ * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+ */
+ public void setSecureRandomAlgorithm(String algorithm)
+ {
+ checkNotStarted();
+ _secureRandomAlgorithm = algorithm;
+ }
+
+ /**
+ * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
+ */
+ public String getSslKeyManagerFactoryAlgorithm()
+ {
+ return (_keyManagerFactoryAlgorithm);
+ }
+
+ /**
+ * @param algorithm
+ * The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
+ */
+ public void setSslKeyManagerFactoryAlgorithm(String algorithm)
+ {
+ checkNotStarted();
+ _keyManagerFactoryAlgorithm = algorithm;
+ }
+
+ /**
+ * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+ */
+ public String getTrustManagerFactoryAlgorithm()
+ {
+ return (_trustManagerFactoryAlgorithm);
+ }
+
+ /**
+ * @return True if all certificates should be trusted if there is no KeyStore or TrustStore
+ */
+ public boolean isTrustAll()
+ {
+ return _trustAll;
+ }
+
+ /**
+ * @param trustAll True if all certificates should be trusted if there is no KeyStore or TrustStore
+ */
+ public void setTrustAll(boolean trustAll)
+ {
+ _trustAll = trustAll;
+ if(trustAll)
+ setEndpointIdentificationAlgorithm(null);
+ }
+
+ /**
+ * @param algorithm
+ * The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+ * Use the string "TrustAll" to install a trust manager that trusts all.
+ */
+ public void setTrustManagerFactoryAlgorithm(String algorithm)
+ {
+ checkNotStarted();
+ _trustManagerFactoryAlgorithm = algorithm;
+ }
+
+ /**
+ * @return whether TLS renegotiation is allowed (true by default)
+ */
+ public boolean isRenegotiationAllowed()
+ {
+ return _renegotiationAllowed;
+ }
+
+ /**
+ * @param renegotiationAllowed whether TLS renegotiation is allowed
+ */
+ public void setRenegotiationAllowed(boolean renegotiationAllowed)
+ {
+ _renegotiationAllowed = renegotiationAllowed;
+ }
+
+ /**
+ * @return Path to file that contains Certificate Revocation List
+ */
+ public String getCrlPath()
+ {
+ return _crlPath;
+ }
+
+ /**
+ * @param crlPath
+ * Path to file that contains Certificate Revocation List
+ */
+ public void setCrlPath(String crlPath)
+ {
+ checkNotStarted();
+ _crlPath = crlPath;
+ }
+
+ /**
+ * @return Maximum number of intermediate certificates in
+ * the certification path (-1 for unlimited)
+ */
+ public int getMaxCertPathLength()
+ {
+ return _maxCertPathLength;
+ }
+
+ /**
+ * @param maxCertPathLength
+ * maximum number of intermediate certificates in
+ * the certification path (-1 for unlimited)
+ */
+ public void setMaxCertPathLength(int maxCertPathLength)
+ {
+ checkNotStarted();
+ _maxCertPathLength = maxCertPathLength;
+ }
+
+ /**
+ * @return The SSLContext
+ */
+ public SSLContext getSslContext()
+ {
+ if (!isStarted())
+ throw new IllegalStateException(getState());
+ return _context;
+ }
+
+ /**
+ * @param sslContext
+ * Set a preconfigured SSLContext
+ */
+ public void setSslContext(SSLContext sslContext)
+ {
+ checkNotStarted();
+ _context = sslContext;
+ }
+
+ /**
+ * When set to "HTTPS" hostname verification will be enabled
+ *
+ * @param endpointIdentificationAlgorithm Set the endpointIdentificationAlgorithm
+ */
+ public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm)
+ {
+ this._endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
+ }
+
+ /**
+ * Override this method to provide alternate way to load a keystore.
+ *
+ * @return the key store instance
+ * @throws Exception if the keystore cannot be loaded
+ */
+ protected KeyStore loadKeyStore() throws Exception
+ {
+ return _keyStore != null ? _keyStore : CertificateUtils.getKeyStore(_keyStoreInputStream,
+ _keyStorePath, _keyStoreType, _keyStoreProvider,
+ _keyStorePassword==null? null: _keyStorePassword.toString());
+ }
+
+ /**
+ * Override this method to provide alternate way to load a truststore.
+ *
+ * @return the key store instance
+ * @throws Exception if the truststore cannot be loaded
+ */
+ protected KeyStore loadTrustStore() throws Exception
+ {
+ return _trustStore != null ? _trustStore : CertificateUtils.getKeyStore(_trustStoreInputStream,
+ _trustStorePath, _trustStoreType, _trustStoreProvider,
+ _trustStorePassword==null? null: _trustStorePassword.toString());
+ }
+
+ /**
+ * Loads certificate revocation list (CRL) from a file.
+ *
+ * Required for integrations to be able to override the mechanism used to
+ * load CRL in order to provide their own implementation.
+ *
+ * @param crlPath path of certificate revocation list file
+ * @return Collection of CRL's
+ * @throws Exception if the certificate revocation list cannot be loaded
+ */
+ protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+ {
+ return CertificateUtils.loadCRL(crlPath);
+ }
+
+ protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception
+ {
+ KeyManager[] managers = null;
+
+ if (keyStore != null)
+ {
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerFactoryAlgorithm);
+ keyManagerFactory.init(keyStore,_keyManagerPassword == null?(_keyStorePassword == null?null:_keyStorePassword.toString().toCharArray()):_keyManagerPassword.toString().toCharArray());
+ managers = keyManagerFactory.getKeyManagers();
+
+ if (_certAlias != null)
+ {
+ for (int idx = 0; idx < managers.length; idx++)
+ {
+ if (managers[idx] instanceof X509KeyManager)
+ {
+ managers[idx] = new AliasedX509ExtendedKeyManager(_certAlias,(X509KeyManager)managers[idx]);
+ }
+ }
+ }
+ }
+
+ return managers;
+ }
+
+ protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
+ {
+ TrustManager[] managers = null;
+ if (trustStore != null)
+ {
+ // Revocation checking is only supported for PKIX algorithm
+ if (_validatePeerCerts && _trustManagerFactoryAlgorithm.equalsIgnoreCase("PKIX"))
+ {
+ PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore,new X509CertSelector());
+
+ // Set maximum certification path length
+ pbParams.setMaxPathLength(_maxCertPathLength);
+
+ // Make sure revocation checking is enabled
+ pbParams.setRevocationEnabled(true);
+
+ if (crls != null && !crls.isEmpty())
+ {
+ pbParams.addCertStore(CertStore.getInstance("Collection",new CollectionCertStoreParameters(crls)));
+ }
+
+ if (_enableCRLDP)
+ {
+ // Enable Certificate Revocation List Distribution Points (CRLDP) support
+ System.setProperty("com.sun.security.enableCRLDP","true");
+ }
+
+ if (_enableOCSP)
+ {
+ // Enable On-Line Certificate Status Protocol (OCSP) support
+ Security.setProperty("ocsp.enable","true");
+
+ if (_ocspResponderURL != null)
+ {
+ // Override location of OCSP Responder
+ Security.setProperty("ocsp.responderURL", _ocspResponderURL);
+ }
+ }
+
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+ trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams));
+
+ managers = trustManagerFactory.getTrustManagers();
+ }
+ else
+ {
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+ trustManagerFactory.init(trustStore);
+
+ managers = trustManagerFactory.getTrustManagers();
+ }
+ }
+
+ return managers;
+ }
+
+ /**
+ * Check KeyStore Configuration. Ensures that if keystore has been
+ * configured but there's no truststore, that keystore is
+ * used as truststore.
+ * @throws IllegalStateException if SslContextFactory configuration can't be used.
+ */
+ public void checkKeyStore()
+ {
+ if (_context != null)
+ return;
+
+ if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null)
+ throw new IllegalStateException("SSL doesn't have a valid keystore");
+
+ // if the keystore has been configured but there is no
+ // truststore configured, use the keystore as the truststore
+ if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null)
+ {
+ _trustStore = _keyStore;
+ _trustStorePath = _keyStorePath;
+ _trustStoreInputStream = _keyStoreInputStream;
+ _trustStoreType = _keyStoreType;
+ _trustStoreProvider = _keyStoreProvider;
+ _trustStorePassword = _keyStorePassword;
+ _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm;
+ }
+
+ // It's the same stream we cannot read it twice, so read it once in memory
+ if (_keyStoreInputStream != null && _keyStoreInputStream == _trustStoreInputStream)
+ {
+ try
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ IO.copy(_keyStoreInputStream, baos);
+ _keyStoreInputStream.close();
+
+ _keyStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+ _trustStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+ }
+ catch (Exception ex)
+ {
+ throw new IllegalStateException(ex);
+ }
+ }
+ }
+
+ /**
+ * Select protocols to be used by the connector
+ * based on configured inclusion and exclusion lists
+ * as well as enabled and supported protocols.
+ * @param enabledProtocols Array of enabled protocols
+ * @param supportedProtocols Array of supported protocols
+ * @return Array of protocols to enable
+ */
+ public String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols)
+ {
+ Set<String> selected_protocols = new LinkedHashSet<>();
+
+ // Set the starting protocols - either from the included or enabled list
+ if (_includeProtocols!=null)
+ {
+ // Use only the supported included protocols
+ for (String protocol : _includeProtocols)
+ if(Arrays.asList(supportedProtocols).contains(protocol))
+ selected_protocols.add(protocol);
+ }
+ else
+ selected_protocols.addAll(Arrays.asList(enabledProtocols));
+
+
+ // Remove any excluded protocols
+ selected_protocols.removeAll(_excludeProtocols);
+
+ return selected_protocols.toArray(new String[selected_protocols.size()]);
+ }
+
+ /**
+ * Select cipher suites to be used by the connector
+ * based on configured inclusion and exclusion lists
+ * as well as enabled and supported cipher suite lists.
+ * @param enabledCipherSuites Array of enabled cipher suites
+ * @param supportedCipherSuites Array of supported cipher suites
+ * @return Array of cipher suites to enable
+ */
+ public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites)
+ {
+ Set<String> selected_ciphers = new CopyOnWriteArraySet<>();
+
+ // Set the starting ciphers - either from the included or enabled list
+ if (_includeCipherSuites!=null)
+ processIncludeCipherSuites(supportedCipherSuites, selected_ciphers);
+ else
+ selected_ciphers.addAll(Arrays.asList(enabledCipherSuites));
+
+ removeExcludedCipherSuites(selected_ciphers);
+
+ return selected_ciphers.toArray(new String[selected_ciphers.size()]);
+ }
+
+ private void processIncludeCipherSuites(String[] supportedCipherSuites, Set<String> selected_ciphers)
+ {
+ for (String cipherSuite : _includeCipherSuites)
+ {
+ Pattern p = Pattern.compile(cipherSuite);
+ for (String supportedCipherSuite : supportedCipherSuites)
+ {
+ Matcher m = p.matcher(supportedCipherSuite);
+ if (m.matches())
+ selected_ciphers.add(supportedCipherSuite);
+ }
+ }
+ }
+
+ private void removeExcludedCipherSuites(Set<String> selected_ciphers)
+ {
+ for (String excludeCipherSuite : _excludeCipherSuites)
+ {
+ Pattern excludeCipherPattern = Pattern.compile(excludeCipherSuite);
+ for (String selectedCipherSuite : selected_ciphers)
+ {
+ Matcher m = excludeCipherPattern.matcher(selectedCipherSuite);
+ if (m.matches())
+ selected_ciphers.remove(selectedCipherSuite);
+ }
+ }
+ }
+
+ /**
+ * Check if the lifecycle has been started and throw runtime exception
+ */
+ protected void checkNotStarted()
+ {
+ if (isStarted())
+ throw new IllegalStateException("Cannot modify configuration when "+getState());
+ }
+
+ /**
+ * @return true if CRL Distribution Points support is enabled
+ */
+ public boolean isEnableCRLDP()
+ {
+ return _enableCRLDP;
+ }
+
+ /** Enables CRL Distribution Points Support
+ * @param enableCRLDP true - turn on, false - turns off
+ */
+ public void setEnableCRLDP(boolean enableCRLDP)
+ {
+ checkNotStarted();
+ _enableCRLDP = enableCRLDP;
+ }
+
+ /**
+ * @return true if On-Line Certificate Status Protocol support is enabled
+ */
+ public boolean isEnableOCSP()
+ {
+ return _enableOCSP;
+ }
+
+ /** Enables On-Line Certificate Status Protocol support
+ * @param enableOCSP true - turn on, false - turn off
+ */
+ public void setEnableOCSP(boolean enableOCSP)
+ {
+ checkNotStarted();
+ _enableOCSP = enableOCSP;
+ }
+
+ /**
+ * @return Location of the OCSP Responder
+ */
+ public String getOcspResponderURL()
+ {
+ return _ocspResponderURL;
+ }
+
+ /** Set the location of the OCSP Responder.
+ * @param ocspResponderURL location of the OCSP Responder
+ */
+ public void setOcspResponderURL(String ocspResponderURL)
+ {
+ checkNotStarted();
+ _ocspResponderURL = ocspResponderURL;
+ }
+
+ /** Set the key store.
+ * @param keyStore the key store to set
+ */
+ public void setKeyStore(KeyStore keyStore)
+ {
+ checkNotStarted();
+ _keyStore = keyStore;
+ }
+
+ /** Set the trust store.
+ * @param trustStore the trust store to set
+ */
+ public void setTrustStore(KeyStore trustStore)
+ {
+ checkNotStarted();
+ _trustStore = trustStore;
+ }
+
+ /** Set the key store resource.
+ * @param resource the key store resource to set
+ */
+ public void setKeyStoreResource(Resource resource)
+ {
+ checkNotStarted();
+ try
+ {
+ _keyStoreInputStream = resource.getInputStream();
+ }
+ catch (IOException e)
+ {
+ throw new InvalidParameterException("Unable to get resource "+
+ "input stream for resource "+resource.toString());
+ }
+ }
+
+ /** Set the trust store resource.
+ * @param resource the trust store resource to set
+ */
+ public void setTrustStoreResource(Resource resource)
+ {
+ checkNotStarted();
+ try
+ {
+ _trustStoreInputStream = resource.getInputStream();
+ }
+ catch (IOException e)
+ {
+ throw new InvalidParameterException("Unable to get resource "+
+ "input stream for resource "+resource.toString());
+ }
+ }
+
+ /**
+ * @return true if SSL Session caching is enabled
+ */
+ public boolean isSessionCachingEnabled()
+ {
+ return _sessionCachingEnabled;
+ }
+
+ /** Set the flag to enable SSL Session caching.
+ * @param enableSessionCaching the value of the flag
+ */
+ public void setSessionCachingEnabled(boolean enableSessionCaching)
+ {
+ _sessionCachingEnabled = enableSessionCaching;
+ }
+
+ /** Get SSL session cache size.
+ * @return SSL session cache size
+ */
+ public int getSslSessionCacheSize()
+ {
+ return _sslSessionCacheSize;
+ }
+
+ /** SEt SSL session cache size.
+ * @param sslSessionCacheSize SSL session cache size to set
+ */
+ public void setSslSessionCacheSize(int sslSessionCacheSize)
+ {
+ _sslSessionCacheSize = sslSessionCacheSize;
+ }
+
+ /** Get SSL session timeout.
+ * @return SSL session timeout
+ */
+ public int getSslSessionTimeout()
+ {
+ return _sslSessionTimeout;
+ }
+
+ /** Set SSL session timeout.
+ * @param sslSessionTimeout SSL session timeout to set
+ */
+ public void setSslSessionTimeout(int sslSessionTimeout)
+ {
+ _sslSessionTimeout = sslSessionTimeout;
+ }
+
+
+ public SSLServerSocket newSslServerSocket(String host,int port,int backlog) throws IOException
+ {
+ SSLServerSocketFactory factory = _context.getServerSocketFactory();
+
+ SSLServerSocket socket =
+ (SSLServerSocket) (host==null ?
+ factory.createServerSocket(port,backlog):
+ factory.createServerSocket(port,backlog,InetAddress.getByName(host)));
+
+ if (getWantClientAuth())
+ socket.setWantClientAuth(getWantClientAuth());
+ if (getNeedClientAuth())
+ socket.setNeedClientAuth(getNeedClientAuth());
+
+ socket.setEnabledCipherSuites(selectCipherSuites(
+ socket.getEnabledCipherSuites(),
+ socket.getSupportedCipherSuites()));
+ socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols()));
+
+ return socket;
+ }
+
+ public SSLSocket newSslSocket() throws IOException
+ {
+ SSLSocketFactory factory = _context.getSocketFactory();
+
+ SSLSocket socket = (SSLSocket)factory.createSocket();
+
+ if (getWantClientAuth())
+ socket.setWantClientAuth(getWantClientAuth());
+ if (getNeedClientAuth())
+ socket.setNeedClientAuth(getNeedClientAuth());
+
+ socket.setEnabledCipherSuites(selectCipherSuites(
+ socket.getEnabledCipherSuites(),
+ socket.getSupportedCipherSuites()));
+ socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols()));
+
+ return socket;
+ }
+
+ /**
+ * Factory method for "scratch" {@link SSLEngine}s, usually only used for retrieving configuration
+ * information such as the application buffer size or the list of protocols/ciphers.
+ * <p />
+ * This method should not be used for creating {@link SSLEngine}s that are used in actual socket
+ * communication.
+ *
+ * @return a new, "scratch" {@link SSLEngine}
+ */
+ public SSLEngine newSSLEngine()
+ {
+ if (!isRunning())
+ throw new IllegalStateException("!STARTED");
+ SSLEngine sslEngine=_context.createSSLEngine();
+ customize(sslEngine);
+ return sslEngine;
+ }
+
+ /**
+ * General purpose factory method for creating {@link SSLEngine}s, although creation of
+ * {@link SSLEngine}s on the server-side should prefer {@link #newSSLEngine(InetSocketAddress)}.
+ *
+ * @param host the remote host
+ * @param port the remote port
+ * @return a new {@link SSLEngine}
+ */
+ public SSLEngine newSSLEngine(String host, int port)
+ {
+ if (!isRunning())
+ throw new IllegalStateException("!STARTED");
+ SSLEngine sslEngine=isSessionCachingEnabled()
+ ? _context.createSSLEngine(host, port)
+ : _context.createSSLEngine();
+ customize(sslEngine);
+ return sslEngine;
+ }
+
+ /**
+ * Server-side only factory method for creating {@link SSLEngine}s.
+ * <p />
+ * If the given {@code address} is null, it is equivalent to {@link #newSSLEngine()}, otherwise
+ * {@link #newSSLEngine(String, int)} is called.
+ * <p />
+ * If {@link #getNeedClientAuth()} is {@code true}, then the host name is passed to
+ * {@link #newSSLEngine(String, int)}, possibly incurring in a reverse DNS lookup, which takes time
+ * and may hang the selector (since this method is usually called by the selector thread).
+ * <p />
+ * Otherwise, the host address is passed to {@link #newSSLEngine(String, int)} without DNS lookup
+ * penalties.
+ * <p />
+ * Clients that wish to create {@link SSLEngine} instances must use {@link #newSSLEngine(String, int)}.
+ *
+ * @param address the remote peer address
+ * @return a new {@link SSLEngine}
+ */
+ public SSLEngine newSSLEngine(InetSocketAddress address)
+ {
+ if (address == null)
+ return newSSLEngine();
+
+ boolean useHostName = getNeedClientAuth();
+ String hostName = useHostName ? address.getHostName() : address.getAddress().getHostAddress();
+ return newSSLEngine(hostName, address.getPort());
+ }
+
+ public void customize(SSLEngine sslEngine)
+ {
+ SSLParameters sslParams = sslEngine.getSSLParameters();
+ sslParams.setEndpointIdentificationAlgorithm(_endpointIdentificationAlgorithm);
+ sslEngine.setSSLParameters(sslParams);
+
+ if (getWantClientAuth())
+ sslEngine.setWantClientAuth(getWantClientAuth());
+ if (getNeedClientAuth())
+ sslEngine.setNeedClientAuth(getNeedClientAuth());
+
+ sslEngine.setEnabledCipherSuites(selectCipherSuites(
+ sslEngine.getEnabledCipherSuites(),
+ sslEngine.getSupportedCipherSuites()));
+
+ sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols()));
+ }
+
+ public static X509Certificate[] getCertChain(SSLSession sslSession)
+ {
+ try
+ {
+ Certificate[] javaxCerts=sslSession.getPeerCertificates();
+ if (javaxCerts==null||javaxCerts.length==0)
+ return null;
+
+ int length=javaxCerts.length;
+ X509Certificate[] javaCerts=new X509Certificate[length];
+
+ java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509");
+ for (int i=0; i<length; i++)
+ {
+ byte bytes[]=javaxCerts[i].getEncoded();
+ ByteArrayInputStream stream=new ByteArrayInputStream(bytes);
+ javaCerts[i]=(X509Certificate)cf.generateCertificate(stream);
+ }
+
+ return javaCerts;
+ }
+ catch (SSLPeerUnverifiedException pue)
+ {
+ return null;
+ }
+ catch (Exception e)
+ {
+ LOG.warn(Log.EXCEPTION,e);
+ return null;
+ }
+ }
+
+ /**
+ * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream
+ * cipher key strength. i.e. How much entropy material is in the key material being fed into the
+ * encryption routines.
+ *
+ * <p>
+ * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
+ * Version 1.0, Appendix C. CipherSuite definitions:
+ *
+ * <pre>
+ * Effective
+ * Cipher Type Key Bits
+ *
+ * NULL * Stream 0
+ * IDEA_CBC Block 128
+ * RC2_CBC_40 * Block 40
+ * RC4_40 * Stream 40
+ * RC4_128 Stream 128
+ * DES40_CBC * Block 40
+ * DES_CBC Block 56
+ * 3DES_EDE_CBC Block 168
+ * </pre>
+ *
+ * @param cipherSuite String name of the TLS cipher suite.
+ * @return int indicating the effective key entropy bit-length.
+ */
+ public static int deduceKeyLength(String cipherSuite)
+ {
+ // Roughly ordered from most common to least common.
+ if (cipherSuite == null)
+ return 0;
+ else if (cipherSuite.contains("WITH_AES_256_"))
+ return 256;
+ else if (cipherSuite.contains("WITH_RC4_128_"))
+ return 128;
+ else if (cipherSuite.contains("WITH_AES_128_"))
+ return 128;
+ else if (cipherSuite.contains("WITH_RC4_40_"))
+ return 40;
+ else if (cipherSuite.contains("WITH_3DES_EDE_CBC_"))
+ return 168;
+ else if (cipherSuite.contains("WITH_IDEA_CBC_"))
+ return 128;
+ else if (cipherSuite.contains("WITH_RC2_CBC_40_"))
+ return 40;
+ else if (cipherSuite.contains("WITH_DES40_CBC_"))
+ return 40;
+ else if (cipherSuite.contains("WITH_DES_CBC_"))
+ return 56;
+ else
+ return 0;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x(%s,%s)",
+ getClass().getSimpleName(),
+ hashCode(),
+ _keyStorePath,
+ _trustStorePath);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Common SSL Utility Classes
+ */
+package org.eclipse.jetty.util.ssl;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.statistic;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.Atomics;
+
+
+/* ------------------------------------------------------------ */
+/** Statistics on a counter value.
+ * <p>
+ * Keep total, current and maximum values of a counter that
+ * can be incremented and decremented. The total refers only
+ * to increments.
+ *
+ */
+public class CounterStatistic
+{
+ protected final AtomicLong _max = new AtomicLong();
+ protected final AtomicLong _curr = new AtomicLong();
+ protected final AtomicLong _total = new AtomicLong();
+
+ /* ------------------------------------------------------------ */
+ public void reset()
+ {
+ reset(0);
+ }
+
+ /* ------------------------------------------------------------ */
+ public void reset(final long value)
+ {
+ _max.set(value);
+ _curr.set(value);
+ _total.set(0); // total always set to 0 to properly calculate cumulative total
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param delta the amount to add to the count
+ */
+ public long add(final long delta)
+ {
+ long value=_curr.addAndGet(delta);
+ if (delta > 0)
+ {
+ _total.addAndGet(delta);
+ Atomics.updateMax(_max,value);
+ }
+ return value;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public long increment()
+ {
+ return add(1);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ */
+ public long decrement()
+ {
+ return add(-1);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return max value
+ */
+ public long getMax()
+ {
+ return _max.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return current value
+ */
+ public long getCurrent()
+ {
+ return _curr.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return total value
+ */
+ public long getTotal()
+ {
+ return _total.get();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{c=%d,m=%d,t=%d}",this.getClass().getSimpleName(),hashCode(),_curr.get(),_max.get(),_total.get());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.statistic;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.Atomics;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * SampledStatistics
+ * <p>
+ * Provides max, total, mean, count, variance, and standard
+ * deviation of continuous sequence of samples.
+ * <p>
+ * Calculates estimates of mean, variance, and standard deviation
+ * characteristics of a sample using a non synchronized
+ * approximation of the on-line algorithm presented
+ * in Donald Knuth's Art of Computer Programming, Volume 2,
+ * Seminumerical Algorithms, 3rd edition, page 232,
+ * Boston: Addison-Wesley. that cites a 1962 paper by B.P. Welford
+ * that can be found by following the link http://www.jstor.org/pss/1266577
+ * <p>
+ * This algorithm is also described in Wikipedia at
+ * http://en.wikipedia.org/w/index.php?title=Algorithms_for_calculating_variance§ion=4#On-line_algorithm
+ */
+public class SampleStatistic
+{
+ protected final AtomicLong _max = new AtomicLong();
+ protected final AtomicLong _total = new AtomicLong();
+ protected final AtomicLong _count = new AtomicLong();
+ protected final AtomicLong _totalVariance100 = new AtomicLong();
+
+ public void reset()
+ {
+ _max.set(0);
+ _total.set(0);
+ _count.set(0);
+ _totalVariance100.set(0);
+ }
+
+ public void set(final long sample)
+ {
+ long total = _total.addAndGet(sample);
+ long count = _count.incrementAndGet();
+
+ if (count>1)
+ {
+ long mean10 = total*10/count;
+ long delta10 = sample*10 - mean10;
+ _totalVariance100.addAndGet(delta10*delta10);
+ }
+
+ Atomics.updateMax(_max, sample);
+ }
+
+ /**
+ * @return the max value
+ */
+ public long getMax()
+ {
+ return _max.get();
+ }
+
+ public long getTotal()
+ {
+ return _total.get();
+ }
+
+ public long getCount()
+ {
+ return _count.get();
+ }
+
+ public double getMean()
+ {
+ return (double)_total.get()/_count.get();
+ }
+
+ public double getVariance()
+ {
+ final long variance100 = _totalVariance100.get();
+ final long count = _count.get();
+
+ return count>1?((double)variance100)/100.0/(count-1):0.0;
+ }
+
+ public double getStdDev()
+ {
+ return Math.sqrt(getVariance());
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{c=%d,m=%d,t=%d,v100=%d}",this.getClass().getSimpleName(),hashCode(),_count.get(),_max.get(),_total.get(),_totalVariance100.get());
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Common Statistics Utility classes
+ */
+package org.eclipse.jetty.util.statistic;
+
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.thread;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Jetty ThreadPool using java 5 ThreadPoolExecutor
+ * This class wraps a {@link ExecutorService} as a {@link ThreadPool} and
+ * {@link LifeCycle} interfaces so that it may be used by the Jetty <code>org.eclipse.jetty.server.Server</code>
+ */
+public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, LifeCycle
+{
+ private static final Logger LOG = Log.getLogger(ExecutorThreadPool.class);
+ private final ExecutorService _executor;
+
+ /* ------------------------------------------------------------ */
+ public ExecutorThreadPool(ExecutorService executor)
+ {
+ _executor = executor;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Wraps an {@link ThreadPoolExecutor}.
+ * Max pool size is 256, pool thread timeout after 60 seconds and
+ * an unbounded {@link LinkedBlockingQueue} is used for the job queue;
+ */
+ public ExecutorThreadPool()
+ {
+ // Using an unbounded queue makes the maxThreads parameter useless
+ // Refer to ThreadPoolExecutor javadocs for details
+ this(new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Wraps an {@link ThreadPoolExecutor}.
+ * Max pool size is 256, pool thread timeout after 60 seconds, and core pool size is 32 when queueSize >= 0.
+ * @param queueSize can be -1 for using an unbounded {@link LinkedBlockingQueue}, 0 for using a
+ * {@link SynchronousQueue}, greater than 0 for using a {@link ArrayBlockingQueue} of the given size.
+ */
+ public ExecutorThreadPool(int queueSize)
+ {
+ this(queueSize < 0 ? new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) :
+ queueSize == 0 ? new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) :
+ new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize)));
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Wraps an {@link ThreadPoolExecutor} using
+ * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue;
+ * @param corePoolSize must be equal to maximumPoolSize
+ * @param maximumPoolSize the maximum number of threads to allow in the pool
+ * @param keepAliveTime the max time a thread can remain idle, in milliseconds
+ */
+ public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime)
+ {
+ this(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS);
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Wraps an {@link ThreadPoolExecutor} using
+ * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue.
+ * @param corePoolSize must be equal to maximumPoolSize
+ * @param maximumPoolSize the maximum number of threads to allow in the pool
+ * @param keepAliveTime the max time a thread can remain idle
+ * @param unit the unit for the keepAliveTime
+ */
+ public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit)
+ {
+ this(corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue<Runnable>());
+ }
+
+ /* ------------------------------------------------------------ */
+
+ /**
+ * Wraps an {@link ThreadPoolExecutor}
+ * @param corePoolSize the number of threads to keep in the pool, even if they are idle
+ * @param maximumPoolSize the maximum number of threads to allow in the pool
+ * @param keepAliveTime the max time a thread can remain idle
+ * @param unit the unit for the keepAliveTime
+ * @param workQueue the queue to use for holding tasks before they are executed
+ */
+ public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
+ {
+ this(new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue));
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void execute(Runnable job)
+ {
+ _executor.execute(job);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean dispatch(Runnable job)
+ {
+ try
+ {
+ _executor.execute(job);
+ return true;
+ }
+ catch(RejectedExecutionException e)
+ {
+ LOG.warn(e);
+ return false;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getIdleThreads()
+ {
+ if (_executor instanceof ThreadPoolExecutor)
+ {
+ final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+ return tpe.getPoolSize() - tpe.getActiveCount();
+ }
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ public int getThreads()
+ {
+ if (_executor instanceof ThreadPoolExecutor)
+ {
+ final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+ return tpe.getPoolSize();
+ }
+ return -1;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isLowOnThreads()
+ {
+ if (_executor instanceof ThreadPoolExecutor)
+ {
+ final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+ // getActiveCount() locks the thread pool, so execute it last
+ return tpe.getPoolSize() == tpe.getMaximumPoolSize() &&
+ tpe.getQueue().size() >= tpe.getPoolSize() - tpe.getActiveCount();
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void join() throws InterruptedException
+ {
+ _executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ _executor.shutdownNow();
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.thread;
+
+/**
+ * Marker that wraps a Runnable, indicating that it is running in a thread that must not be blocked.
+ * <p />
+ * Client code can use the thread-local {@link #isNonBlockingThread()} to detect whether they are
+ * in the context of a non-blocking thread, and perform different actions if that's the case.
+ */
+public class NonBlockingThread implements Runnable
+{
+ private final static ThreadLocal<Boolean> __nonBlockingThread = new ThreadLocal<>();
+
+ /**
+ * @return whether the current thread is a thread that must not block.
+ */
+ public static boolean isNonBlockingThread()
+ {
+ return Boolean.TRUE.equals(__nonBlockingThread.get());
+ }
+
+ private final Runnable delegate;
+
+ public NonBlockingThread(Runnable delegate)
+ {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ __nonBlockingThread.set(Boolean.TRUE);
+ delegate.run();
+ }
+ finally
+ {
+ __nonBlockingThread.remove();
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.thread;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
+
+@ManagedObject("A thread pool with no max bound by default")
+public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Dumpable
+{
+ private static final Logger LOG = Log.getLogger(QueuedThreadPool.class);
+
+ private final AtomicInteger _threadsStarted = new AtomicInteger();
+ private final AtomicInteger _threadsIdle = new AtomicInteger();
+ private final AtomicLong _lastShrink = new AtomicLong();
+ private final ConcurrentLinkedQueue<Thread> _threads = new ConcurrentLinkedQueue<>();
+ private final Object _joinLock = new Object();
+ private final BlockingQueue<Runnable> _jobs;
+ private String _name = "qtp" + hashCode();
+ private int _idleTimeout;
+ private int _maxThreads;
+ private int _minThreads;
+ private int _priority = Thread.NORM_PRIORITY;
+ private boolean _daemon = false;
+ private boolean _detailedDump = false;
+
+ public QueuedThreadPool()
+ {
+ this(200);
+ }
+
+ public QueuedThreadPool(@Name("maxThreads") int maxThreads)
+ {
+ this(maxThreads, 8);
+ }
+
+ public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads)
+ {
+ this(maxThreads, minThreads, 60000);
+ }
+
+ public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout")int idleTimeout)
+ {
+ this(maxThreads, minThreads, idleTimeout, null);
+ }
+
+ public QueuedThreadPool(@Name("maxThreads") int maxThreads, @Name("minThreads") int minThreads, @Name("idleTimeout") int idleTimeout, @Name("queue") BlockingQueue<Runnable> queue)
+ {
+ setMinThreads(minThreads);
+ setMaxThreads(maxThreads);
+ setIdleTimeout(idleTimeout);
+ setStopTimeout(5000);
+
+ if (queue==null)
+ queue=new BlockingArrayQueue<>(_minThreads, _minThreads);
+ _jobs=queue;
+
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ super.doStart();
+ _threadsStarted.set(0);
+
+ startThreads(_minThreads);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+
+ long timeout = getStopTimeout();
+ BlockingQueue<Runnable> jobs = getQueue();
+
+ // If no stop timeout, clear job queue
+ if (timeout <= 0)
+ jobs.clear();
+
+ // Fill job Q with noop jobs to wakeup idle
+ Runnable noop = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ }
+ };
+ for (int i = _threadsStarted.get(); i-- > 0; )
+ jobs.offer(noop);
+
+ // try to jobs complete naturally for half our stop time
+ long stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
+ for (Thread thread : _threads)
+ {
+ long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
+ if (canwait > 0)
+ thread.join(canwait);
+ }
+
+ // If we still have threads running, get a bit more aggressive
+
+ // interrupt remaining threads
+ if (_threadsStarted.get() > 0)
+ for (Thread thread : _threads)
+ thread.interrupt();
+
+ // wait again for the other half of our stop time
+ stopby = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout) / 2;
+ for (Thread thread : _threads)
+ {
+ long canwait = TimeUnit.NANOSECONDS.toMillis(stopby - System.nanoTime());
+ if (canwait > 0)
+ thread.join(canwait);
+ }
+
+ Thread.yield();
+ int size = _threads.size();
+ if (size > 0)
+ {
+ Thread.yield();
+
+ if (LOG.isDebugEnabled())
+ {
+ for (Thread unstopped : _threads)
+ {
+ StringBuilder dmp = new StringBuilder();
+ for (StackTraceElement element : unstopped.getStackTrace())
+ {
+ dmp.append(StringUtil.__LINE_SEPARATOR).append("\tat ").append(element);
+ }
+ LOG.warn("Couldn't stop {}{}", unstopped, dmp.toString());
+ }
+ }
+ else
+ {
+ for (Thread unstopped : _threads)
+ LOG.warn("{} Couldn't stop {}",this,unstopped);
+ }
+ }
+
+ synchronized (_joinLock)
+ {
+ _joinLock.notifyAll();
+ }
+ }
+
+ /**
+ * Delegated to the named or anonymous Pool.
+ */
+ public void setDaemon(boolean daemon)
+ {
+ _daemon = daemon;
+ }
+
+ /**
+ * Set the maximum thread idle time.
+ * Threads that are idle for longer than this period may be
+ * stopped.
+ * Delegated to the named or anonymous Pool.
+ *
+ * @param idleTimeout Max idle time in ms.
+ * @see #getIdleTimeout
+ */
+ public void setIdleTimeout(int idleTimeout)
+ {
+ _idleTimeout = idleTimeout;
+ }
+
+ /**
+ * Set the maximum number of threads.
+ * Delegated to the named or anonymous Pool.
+ *
+ * @param maxThreads maximum number of threads.
+ * @see #getMaxThreads
+ */
+ @Override
+ public void setMaxThreads(int maxThreads)
+ {
+ _maxThreads = maxThreads;
+ if (_minThreads > _maxThreads)
+ _minThreads = _maxThreads;
+ }
+
+ /**
+ * Set the minimum number of threads.
+ * Delegated to the named or anonymous Pool.
+ *
+ * @param minThreads minimum number of threads
+ * @see #getMinThreads
+ */
+ @Override
+ public void setMinThreads(int minThreads)
+ {
+ _minThreads = minThreads;
+
+ if (_minThreads > _maxThreads)
+ _maxThreads = _minThreads;
+
+ int threads = _threadsStarted.get();
+ if (isStarted() && threads < _minThreads)
+ startThreads(_minThreads - threads);
+ }
+
+ /**
+ * @param name Name of this thread pool to use when naming threads.
+ */
+ public void setName(String name)
+ {
+ if (isRunning())
+ throw new IllegalStateException("started");
+ _name = name;
+ }
+
+ /**
+ * Set the priority of the pool threads.
+ *
+ * @param priority the new thread priority.
+ */
+ public void setThreadsPriority(int priority)
+ {
+ _priority = priority;
+ }
+
+ /**
+ * Get the maximum thread idle time.
+ * Delegated to the named or anonymous Pool.
+ *
+ * @return Max idle time in ms.
+ * @see #setIdleTimeout
+ */
+ @ManagedAttribute("maximum time a thread may be idle in ms")
+ public int getIdleTimeout()
+ {
+ return _idleTimeout;
+ }
+
+ /**
+ * Set the maximum number of threads.
+ * Delegated to the named or anonymous Pool.
+ *
+ * @return maximum number of threads.
+ * @see #setMaxThreads
+ */
+ @Override
+ @ManagedAttribute("maximum number of threads in the pool")
+ public int getMaxThreads()
+ {
+ return _maxThreads;
+ }
+
+ /**
+ * Get the minimum number of threads.
+ * Delegated to the named or anonymous Pool.
+ *
+ * @return minimum number of threads.
+ * @see #setMinThreads
+ */
+ @Override
+ @ManagedAttribute("minimum number of threads in the pool")
+ public int getMinThreads()
+ {
+ return _minThreads;
+ }
+
+ /**
+ * @return The name of the this thread pool
+ */
+ @ManagedAttribute("name of the thread pool")
+ public String getName()
+ {
+ return _name;
+ }
+
+ /**
+ * Get the priority of the pool threads.
+ *
+ * @return the priority of the pool threads.
+ */
+ @ManagedAttribute("priority of threads in the pool")
+ public int getThreadsPriority()
+ {
+ return _priority;
+ }
+
+ /**
+ * Get the size of the job queue.
+ *
+ * @return Number of jobs queued waiting for a thread
+ */
+ @ManagedAttribute("Size of the job queue")
+ public int getQueueSize()
+ {
+ return _jobs.size();
+ }
+
+ /**
+ * Delegated to the named or anonymous Pool.
+ */
+ @ManagedAttribute("thead pool using a daemon thread")
+ public boolean isDaemon()
+ {
+ return _daemon;
+ }
+
+ public boolean isDetailedDump()
+ {
+ return _detailedDump;
+ }
+
+ public void setDetailedDump(boolean detailedDump)
+ {
+ _detailedDump = detailedDump;
+ }
+
+ @Override
+ public void execute(Runnable job)
+ {
+ if (!isRunning() || !_jobs.offer(job))
+ {
+ LOG.warn("{} rejected {}", this, job);
+ throw new RejectedExecutionException(job.toString());
+ }
+ }
+
+ /**
+ * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
+ */
+ @Override
+ public void join() throws InterruptedException
+ {
+ synchronized (_joinLock)
+ {
+ while (isRunning())
+ _joinLock.wait();
+ }
+
+ while (isStopping())
+ Thread.sleep(1);
+ }
+
+ /**
+ * @return The total number of threads currently in the pool
+ */
+ @Override
+ @ManagedAttribute("total number of threads currently in the pool")
+ public int getThreads()
+ {
+ return _threadsStarted.get();
+ }
+
+ /**
+ * @return The number of idle threads in the pool
+ */
+ @Override
+ @ManagedAttribute("total number of idle threads in the pool")
+ public int getIdleThreads()
+ {
+ return _threadsIdle.get();
+ }
+
+ /**
+ * @return True if the pool is at maxThreads and there are not more idle threads than queued jobs
+ */
+ @Override
+ @ManagedAttribute("True if the pools is at maxThreads and there are not idle threads than queued jobs")
+ public boolean isLowOnThreads()
+ {
+ return _threadsStarted.get() == _maxThreads && _jobs.size() >= _threadsIdle.get();
+ }
+
+ private boolean startThreads(int threadsToStart)
+ {
+ while (threadsToStart > 0)
+ {
+ int threads = _threadsStarted.get();
+ if (threads >= _maxThreads)
+ return false;
+
+ if (!_threadsStarted.compareAndSet(threads, threads + 1))
+ continue;
+
+ boolean started = false;
+ try
+ {
+ Thread thread = newThread(_runnable);
+ thread.setDaemon(isDaemon());
+ thread.setPriority(getThreadsPriority());
+ thread.setName(_name + "-" + thread.getId());
+ _threads.add(thread);
+
+ thread.start();
+ started = true;
+ }
+ finally
+ {
+ if (!started)
+ _threadsStarted.decrementAndGet();
+ }
+ if (started)
+ threadsToStart--;
+ }
+ return true;
+ }
+
+ protected Thread newThread(Runnable runnable)
+ {
+ return new Thread(runnable);
+ }
+
+
+ @Override
+ @ManagedOperation("dump thread state")
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ List<Object> dump = new ArrayList<>(getMaxThreads());
+ for (final Thread thread : _threads)
+ {
+ final StackTraceElement[] trace = thread.getStackTrace();
+ boolean inIdleJobPoll = false;
+ for (StackTraceElement t : trace)
+ {
+ if ("idleJobPoll".equals(t.getMethodName()))
+ {
+ inIdleJobPoll = true;
+ break;
+ }
+ }
+ final boolean idle = inIdleJobPoll;
+
+ if (isDetailedDump())
+ {
+ dump.add(new Dumpable()
+ {
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle ? " IDLE" : "").append('\n');
+ if (!idle)
+ ContainerLifeCycle.dump(out, indent, Arrays.asList(trace));
+ }
+
+ @Override
+ public String dump()
+ {
+ return null;
+ }
+ });
+ }
+ else
+ {
+ dump.add(thread.getId() + " " + thread.getName() + " " + thread.getState() + " @ " + (trace.length > 0 ? trace[0] : "???") + (idle ? " IDLE" : ""));
+ }
+ }
+
+ ContainerLifeCycle.dumpObject(out, this);
+ ContainerLifeCycle.dump(out, indent, dump);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s{%s,%d<=%d<=%d,i=%d,q=%d}", _name, getState(), getMinThreads(), getThreads(), getMaxThreads(), getIdleThreads(), (_jobs == null ? -1 : _jobs.size()));
+ }
+
+ private Runnable idleJobPoll() throws InterruptedException
+ {
+ return _jobs.poll(_idleTimeout, TimeUnit.MILLISECONDS);
+ }
+
+ private Runnable _runnable = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ boolean shrink = false;
+ try
+ {
+ Runnable job = _jobs.poll();
+
+ if (job != null && _threadsIdle.get() == 0)
+ {
+ startThreads(1);
+ }
+
+ loop: while (isRunning())
+ {
+ // Job loop
+ while (job != null && isRunning())
+ {
+ runJob(job);
+ if (Thread.interrupted())
+ break loop;
+ job = _jobs.poll();
+ }
+
+ // Idle loop
+ try
+ {
+ _threadsIdle.incrementAndGet();
+
+ while (isRunning() && job == null)
+ {
+ if (_idleTimeout <= 0)
+ job = _jobs.take();
+ else
+ {
+ // maybe we should shrink?
+ final int size = _threadsStarted.get();
+ if (size > _minThreads)
+ {
+ long last = _lastShrink.get();
+ long now = System.nanoTime();
+ if (last == 0 || (now - last) > TimeUnit.MILLISECONDS.toNanos(_idleTimeout))
+ {
+ shrink = _lastShrink.compareAndSet(last, now) &&
+ _threadsStarted.compareAndSet(size, size - 1);
+ if (shrink)
+ {
+ return;
+ }
+ }
+ }
+ job = idleJobPoll();
+ }
+ }
+ }
+ finally
+ {
+ if (_threadsIdle.decrementAndGet() == 0)
+ {
+ startThreads(1);
+ }
+ }
+ }
+ }
+ catch (InterruptedException e)
+ {
+ LOG.ignore(e);
+ }
+ catch (Throwable e)
+ {
+ LOG.warn(e);
+ }
+ finally
+ {
+ if (!shrink)
+ _threadsStarted.decrementAndGet();
+ _threads.remove(Thread.currentThread());
+ }
+ }
+ };
+
+ /**
+ * <p>Runs the given job in the {@link Thread#currentThread() current thread}.</p>
+ * <p>Subclasses may override to perform pre/post actions before/after the job is run.</p>
+ *
+ * @param job the job to run
+ */
+ protected void runJob(Runnable job)
+ {
+ job.run();
+ }
+
+ /**
+ * @return the job queue
+ */
+ protected BlockingQueue<Runnable> getQueue()
+ {
+ return _jobs;
+ }
+
+ /**
+ * @param queue the job queue
+ */
+ public void setQueue(BlockingQueue<Runnable> queue)
+ {
+ throw new UnsupportedOperationException("Use constructor injection");
+ }
+
+ /**
+ * @param id The thread ID to interrupt.
+ * @return true if the thread was found and interrupted.
+ */
+ @ManagedOperation("interrupt a pool thread")
+ public boolean interruptThread(@Name("id") long id)
+ {
+ for (Thread thread : _threads)
+ {
+ if (thread.getId() == id)
+ {
+ thread.interrupt();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param id The thread ID to interrupt.
+ * @return true if the thread was found and interrupted.
+ */
+ @ManagedOperation("dump a pool thread stack")
+ public String dumpThread(@Name("id") long id)
+ {
+ for (Thread thread : _threads)
+ {
+ if (thread.getId() == id)
+ {
+ StringBuilder buf = new StringBuilder();
+ buf.append(thread.getId()).append(" ").append(thread.getName()).append(" ").append(thread.getState()).append(":\n");
+ for (StackTraceElement element : thread.getStackTrace())
+ buf.append(" at ").append(element.toString()).append('\n');
+ return buf.toString();
+ }
+ }
+ return null;
+ }
+
+
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.thread;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+
+/**
+ * Implementation of {@link Scheduler} based on JDK's {@link ScheduledThreadPoolExecutor}.
+ * <p />
+ * While use of {@link ScheduledThreadPoolExecutor} creates futures that will not be used,
+ * it has the advantage of allowing to set a property to remove cancelled tasks from its
+ * queue even if the task did not fire, which provides a huge benefit in the performance
+ * of garbage collection in young generation.
+ */
+public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Scheduler
+{
+ private final String name;
+ private final boolean daemon;
+ private volatile ScheduledThreadPoolExecutor scheduler;
+
+ public ScheduledExecutorScheduler()
+ {
+ this(null, false);
+ }
+
+ public ScheduledExecutorScheduler(String name, boolean daemon)
+ {
+ this.name = name == null ? "Scheduler-" + hashCode() : name;
+ this.daemon = daemon;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactory()
+ {
+ @Override
+ public Thread newThread(Runnable r)
+ {
+ Thread thread = new Thread(r, name);
+ thread.setDaemon(daemon);
+ return thread;
+ }
+ });
+ scheduler.setRemoveOnCancelPolicy(true);
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ scheduler.shutdownNow();
+ super.doStop();
+ scheduler = null;
+ }
+
+ @Override
+ public Task schedule(Runnable task, long delay, TimeUnit unit)
+ {
+ ScheduledFuture<?> result = scheduler.schedule(task, delay, unit);
+ return new ScheduledFutureTask(result);
+ }
+
+ private class ScheduledFutureTask implements Task
+ {
+ private final ScheduledFuture<?> scheduledFuture;
+
+ public ScheduledFutureTask(ScheduledFuture<?> scheduledFuture)
+ {
+ this.scheduledFuture = scheduledFuture;
+ }
+
+ @Override
+ public boolean cancel()
+ {
+ return scheduledFuture.cancel(false);
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.thread;
+
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+public interface Scheduler extends LifeCycle
+{
+ interface Task
+ {
+ boolean cancel();
+ }
+
+ Task schedule(Runnable task, long delay, TimeUnit units);
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.thread;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * ShutdownThread is a shutdown hook thread implemented as
+ * singleton that maintains a list of lifecycle instances
+ * that are registered with it and provides ability to stop
+ * these lifecycles upon shutdown of the Java Virtual Machine
+ */
+public class ShutdownThread extends Thread
+{
+ private static final Logger LOG = Log.getLogger(ShutdownThread.class);
+ private static final ShutdownThread _thread = new ShutdownThread();
+
+ private boolean _hooked;
+ private final List<LifeCycle> _lifeCycles = new CopyOnWriteArrayList<LifeCycle>();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Default constructor for the singleton
+ *
+ * Registers the instance as shutdown hook with the Java Runtime
+ */
+ private ShutdownThread()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ private synchronized void hook()
+ {
+ try
+ {
+ if (!_hooked)
+ Runtime.getRuntime().addShutdownHook(this);
+ _hooked=true;
+ }
+ catch(Exception e)
+ {
+ LOG.ignore(e);
+ LOG.info("shutdown already commenced");
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ private synchronized void unhook()
+ {
+ try
+ {
+ _hooked=false;
+ Runtime.getRuntime().removeShutdownHook(this);
+ }
+ catch(Exception e)
+ {
+ LOG.ignore(e);
+ LOG.debug("shutdown already commenced");
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Returns the instance of the singleton
+ *
+ * @return the singleton instance of the {@link ShutdownThread}
+ */
+ public static ShutdownThread getInstance()
+ {
+ return _thread;
+ }
+
+ /* ------------------------------------------------------------ */
+ public static synchronized void register(LifeCycle... lifeCycles)
+ {
+ _thread._lifeCycles.addAll(Arrays.asList(lifeCycles));
+ if (_thread._lifeCycles.size()>0)
+ _thread.hook();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static synchronized void register(int index, LifeCycle... lifeCycles)
+ {
+ _thread._lifeCycles.addAll(index,Arrays.asList(lifeCycles));
+ if (_thread._lifeCycles.size()>0)
+ _thread.hook();
+ }
+
+ /* ------------------------------------------------------------ */
+ public static synchronized void deregister(LifeCycle lifeCycle)
+ {
+ _thread._lifeCycles.remove(lifeCycle);
+ if (_thread._lifeCycles.size()==0)
+ _thread.unhook();
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void run()
+ {
+ for (LifeCycle lifeCycle : _thread._lifeCycles)
+ {
+ try
+ {
+ if (lifeCycle.isStarted())
+ {
+ lifeCycle.stop();
+ LOG.debug("Stopped {}",lifeCycle);
+ }
+
+ if (lifeCycle instanceof Destroyable)
+ {
+ ((Destroyable)lifeCycle).destroy();
+ LOG.debug("Destroyed {}",lifeCycle);
+ }
+ }
+ catch (Exception ex)
+ {
+ LOG.debug(ex);
+ }
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.thread;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** ThreadPool.
+ *
+ * A specialization of Executor interface that provides reporting methods (eg {@link #getThreads()})
+ * and the option of configuration methods (e.g. @link {@link SizedThreadPool#setMaxThreads(int)}).
+ *
+ */
+@ManagedObject("Pool of Threads")
+public interface ThreadPool extends Executor
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
+ */
+ public void join() throws InterruptedException;
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The total number of threads currently in the pool
+ */
+ @ManagedAttribute("number of threads in pool")
+ public int getThreads();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The number of idle threads in the pool
+ */
+ @ManagedAttribute("number of idle threads in pool")
+ public int getIdleThreads();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return True if the pool is low on threads
+ */
+ @ManagedAttribute("indicates the pool is low on available threads")
+ public boolean isLowOnThreads();
+
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public interface SizedThreadPool extends ThreadPool
+ {
+ public int getMinThreads();
+ public int getMaxThreads();
+ public void setMinThreads(int threads);
+ public void setMaxThreads(int threads);
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.util.thread;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A scheduler based on the the JVM Timer class
+ */
+public class TimerScheduler extends AbstractLifeCycle implements Scheduler, Runnable
+{
+ private static final Logger LOG = Log.getLogger(TimerScheduler.class);
+
+ /*
+ * This class uses the Timer class rather than an ScheduledExecutionService because
+ * it uses the same algorithm internally and the signature is cheaper to use as there are no
+ * Futures involved (which we do not need).
+ * However, Timer is still locking and a concurrent queue would be better.
+ */
+
+ private final String _name;
+ private final boolean _daemon;
+ private Timer _timer;
+
+ public TimerScheduler()
+ {
+ this(null, false);
+ }
+
+ public TimerScheduler(String name, boolean daemon)
+ {
+ _name = name;
+ _daemon = daemon;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ _timer = _name == null ? new Timer() : new Timer(_name, _daemon);
+ run();
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ _timer.cancel();
+ super.doStop();
+ _timer = null;
+ }
+
+ @Override
+ public Task schedule(final Runnable task, final long delay, final TimeUnit units)
+ {
+ Timer timer = _timer;
+ if (timer == null)
+ throw new RejectedExecutionException("STOPPED: " + this);
+ SimpleTask t = new SimpleTask(task);
+ timer.schedule(t, units.toMillis(delay));
+ return t;
+ }
+
+ @Override
+ public void run()
+ {
+ Timer timer = _timer;
+ if (timer != null)
+ {
+ timer.purge();
+ schedule(this, 1, TimeUnit.SECONDS);
+ }
+ }
+
+ private static class SimpleTask extends TimerTask implements Task
+ {
+ private final Runnable _task;
+
+ private SimpleTask(Runnable runnable)
+ {
+ _task = runnable;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ _task.run();
+ }
+ catch (Throwable x)
+ {
+ LOG.debug("Exception while executing task " + _task, x);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s.%s@%x",
+ TimerScheduler.class.getSimpleName(),
+ SimpleTask.class.getSimpleName(),
+ hashCode());
+ }
+ }
+}
--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 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.
+// ========================================================================
+//
+
+/**
+ * Jetty Util : Common ThreadPool Utilities
+ */
+package org.eclipse.jetty.util.thread;
+