This class is not synchronized as it is expected that modifications will only be performed by a
+ * single thread.
+ *
+ *
The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
+ *
+ */
+public class HttpFields implements Iterable
+{
+ 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 _fields = new ArrayList<>(20);
+
+ /**
+ * Constructor.
+ */
+ public HttpFields()
+ {
+ }
+
+ /**
+ * Get Collection of header names.
+ */
+ public Collection getFieldNamesCollection()
+ {
+ final Set 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 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 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 getValuesList(String name)
+ {
+ final List 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 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()
+ {
+ 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 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 getValues(String name, final String separators)
+ {
+ final Enumeration e = getValues(name);
+ if (e == null)
+ return null;
+ return new Enumeration()
+ {
+ 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 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 e = fields.getFieldNames();
+ while (e.hasMoreElements())
+ {
+ String name = e.nextElement();
+ Enumeration 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:
+ *
+ *
+ *
+ * @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 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 __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 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 qualityList(Enumeration 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 vl = LazyList.getList(list, false);
+ if (vl.size() < 2)
+ return vl;
+
+ List 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;
+ }
+
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java b/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java
new file mode 100644
index 00000000..a51e4ba7
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpGenerator.java
@@ -0,0 +1,1104 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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
+ *
+ */
+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;i0)
+ {
+ // 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=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;i0xff || 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;i0xff || 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);
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpHeader.java b/lib/jetty/org/eclipse/jetty/http/HttpHeader.java
new file mode 100644
index 00000000..ab0ddcf3
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpHeader.java
@@ -0,0 +1,178 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 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;
+ }
+
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java b/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java
new file mode 100644
index 00000000..8338a322
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpHeaderValue.java
@@ -0,0 +1,104 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 CACHE= new ArrayTrie();
+ 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 __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);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpMethod.java b/lib/jetty/org/eclipse/jetty/http/HttpMethod.java
new file mode 100644
index 00000000..8a262680
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpMethod.java
@@ -0,0 +1,174 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 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);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpParser.java b/lib/jetty/org/eclipse/jetty/http/HttpParser.java
new file mode 100644
index 00000000..79f1c28d
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpParser.java
@@ -0,0 +1,1688 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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
+ *
+ * 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.
+ *
+ *
+ * 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()}
+ *
+ *
+ * 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.
+ *
+ *
+ * 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
+ *
+ */
+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:
+ *
Common static combinations such as:
+ *
Connection: close
+ *
Accept-Encoding: gzip
+ *
Content-Length: 0
+ *
+ *
Combinations of Content-Type header for common mime types by common charsets
+ *
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
+ *
+ */
+ public final static Trie 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 _handler;
+ private final RequestHandler _requestHandler;
+ private final ResponseHandler _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 _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 handler)
+ {
+ this(handler,-1,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler handler)
+ {
+ this(handler,-1,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler handler,int maxHeaderBytes,boolean strict)
+ {
+ _handler=handler;
+ _requestHandler=handler;
+ _responseHandler=null;
+ _maxHeaderBytes=maxHeaderBytes;
+ _strict=strict;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler 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()=0 && ch0 && _state.ordinal() 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()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 (iHttpTokens.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=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()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() && _state.ordinal()= State.CONTENT.ordinal() && _state.ordinal()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
+ {
+ 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 extends HttpHandler
+ {
+ /**
+ * 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 extends HttpHandler
+ {
+ /**
+ * 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 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();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpScheme.java b/lib/jetty/org/eclipse/jetty/http/HttpScheme.java
new file mode 100644
index 00000000..13f2a8d3
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpScheme.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 CACHE= new ArrayTrie();
+ 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;
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpStatus.java b/lib/jetty/org/eclipse/jetty/http/HttpStatus.java
new file mode 100644
index 00000000..e6ea1f71
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpStatus.java
@@ -0,0 +1,1037 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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;
+
+/**
+ *
+ * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see
+ * table below)
+ *
+ *
+ * @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
+ */
+
+ /** 100 Continue */
+ CONTINUE(CONTINUE_100, "Continue"),
+ /** 101 Switching Protocols */
+ SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"),
+ /** 102 Processing */
+ 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
+ */
+
+ /** 200 OK */
+ OK(OK_200, "OK"),
+ /** 201 Created */
+ CREATED(CREATED_201, "Created"),
+ /** 202 Accepted */
+ ACCEPTED(ACCEPTED_202, "Accepted"),
+ /** 203 Non Authoritative Information */
+ NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"),
+ /** 204 No Content */
+ NO_CONTENT(NO_CONTENT_204, "No Content"),
+ /** 205 Reset Content */
+ RESET_CONTENT(RESET_CONTENT_205, "Reset Content"),
+ /** 206 Partial Content */
+ PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"),
+ /** 207 Multi-Status */
+ 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
+ */
+
+ /** 300 Mutliple Choices */
+ MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"),
+ /** 301 Moved Permanently */
+ MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"),
+ /** 302 Moved Temporarily */
+ MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"),
+ /** 302 Found */
+ FOUND(FOUND_302, "Found"),
+ /** 303 See Other */
+ SEE_OTHER(SEE_OTHER_303, "See Other"),
+ /** 304 Not Modified */
+ NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"),
+ /** 305 Use Proxy */
+ USE_PROXY(USE_PROXY_305, "Use Proxy"),
+ /** 307 Temporary Redirect */
+ 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
+ */
+
+ /** 400 Bad Request */
+ BAD_REQUEST(BAD_REQUEST_400, "Bad Request"),
+ /** 401 Unauthorized */
+ UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"),
+ /** 402 Payment Required */
+ PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"),
+ /** 403 Forbidden */
+ FORBIDDEN(FORBIDDEN_403, "Forbidden"),
+ /** 404 Not Found */
+ NOT_FOUND(NOT_FOUND_404, "Not Found"),
+ /** 405 Method Not Allowed */
+ METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"),
+ /** 406 Not Acceptable */
+ NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"),
+ /** 407 Proxy Authentication Required */
+ PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"),
+ /** 408 Request Timeout */
+ REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"),
+ /** 409 Conflict */
+ CONFLICT(CONFLICT_409, "Conflict"),
+ /** 410 Gone */
+ GONE(GONE_410, "Gone"),
+ /** 411 Length Required */
+ LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"),
+ /** 412 Precondition Failed */
+ PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"),
+ /** 413 Request Entity Too Large */
+ REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"),
+ /** 414 Request-URI Too Long */
+ REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"),
+ /** 415 Unsupported Media Type */
+ UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"),
+ /** 416 Requested Range Not Satisfiable */
+ REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"),
+ /** 417 Expectation Failed */
+ EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"),
+ /** 422 Unprocessable Entity */
+ UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"),
+ /** 423 Locked */
+ LOCKED(LOCKED_423, "Locked"),
+ /** 424 Failed Dependency */
+ 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
+ */
+
+ /** 500 Server Error */
+ INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"),
+ /** 501 Not Implemented */
+ NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"),
+ /** 502 Bad Gateway */
+ BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"),
+ /** 503 Service Unavailable */
+ SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"),
+ /** 504 Gateway Timeout */
+ GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"),
+ /** 505 HTTP Version Not Supported */
+ HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"),
+ /** 507 Insufficient Storage */
+ 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
+ * Informational message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 2616 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Informational messages.
+ */
+ public boolean isInformational()
+ {
+ return HttpStatus.isInformational(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Success message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 2616 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Success messages.
+ */
+ public boolean isSuccess()
+ {
+ return HttpStatus.isSuccess(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Redirection message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 2616 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Redirection messages.
+ */
+ public boolean isRedirection()
+ {
+ return HttpStatus.isRedirection(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Client Error message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 2616 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Client Error messages.
+ */
+ public boolean isClientError()
+ {
+ return HttpStatus.isClientError(this._code);
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Server Error message category as defined in the RFC 1945 - HTTP/1.0,
+ * and RFC 2616 -
+ * HTTP/1.1.
+ *
+ * @return true if within range of codes that belongs to
+ * Server Error 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
+ * Informational message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Informational messages.
+ */
+ public static boolean isInformational(int code)
+ {
+ return ((100 <= code) && (code <= 199));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Success message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Success messages.
+ */
+ public static boolean isSuccess(int code)
+ {
+ return ((200 <= code) && (code <= 299));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Redirection message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Redirection messages.
+ */
+ public static boolean isRedirection(int code)
+ {
+ return ((300 <= code) && (code <= 399));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Client Error message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Client Error messages.
+ */
+ public static boolean isClientError(int code)
+ {
+ return ((400 <= code) && (code <= 499));
+ }
+
+ /**
+ * Simple test against an code to determine if it falls into the
+ * Server Error message category as defined in the RFC 1945 - HTTP/1.0, and RFC 2616 - HTTP/1.1.
+ *
+ * @param code
+ * the code to test.
+ * @return true if within range of codes that belongs to
+ * Server Error messages.
+ */
+ public static boolean isServerError(int code)
+ {
+ return ((500 <= code) && (code <= 599));
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpTester.java b/lib/jetty/org/eclipse/jetty/http/HttpTester.java
new file mode 100644
index 00000000..537c457c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpTester.java
@@ -0,0 +1,367 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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
+ {
+ 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
+ {
+ 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
+ {
+ 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());
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpTokens.java b/lib/jetty/org/eclipse/jetty/http/HttpTokens.java
new file mode 100644
index 00000000..4138334d
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpTokens.java
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 }
+
+}
+
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpURI.java b/lib/jetty/org/eclipse/jetty/http/HttpURI.java
new file mode 100644
index 00000000..afe925e1
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpURI.java
@@ -0,0 +1,784 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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
+ * http://user@host:port/path/info;param?query#fragment
+ * this class will split it into the following undecoded optional elements:
+ *
{@link #getScheme()} - http:
+ *
{@link #getAuthority()} - //name@host:port
+ *
{@link #getHost()} - host
+ *
{@link #getPort()} - port
+ *
{@link #getPath()} - /path/info
+ *
{@link #getParam()} - param
+ *
{@link #getQuery()} - query
+ *
{@link #getFragment()} - fragment
+ *
+ *
+ */
+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 (i6 && 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 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 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 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);
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/HttpVersion.java b/lib/jetty/org/eclipse/jetty/http/HttpVersion.java
new file mode 100644
index 00000000..eb889e56
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/HttpVersion.java
@@ -0,0 +1,172 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 CACHE= new ArrayTrie();
+ 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();
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/MimeTypes.java b/lib/jetty/org/eclipse/jetty/http/MimeTypes.java
new file mode 100644
index 00000000..9cac4abd
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/MimeTypes.java
@@ -0,0 +1,485 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 CACHE= new ArrayTrie<>(512);
+ private final static Trie TYPES= new ArrayTrie(512);
+ private final static Map __dftMimeMap = new HashMap();
+ private final static Map __encodings = new HashMap();
+
+ 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 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 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 _mimeMap=new HashMap();
+
+ /* ------------------------------------------------------------ */
+ /** Constructor.
+ */
+ public MimeTypes()
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ public synchronized Map getMimeMap()
+ {
+ return _mimeMap;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @param mimeMap A Map of file extension to mime-type.
+ */
+ public void setMimeMap(Map mimeMap)
+ {
+ _mimeMap.clear();
+ if (mimeMap!=null)
+ {
+ for (Entry 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 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
+ * /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
+ *
+ * Matching is performed in the following order
+ *
Exact match.
+ *
Longest prefix match.
+ *
Longest suffix match.
+ *
default.
+ *
+ * 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)
+ *
+ * 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.
+ *
+ * This class is not synchronized. If concurrent modifications are
+ * possible then it should be synchronized at a higher level.
+ *
+ *
+ */
+public class PathMap extends HashMap
+{
+ /* ------------------------------------------------------------ */
+ 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> _prefixMap=new ArrayTernaryTrie<>(false);
+ Trie> _suffixMap=new ArrayTernaryTrie<>(false);
+ final Map> _exactMap=new HashMap<>();
+
+ List> _defaultSingletonList=null;
+ MappedEntry _prefixDefault=null;
+ MappedEntry _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 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 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 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>)_prefixMap,1.5);
+ }
+ else if (spec.startsWith("*."))
+ {
+ String suffix=spec.substring(2);
+ while(!_suffixMap.put(suffix,entry))
+ _suffixMap=new ArrayTernaryTrie<>((ArrayTernaryTrie>)_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 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 getMatch(String path)
+ {
+ if (path==null)
+ return null;
+
+ int l=path.length();
+
+ MappedEntry 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> 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> 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> getMatches(String path)
+ {
+ MappedEntry entry;
+ List> 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> 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> 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 implements Map.Entry
+ {
+ 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;
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/http/encoding.properties b/lib/jetty/org/eclipse/jetty/http/encoding.properties
new file mode 100644
index 00000000..311c8021
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/encoding.properties
@@ -0,0 +1,4 @@
+text/html = ISO-8859-1
+text/plain = ISO-8859-1
+text/xml = UTF-8
+text/json = UTF-8
diff --git a/lib/jetty/org/eclipse/jetty/http/mime.properties b/lib/jetty/org/eclipse/jetty/http/mime.properties
new file mode 100644
index 00000000..b2709897
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/mime.properties
@@ -0,0 +1,183 @@
+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
diff --git a/lib/jetty/org/eclipse/jetty/http/package-info.java b/lib/jetty/org/eclipse/jetty/http/package-info.java
new file mode 100644
index 00000000..825422a3
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/http/package-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.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;
+
diff --git a/lib/jetty/org/eclipse/jetty/http/useragents b/lib/jetty/org/eclipse/jetty/http/useragents
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java b/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java
new file mode 100644
index 00000000..7f8e9f81
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/AbstractConnection.java
@@ -0,0 +1,587 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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;
+
+/**
+ *
A convenience base implementation of {@link Connection}.
+ *
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.
+ */
+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 listeners = new CopyOnWriteArrayList<>();
+ private final AtomicReference _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);
+ }
+ }
+
+ /**
+ *
Utility method to be called to register read interest.
+ *
After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
+ * will be called back as appropriate.
+ * @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;
+ }
+ }
+
+ /**
+ *
Callback method invoked when the endpoint is ready to be read.
Callback method invoked when the endpoint failed to be ready to be read.
+ * @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();
+ }
+
+ /**
+ *
Callback method invoked when the endpoint failed to be ready to be read after a timeout
+ * @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);
+ }
+ };
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java b/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java
new file mode 100644
index 00000000..8fa2cc86
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/AbstractEndPoint.java
@@ -0,0 +1,180 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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());
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java
new file mode 100644
index 00000000..fa3a01d0
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ArrayByteBufferPool.java
@@ -0,0 +1,133 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 _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;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java b/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java
new file mode 100644
index 00000000..4b8c527c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ByteArrayEndPoint.java
@@ -0,0 +1,409 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 true 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;
+ }
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java
new file mode 100644
index 00000000..302adde3
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ByteBufferPool.java
@@ -0,0 +1,51 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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;
+
+/**
+ *
A {@link ByteBuffer} pool.
+ *
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.
The returned buffer may have a bigger capacity than the size being
+ * requested but it will have the limit set to the given size.
+ *
+ * @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);
+
+ /**
+ *
Returns a {@link ByteBuffer}, usually obtained with {@link #acquire(int, boolean)}
+ * (but not necessarily), making it available for recycling and reuse.
+ *
+ * @param buffer the buffer to return
+ * @see #acquire(int, boolean)
+ */
+ public void release(ByteBuffer buffer);
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java
new file mode 100644
index 00000000..c65ca0cc
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ChannelEndPoint.java
@@ -0,0 +1,229 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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.
+ *
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();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java
new file mode 100644
index 00000000..7eb59318
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/ClientConnectionFactory.java
@@ -0,0 +1,89 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 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.
+ *
+ * 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);
+ }
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/Connection.java b/lib/jetty/org/eclipse/jetty/io/Connection.java
new file mode 100644
index 00000000..96baa016
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/Connection.java
@@ -0,0 +1,89 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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;
+
+/**
+ *
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}.
+ *
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.
Callback method invoked when this {@link Connection} is opened.
+ *
Creators of the connection implementation are responsible for calling this method.
+ */
+ public void onOpen();
+
+ /**
+ *
Callback method invoked when this {@link Connection} is closed.
+ *
Creators of the connection implementation are responsible for calling this method.
+ */
+ public void onClose();
+
+ /**
+ * @return the {@link EndPoint} associated with this {@link Connection}
+ */
+ public EndPoint getEndPoint();
+
+ /**
+ *
Performs a logical close of this connection.
+ *
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}.
+ */
+ @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)
+ {
+ }
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/EndPoint.java b/lib/jetty/org/eclipse/jetty/io/EndPoint.java
new file mode 100644
index 00000000..87adb40b
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/EndPoint.java
@@ -0,0 +1,243 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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
+ *
+ *
Asynchronous Methods
+ *
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.
+ *
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:
+ *
+ *
Blocking Read
+ *
A FutureCallback can be used to block until an endpoint is ready to be filled
+ * from:
+ *
+ * FutureCallback<String> future = new FutureCallback<>();
+ * endpoint.fillInterested("ContextObj",future);
+ * ...
+ * String context = future.get(); // This blocks
+ * int filled=endpoint.fill(mybuffer);
+ *
+ *
+ *
Dispatched Read
+ *
By using a different callback, the read can be done asynchronously in its own dispatched thread:
+ *
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.
+ *
+ *
Blocking Write
+ *
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:
+ *
+ */
+public interface EndPoint extends Closeable
+{
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The local Inet address to which this EndPoint is bound, or null
+ * if this EndPoint does not represent a network connection.
+ */
+ InetSocketAddress getLocalAddress();
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @return The remote Inet address to which this EndPoint is bound, or null
+ * if this EndPoint does not represent a network connection.
+ */
+ InetSocketAddress getRemoteAddress();
+
+ /* ------------------------------------------------------------ */
+ boolean isOpen();
+
+ /* ------------------------------------------------------------ */
+ long getCreatedTimeStamp();
+
+ /* ------------------------------------------------------------ */
+ /** Shutdown the output.
+ *
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.
+ *
+ * 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 int 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.
+ *
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);
+
+
+ /**
+ *
Requests callback methods to be invoked when a call to {@link #fill(ByteBuffer)} would return data or EOF.
+ *
+ * @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;
+
+ /**
+ *
Writes the given buffers via {@link #flush(ByteBuffer...)} and invokes callback methods when either
+ * all the data has been flushed or an error occurs.
+ *
+ * @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);
+
+ /**
+ *
Callback method invoked when this {@link EndPoint} is opened.
Callback method invoked when this {@link EndPoint} is close.
+ * @see #onOpen()
+ */
+ void onClose();
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/EofException.java b/lib/jetty/org/eclipse/jetty/io/EofException.java
new file mode 100644
index 00000000..72042f40
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/EofException.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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.
+ *
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);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/FillInterest.java b/lib/jetty/org/eclipse/jetty/io/FillInterest.java
new file mode 100644
index 00000000..b2c3f685
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/FillInterest.java
@@ -0,0 +1,135 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 _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 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;
+
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java b/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java
new file mode 100644
index 00000000..8b251ac8
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/IdleTimeout.java
@@ -0,0 +1,182 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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.
+ *
+ * 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 _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();
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java
new file mode 100644
index 00000000..a6fc7562
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/LeakTrackingByteBufferPool.java
@@ -0,0 +1,73 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 leakDetector = new LeakDetector()
+ {
+ @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.LeakInfo leakInfo)
+ {
+ LOG.warn("ByteBuffer " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java b/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java
new file mode 100644
index 00000000..b331904c
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/MappedByteBufferPool.java
@@ -0,0 +1,111 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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> directBuffers = new ConcurrentHashMap<>();
+ private final ConcurrentMap> 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> buffers = buffersFor(direct);
+
+ ByteBuffer result = null;
+ Queue 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> buffers = buffersFor(buffer.isDirect());
+
+ // Avoid to create a new queue every time, just to be discarded immediately
+ Queue byteBuffers = buffers.get(bucket);
+ if (byteBuffers == null)
+ {
+ byteBuffers = new ConcurrentLinkedQueue<>();
+ Queue 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> buffersFor(boolean direct)
+ {
+ return direct ? directBuffers : heapBuffers;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java
new file mode 100644
index 00000000..cd05630f
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnection.java
@@ -0,0 +1,128 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 context;
+ private volatile boolean completed;
+
+ protected NegotiatingClientConnection(EndPoint endp, Executor executor, SSLEngine sslEngine, ClientConnectionFactory connectionFactory, Map 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();
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java
new file mode 100644
index 00000000..ff386007
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/NegotiatingClientConnectionFactory.java
@@ -0,0 +1,35 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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;
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java
new file mode 100644
index 00000000..6a4239f0
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficListener.java
@@ -0,0 +1,100 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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;
+
+/**
+ *
A listener for raw network traffic within Jetty.
+ *
{@link NetworkTrafficListener}s can be installed in a
+ * org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector,
+ * and are notified of the following network traffic events:
+ *
+ *
Connection opened, when the server has accepted the connection from a remote client
+ *
Incoming bytes, when the server receives bytes sent from a remote client
+ *
Outgoing bytes, when the server sends bytes to a remote client
+ *
Connection closed, when the server has closed the connection to a remote client
+ *
+ *
{@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.
Callback method invoked when a connection from a remote client has been accepted.
+ *
The {@code socket} parameter can be used to extract socket address information of
+ * the remote client.
+ *
+ * @param socket the socket associated with the remote client
+ */
+ public void opened(Socket socket);
+
+ /**
+ *
Callback method invoked when bytes sent by a remote client arrived on the server.
+ *
+ * @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);
+
+ /**
+ *
Callback method invoked when bytes are sent to a remote client from the server.
+ *
This method is invoked after the bytes have been actually written to the remote client.
+ *
+ * @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);
+
+ /**
+ *
Callback method invoked when a connection to a remote client has been closed.
+ *
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.
+ * 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);
+
+ /**
+ *
A commodity class that implements {@link NetworkTrafficListener} with empty methods.
+ */
+ 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)
+ {
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
new file mode 100644
index 00000000..a4b6f7d2
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java
@@ -0,0 +1,155 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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 listeners;
+
+ public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key, Scheduler scheduler, long idleTimeout, List 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);
+ }
+ }
+ }
+ }
+
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java b/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java
new file mode 100644
index 00000000..88c33f73
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/RuntimeIOException.java
@@ -0,0 +1,48 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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);
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java b/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java
new file mode 100644
index 00000000..30504029
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/SelectChannelEndPoint.java
@@ -0,0 +1,207 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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());
+ }
+ }
+}
diff --git a/lib/jetty/org/eclipse/jetty/io/SelectorManager.java b/lib/jetty/org/eclipse/jetty/io/SelectorManager.java
new file mode 100644
index 00000000..fd3c6c6b
--- /dev/null
+++ b/lib/jetty/org/eclipse/jetty/io/SelectorManager.java
@@ -0,0 +1,984 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// 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;
+
+/**
+ *
{@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.
+ *
{@link SelectorManager} subclasses implement methods to return protocol-specific
+ * {@link EndPoint}s and {@link Connection}s.
+ */
+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];
+ }
+
+ /**
+ *
Registers a channel to perform a non-blocking connect.
+ *
The channel must be set in non-blocking mode, and {@link SocketChannel#connect(SocketAddress)}
+ * must be called prior to calling this method.
+ *
+ * @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));
+ }
+
+ /**
+ *
Registers a channel to perform non-blocking read/write operations.
+ *
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)}.
+ *
+ * @param channel the channel to register
+ */
+ public void accept(final SocketChannel channel)
+ {
+ final ManagedSelector selector = chooseSelector();
+ selector.submit(selector.new Accept(channel));
+ }
+
+ /**
+ *
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));
+ }
+ }
+
+ /**
+ *
Factory method for {@link ManagedSelector}.
+ *
+ * @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();
+ }
+
+ /**
+ *
Callback method invoked when an endpoint is opened.
+ *
+ * @param endpoint the endpoint being opened
+ */
+ protected void endPointOpened(EndPoint endpoint)
+ {
+ endpoint.onOpen();
+ }
+
+ /**
+ *
Callback method invoked when an endpoint is closed.
Callback method invoked when a non-blocking connect cannot be completed.
+ *
By default it just logs with level warning.
+ *
+ * @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);
+ }
+
+ /**
+ *
Factory method to create {@link EndPoint}.
+ *
This method is invoked as a result of the registration of a channel via {@link #connect(SocketChannel, Object)}
+ * or {@link #accept(SocketChannel)}.
+ *
+ * @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;
+
+ /**
+ *
Factory method to create {@link Connection}.
+ *
+ * @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
+ }
+
+ /**
+ *
{@link ManagedSelector} wraps a {@link Selector} simplifying non-blocking operations on channels.
+ *
{@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.
+ */
+ public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
+ {
+ private final AtomicReference _state= new AtomicReference<>(State.PROCESS);
+ private final Queue _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();
+ }
+ }
+
+ /**
+ *
Submits a change to be executed in the selector thread.
+ *
Changes may be submitted from any thread, and the selector thread woken up
+ * (if necessary) to execute the change.
+ *
+ * @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);
+ }
+ }
+
+ /**
+ *
Process changes and waits on {@link Selector#select()}.
+ *
+ * @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 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