Introduce TextHeaders and AsciiString
Motivation: We have quite a bit of code duplication between HTTP/1, HTTP/2, SPDY, and STOMP codec, because they all have a notion of 'headers', which is a multimap of string names and values. Modifications: - Add TextHeaders and its default implementation - Add AsciiString to replace HttpHeaderEntity - Borrowed some portion from Apache Harmony's java.lang.String. - Reimplement HttpHeaders, SpdyHeaders, and StompHeaders using TextHeaders - Add AsciiHeadersEncoder to reuse the encoding a TextHeaders - Used a dedicated encoder for HTTP headers for better performance though - Remove shortcut methods in SpdyHeaders - Replace SpdyHeaders.getStatus() with HttpResponseStatus.parseLine() Result: - Removed quite a bit of code duplication in the header implementations. - Slightly better performance thanks to improved header validation and hash code calculation
This commit is contained in:
parent
2a2a21ec59
commit
681d460938
@ -64,6 +64,14 @@ Bloch of Google, Inc:
|
|||||||
* LICENSE:
|
* LICENSE:
|
||||||
* license/LICENSE.deque.txt (Public Domain)
|
* license/LICENSE.deque.txt (Public Domain)
|
||||||
|
|
||||||
|
This product contains a modified portion of 'Apache Harmony', an open source
|
||||||
|
Java SE, which can be obtained at:
|
||||||
|
|
||||||
|
* LICENSE:
|
||||||
|
* license/LICENSE.harmony.txt (Apache License 2.0)
|
||||||
|
* HOMEPAGE:
|
||||||
|
* http://archive.apache.org/dist/harmony/
|
||||||
|
|
||||||
This product contains a modified version of Roland Kuhn's ASL2
|
This product contains a modified version of Roland Kuhn's ASL2
|
||||||
AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue.
|
AbstractNodeQueue, which is based on Dmitriy Vyukov's non-intrusive MPSC queue.
|
||||||
It can be obtained at:
|
It can be obtained at:
|
||||||
@ -137,3 +145,4 @@ can be obtained at:
|
|||||||
* license/LICENSE.log4j.txt (Apache License 2.0)
|
* license/LICENSE.log4j.txt (Apache License 2.0)
|
||||||
* HOMEPAGE:
|
* HOMEPAGE:
|
||||||
* http://logging.apache.org/log4j/
|
* http://logging.apache.org/log4j/
|
||||||
|
|
||||||
|
@ -16,53 +16,37 @@
|
|||||||
package io.netty.handler.codec.http;
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
|
import io.netty.handler.codec.DefaultTextHeaders;
|
||||||
|
import io.netty.handler.codec.TextHeaders;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class DefaultHttpHeaders extends HttpHeaders {
|
public class DefaultHttpHeaders extends HttpHeaders {
|
||||||
|
|
||||||
private static final int BUCKET_SIZE = 17;
|
private final TextHeaders headers;
|
||||||
|
|
||||||
private static int index(int hash) {
|
|
||||||
return hash % BUCKET_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
|
|
||||||
private final HeaderEntry head = new HeaderEntry();
|
|
||||||
protected final boolean validate;
|
|
||||||
|
|
||||||
public DefaultHttpHeaders() {
|
public DefaultHttpHeaders() {
|
||||||
this(true);
|
this(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttpHeaders(boolean validate) {
|
public DefaultHttpHeaders(boolean validate) {
|
||||||
this.validate = validate;
|
headers = validate? new ValidatingTextHeaders() : new NonValidatingTextHeaders();
|
||||||
head.before = head.after = head;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateHeaderName0(CharSequence headerName) {
|
DefaultHttpHeaders(TextHeaders headers) {
|
||||||
validateHeaderName(headerName);
|
this.headers = headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders add(HttpHeaders headers) {
|
public HttpHeaders add(HttpHeaders headers) {
|
||||||
if (headers instanceof DefaultHttpHeaders) {
|
if (headers instanceof DefaultHttpHeaders) {
|
||||||
DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers;
|
this.headers.add(((DefaultHttpHeaders) headers).headers);
|
||||||
HeaderEntry e = defaultHttpHeaders.head.after;
|
|
||||||
while (e != defaultHttpHeaders.head) {
|
|
||||||
add(e.key, e.value);
|
|
||||||
e = e.after;
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
} else {
|
} else {
|
||||||
return super.add(headers);
|
return super.add(headers);
|
||||||
@ -72,13 +56,7 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
|||||||
@Override
|
@Override
|
||||||
public HttpHeaders set(HttpHeaders headers) {
|
public HttpHeaders set(HttpHeaders headers) {
|
||||||
if (headers instanceof DefaultHttpHeaders) {
|
if (headers instanceof DefaultHttpHeaders) {
|
||||||
clear();
|
this.headers.set(((DefaultHttpHeaders) headers).headers);
|
||||||
DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers;
|
|
||||||
HeaderEntry e = defaultHttpHeaders.head.after;
|
|
||||||
while (e != defaultHttpHeaders.head) {
|
|
||||||
add(e.key, e.value);
|
|
||||||
e = e.after;
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
} else {
|
} else {
|
||||||
return super.set(headers);
|
return super.set(headers);
|
||||||
@ -86,413 +64,329 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders add(final String name, final Object value) {
|
public HttpHeaders add(String name, Object value) {
|
||||||
return add((CharSequence) name, value);
|
headers.add(name, value);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders add(final CharSequence name, final Object value) {
|
public HttpHeaders add(CharSequence name, Object value) {
|
||||||
CharSequence strVal;
|
headers.add(name, value);
|
||||||
if (validate) {
|
|
||||||
validateHeaderName0(name);
|
|
||||||
strVal = toCharSequence(value);
|
|
||||||
validateHeaderValue(strVal);
|
|
||||||
} else {
|
|
||||||
strVal = toCharSequence(value);
|
|
||||||
}
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
add0(h, i, name, strVal);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders add(String name, Iterable<?> values) {
|
public HttpHeaders add(String name, Iterable<?> values) {
|
||||||
return add((CharSequence) name, values);
|
headers.add(name, values);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders add(CharSequence name, Iterable<?> values) {
|
public HttpHeaders add(CharSequence name, Iterable<?> values) {
|
||||||
if (validate) {
|
headers.add(name, values);
|
||||||
validateHeaderName0(name);
|
|
||||||
}
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
for (Object v: values) {
|
|
||||||
CharSequence vstr = toCharSequence(v);
|
|
||||||
if (validate) {
|
|
||||||
validateHeaderValue(vstr);
|
|
||||||
}
|
|
||||||
add0(h, i, name, vstr);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add0(int h, int i, final CharSequence name, final CharSequence value) {
|
|
||||||
// Update the hash table.
|
|
||||||
HeaderEntry e = entries[i];
|
|
||||||
HeaderEntry newEntry;
|
|
||||||
entries[i] = newEntry = new HeaderEntry(h, name, value);
|
|
||||||
newEntry.next = e;
|
|
||||||
|
|
||||||
// Update the linked list.
|
|
||||||
newEntry.addBefore(head);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpHeaders remove(final String name) {
|
|
||||||
return remove((CharSequence) name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpHeaders remove(final CharSequence name) {
|
|
||||||
if (name == null) {
|
|
||||||
throw new NullPointerException("name");
|
|
||||||
}
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
remove0(h, i, name);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void remove0(int h, int i, CharSequence name) {
|
|
||||||
HeaderEntry e = entries[i];
|
|
||||||
if (e == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
if (e.hash == h && equalsIgnoreCase(name, e.key)) {
|
|
||||||
e.remove();
|
|
||||||
HeaderEntry next = e.next;
|
|
||||||
if (next != null) {
|
|
||||||
entries[i] = next;
|
|
||||||
e = next;
|
|
||||||
} else {
|
|
||||||
entries[i] = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
HeaderEntry next = e.next;
|
|
||||||
if (next == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (next.hash == h && equalsIgnoreCase(name, next.key)) {
|
|
||||||
e.next = next.next;
|
|
||||||
next.remove();
|
|
||||||
} else {
|
|
||||||
e = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpHeaders set(final String name, final Object value) {
|
|
||||||
return set((CharSequence) name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpHeaders set(final CharSequence name, final Object value) {
|
|
||||||
CharSequence strVal;
|
|
||||||
if (validate) {
|
|
||||||
validateHeaderName0(name);
|
|
||||||
strVal = toCharSequence(value);
|
|
||||||
validateHeaderValue(strVal);
|
|
||||||
} else {
|
|
||||||
strVal = toCharSequence(value);
|
|
||||||
}
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
remove0(h, i, name);
|
|
||||||
add0(h, i, name, strVal);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders set(final String name, final Iterable<?> values) {
|
public HttpHeaders remove(String name) {
|
||||||
return set((CharSequence) name, values);
|
headers.remove(name);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders set(final CharSequence name, final Iterable<?> values) {
|
public HttpHeaders remove(CharSequence name) {
|
||||||
if (values == null) {
|
headers.remove(name);
|
||||||
throw new NullPointerException("values");
|
return this;
|
||||||
}
|
}
|
||||||
if (validate) {
|
|
||||||
validateHeaderName0(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
int h = hash(name);
|
@Override
|
||||||
int i = index(h);
|
public HttpHeaders set(String name, Object value) {
|
||||||
|
headers.set(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
remove0(h, i, name);
|
@Override
|
||||||
for (Object v: values) {
|
public HttpHeaders set(CharSequence name, Object value) {
|
||||||
if (v == null) {
|
headers.set(name, value);
|
||||||
break;
|
return this;
|
||||||
}
|
}
|
||||||
CharSequence strVal = toCharSequence(v);
|
|
||||||
if (validate) {
|
|
||||||
validateHeaderValue(strVal);
|
|
||||||
}
|
|
||||||
add0(h, i, name, strVal);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpHeaders set(String name, Iterable<?> values) {
|
||||||
|
headers.set(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpHeaders set(CharSequence name, Iterable<?> values) {
|
||||||
|
headers.set(name, values);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpHeaders clear() {
|
public HttpHeaders clear() {
|
||||||
Arrays.fill(entries, null);
|
headers.clear();
|
||||||
head.before = head.after = head;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get(final String name) {
|
public String get(String name) {
|
||||||
return get((CharSequence) name);
|
return headers.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get(final CharSequence name) {
|
public String get(CharSequence name) {
|
||||||
if (name == null) {
|
return headers.get(name);
|
||||||
throw new NullPointerException("name");
|
|
||||||
}
|
|
||||||
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
HeaderEntry e = entries[i];
|
|
||||||
CharSequence value = null;
|
|
||||||
// loop until the first header was found
|
|
||||||
while (e != null) {
|
|
||||||
if (e.hash == h && equalsIgnoreCase(name, e.key)) {
|
|
||||||
value = e.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
e = e.next;
|
|
||||||
}
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getAll(final String name) {
|
public List<String> getAll(String name) {
|
||||||
return getAll((CharSequence) name);
|
return headers.getAll(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getAll(final CharSequence name) {
|
public List<String> getAll(CharSequence name) {
|
||||||
if (name == null) {
|
return headers.getAll(name);
|
||||||
throw new NullPointerException("name");
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkedList<String> values = new LinkedList<String>();
|
|
||||||
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
HeaderEntry e = entries[i];
|
|
||||||
while (e != null) {
|
|
||||||
if (e.hash == h && equalsIgnoreCase(name, e.key)) {
|
|
||||||
values.addFirst(e.getValue());
|
|
||||||
}
|
|
||||||
e = e.next;
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Map.Entry<String, String>> entries() {
|
public List<Map.Entry<String, String>> entries() {
|
||||||
List<Map.Entry<String, String>> all =
|
return headers.entries();
|
||||||
new LinkedList<Map.Entry<String, String>>();
|
|
||||||
|
|
||||||
HeaderEntry e = head.after;
|
|
||||||
while (e != head) {
|
|
||||||
all.add(e);
|
|
||||||
e = e.after;
|
|
||||||
}
|
|
||||||
return all;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Map.Entry<String, String>> iterator() {
|
public Iterator<Map.Entry<String, String>> iterator() {
|
||||||
return new HeaderIterator();
|
return headers.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(String name) {
|
public boolean contains(String name) {
|
||||||
return get(name) != null;
|
return headers.contains(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(CharSequence name) {
|
public boolean contains(CharSequence name) {
|
||||||
return get(name) != null;
|
return headers.contains(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return head == head.after;
|
return headers.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(String name, String value, boolean ignoreCaseValue) {
|
public boolean contains(String name, String value, boolean ignoreCase) {
|
||||||
return contains((CharSequence) name, (CharSequence) value, ignoreCaseValue);
|
return headers.contains(name, value, ignoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCaseValue) {
|
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
|
||||||
if (name == null) {
|
return headers.contains(name, value, ignoreCase);
|
||||||
throw new NullPointerException("name");
|
|
||||||
}
|
|
||||||
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
HeaderEntry e = entries[i];
|
|
||||||
while (e != null) {
|
|
||||||
if (e.hash == h && equalsIgnoreCase(name, e.key)) {
|
|
||||||
if (ignoreCaseValue) {
|
|
||||||
if (equalsIgnoreCase(e.value, value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (e.value.equals(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e = e.next;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> names() {
|
public Set<String> names() {
|
||||||
Set<String> names = new LinkedHashSet<String>();
|
return headers.names();
|
||||||
HeaderEntry e = head.after;
|
|
||||||
while (e != head) {
|
|
||||||
names.add(e.getKey());
|
|
||||||
e = e.after;
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void encode(ByteBuf buf) {
|
void encode(ByteBuf buf) {
|
||||||
HeaderEntry e = head.after;
|
headers.forEachEntry(new HttpHeadersEncoder(buf));
|
||||||
while (e != head) {
|
|
||||||
e.encode(buf);
|
|
||||||
e = e.after;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CharSequence toCharSequence(Object value) {
|
static class NonValidatingTextHeaders extends DefaultTextHeaders {
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (value instanceof CharSequence) {
|
|
||||||
return (CharSequence) value;
|
|
||||||
}
|
|
||||||
if (value instanceof Number) {
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
if (value instanceof Date) {
|
|
||||||
return HttpHeaderDateFormat.get().format((Date) value);
|
|
||||||
}
|
|
||||||
if (value instanceof Calendar) {
|
|
||||||
return HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class HeaderIterator implements Iterator<Map.Entry<String, String>> {
|
|
||||||
|
|
||||||
private HeaderEntry current = head;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext() {
|
protected CharSequence convertValue(Object value) {
|
||||||
return current.after != head;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Entry<String, String> next() {
|
|
||||||
current = current.after;
|
|
||||||
|
|
||||||
if (current == head) {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class HeaderEntry implements Map.Entry<String, String> {
|
|
||||||
final int hash;
|
|
||||||
final CharSequence key;
|
|
||||||
CharSequence value;
|
|
||||||
HeaderEntry next;
|
|
||||||
HeaderEntry before, after;
|
|
||||||
|
|
||||||
HeaderEntry(int hash, CharSequence key, CharSequence value) {
|
|
||||||
this.hash = hash;
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
HeaderEntry() {
|
|
||||||
hash = -1;
|
|
||||||
key = null;
|
|
||||||
value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove() {
|
|
||||||
before.after = after;
|
|
||||||
after.before = before;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addBefore(HeaderEntry e) {
|
|
||||||
after = e;
|
|
||||||
before = e.before;
|
|
||||||
before.after = this;
|
|
||||||
after.before = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getKey() {
|
|
||||||
return key.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getValue() {
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String setValue(String value) {
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw new NullPointerException("value");
|
throw new NullPointerException("value");
|
||||||
}
|
}
|
||||||
validateHeaderValue(value);
|
|
||||||
CharSequence oldValue = this.value;
|
CharSequence seq;
|
||||||
this.value = value;
|
if (value instanceof CharSequence) {
|
||||||
return oldValue.toString();
|
seq = (CharSequence) value;
|
||||||
|
} else if (value instanceof Number) {
|
||||||
|
seq = value.toString();
|
||||||
|
} else if (value instanceof Date) {
|
||||||
|
seq = HttpHeaderDateFormat.get().format((Date) value);
|
||||||
|
} else if (value instanceof Calendar) {
|
||||||
|
seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
|
||||||
|
} else {
|
||||||
|
seq = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ValidatingTextHeaders extends NonValidatingTextHeaders {
|
||||||
|
private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63;
|
||||||
|
private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A look-up table used for checking if a character in a header name is prohibited.
|
||||||
|
*/
|
||||||
|
private static final byte[] LOOKUP_TABLE = new byte[~HIGHEST_INVALID_NAME_CHAR_MASK + 1];
|
||||||
|
|
||||||
|
static {
|
||||||
|
LOOKUP_TABLE['\t'] = -1;
|
||||||
|
LOOKUP_TABLE['\n'] = -1;
|
||||||
|
LOOKUP_TABLE[0x0b] = -1;
|
||||||
|
LOOKUP_TABLE['\f'] = -1;
|
||||||
|
LOOKUP_TABLE[' '] = -1;
|
||||||
|
LOOKUP_TABLE[','] = -1;
|
||||||
|
LOOKUP_TABLE[':'] = -1;
|
||||||
|
LOOKUP_TABLE[';'] = -1;
|
||||||
|
LOOKUP_TABLE['='] = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
protected CharSequence convertName(CharSequence name) {
|
||||||
return key.toString() + '=' + value.toString();
|
name = super.convertName(name);
|
||||||
|
if (name instanceof AsciiString) {
|
||||||
|
validateName((AsciiString) name);
|
||||||
|
} else {
|
||||||
|
validateName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void encode(ByteBuf buf) {
|
private static void validateName(AsciiString name) {
|
||||||
HttpHeaders.encode(key, value, buf);
|
// Go through each characters in the name
|
||||||
|
final int start = name.arrayOffset();
|
||||||
|
final int end = start + name.length();
|
||||||
|
final byte[] array = name.array();
|
||||||
|
for (int index = start; index < end; index ++) {
|
||||||
|
byte b = array[index];
|
||||||
|
|
||||||
|
// Check to see if the character is not an ASCII character
|
||||||
|
if (b < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"a header name cannot contain non-ASCII characters: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for prohibited characters.
|
||||||
|
validateNameChar(name, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateName(CharSequence name) {
|
||||||
|
// Go through each characters in the name
|
||||||
|
for (int index = 0; index < name.length(); index ++) {
|
||||||
|
char character = name.charAt(index);
|
||||||
|
|
||||||
|
// Check to see if the character is not an ASCII character
|
||||||
|
if (character > 127) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"a header name cannot contain non-ASCII characters: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for prohibited characters.
|
||||||
|
validateNameChar(name, character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateNameChar(CharSequence name, int character) {
|
||||||
|
if ((character & HIGHEST_INVALID_NAME_CHAR_MASK) == 0 && LOOKUP_TABLE[character] != 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"a header name cannot contain the following prohibited characters: " +
|
||||||
|
"=,;: \\t\\r\\n\\v\\f: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CharSequence convertValue(Object value) {
|
||||||
|
CharSequence seq = super.convertValue(value);
|
||||||
|
if (value instanceof AsciiString) {
|
||||||
|
validateValue((AsciiString) seq);
|
||||||
|
} else {
|
||||||
|
validateValue(seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateValue(AsciiString seq) {
|
||||||
|
int state = 0;
|
||||||
|
// Start looping through each of the character
|
||||||
|
final int start = seq.arrayOffset();
|
||||||
|
final int end = start + seq.length();
|
||||||
|
final byte[] array = seq.array();
|
||||||
|
for (int index = start; index < end; index ++) {
|
||||||
|
state = validateValueChar(seq, state, (char) (array[index] & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"a header value must not end with '\\r' or '\\n':" + seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateValue(CharSequence seq) {
|
||||||
|
int state = 0;
|
||||||
|
// Start looping through each of the character
|
||||||
|
for (int index = 0; index < seq.length(); index ++) {
|
||||||
|
state = validateValueChar(seq, state, seq.charAt(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"a header value must not end with '\\r' or '\\n':" + seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int validateValueChar(CharSequence seq, int state, char character) {
|
||||||
|
/*
|
||||||
|
* State:
|
||||||
|
*
|
||||||
|
* 0: Previous character was neither CR nor LF
|
||||||
|
* 1: The previous character was CR
|
||||||
|
* 2: The previous character was LF
|
||||||
|
*/
|
||||||
|
if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) {
|
||||||
|
// Check the absolutely prohibited characters.
|
||||||
|
switch (character) {
|
||||||
|
case 0x0b: // Vertical tab
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"a header value contains a prohibited character '\\v': " + seq);
|
||||||
|
case '\f':
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"a header value contains a prohibited character '\\f': " + seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the CRLF (HT | SP) pattern
|
||||||
|
switch (state) {
|
||||||
|
case 0:
|
||||||
|
switch (character) {
|
||||||
|
case '\r':
|
||||||
|
state = 1;
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
state = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
switch (character) {
|
||||||
|
case '\n':
|
||||||
|
state = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"only '\\n' is allowed after '\\r': " + seq);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
switch (character) {
|
||||||
|
case '\t': case ' ':
|
||||||
|
state = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"only ' ' and '\\t' are allowed after '\\n': " + seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ package io.netty.handler.codec.http;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.handler.codec.http.DefaultHttpHeaders.NonValidatingTextHeaders;
|
||||||
|
import io.netty.handler.codec.http.DefaultHttpHeaders.ValidatingTextHeaders;
|
||||||
import io.netty.util.internal.StringUtil;
|
import io.netty.util.internal.StringUtil;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -39,7 +41,8 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
|
|||||||
|
|
||||||
public DefaultLastHttpContent(ByteBuf content, boolean validateHeaders) {
|
public DefaultLastHttpContent(ByteBuf content, boolean validateHeaders) {
|
||||||
super(content);
|
super(content);
|
||||||
trailingHeaders = new TrailingHeaders(validateHeaders);
|
trailingHeaders = new DefaultHttpHeaders(
|
||||||
|
validateHeaders? new ValidatingTrailingTextHeaders() : new NonValidatingTextHeaders());
|
||||||
this.validateHeaders = validateHeaders;
|
this.validateHeaders = validateHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,20 +109,17 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class TrailingHeaders extends DefaultHttpHeaders {
|
private static final class ValidatingTrailingTextHeaders extends ValidatingTextHeaders {
|
||||||
TrailingHeaders(boolean validate) {
|
|
||||||
super(validate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void validateHeaderName0(CharSequence name) {
|
protected CharSequence convertName(CharSequence name) {
|
||||||
super.validateHeaderName0(name);
|
name = super.convertName(name);
|
||||||
if (equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) ||
|
if (HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) ||
|
||||||
equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) ||
|
HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) ||
|
||||||
equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) {
|
HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"prohibited trailing header: " + name);
|
"prohibited trailing header: " + name);
|
||||||
}
|
}
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013 The Netty Project
|
|
||||||
*
|
|
||||||
* The Netty Project licenses this file to you under the Apache License,
|
|
||||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package io.netty.handler.codec.http;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.util.CharsetUtil;
|
|
||||||
|
|
||||||
final class HttpHeaderEntity implements CharSequence {
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final int hash;
|
|
||||||
private final byte[] bytes;
|
|
||||||
|
|
||||||
public HttpHeaderEntity(String name) {
|
|
||||||
this.name = name;
|
|
||||||
hash = HttpHeaders.hash(name);
|
|
||||||
bytes = name.getBytes(CharsetUtil.US_ASCII);
|
|
||||||
}
|
|
||||||
|
|
||||||
int hash() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int length() {
|
|
||||||
return bytes.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char charAt(int index) {
|
|
||||||
return (char) bytes[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence subSequence(int start, int end) {
|
|
||||||
return new HttpHeaderEntity(name.substring(start, end));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void encode(ByteBuf buf) {
|
|
||||||
buf.writeBytes(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,6 +16,7 @@
|
|||||||
package io.netty.handler.codec.http;
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
@ -1146,115 +1147,6 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the name of a header
|
|
||||||
*
|
|
||||||
* @param headerName The header name being validated
|
|
||||||
*/
|
|
||||||
static void validateHeaderName(CharSequence headerName) {
|
|
||||||
//Check to see if the name is null
|
|
||||||
if (headerName == null) {
|
|
||||||
throw new NullPointerException("Header names cannot be null");
|
|
||||||
}
|
|
||||||
//Go through each of the characters in the name
|
|
||||||
for (int index = 0; index < headerName.length(); index ++) {
|
|
||||||
//Actually get the character
|
|
||||||
char character = headerName.charAt(index);
|
|
||||||
|
|
||||||
//Check to see if the character is not an ASCII character
|
|
||||||
if (character > 127) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Header name cannot contain non-ASCII characters: " + headerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check for prohibited characters.
|
|
||||||
switch (character) {
|
|
||||||
case '\t': case '\n': case 0x0b: case '\f': case '\r':
|
|
||||||
case ' ': case ',': case ':': case ';': case '=':
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Header name cannot contain the following prohibited characters: " +
|
|
||||||
"=,;: \\t\\r\\n\\v\\f: " + headerName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the specified header value
|
|
||||||
*
|
|
||||||
* @param headerValue The value being validated
|
|
||||||
*/
|
|
||||||
static void validateHeaderValue(CharSequence headerValue) {
|
|
||||||
//Check to see if the value is null
|
|
||||||
if (headerValue == null) {
|
|
||||||
throw new NullPointerException("Header values cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set up the state of the validation
|
|
||||||
*
|
|
||||||
* States are as follows:
|
|
||||||
*
|
|
||||||
* 0: Previous character was neither CR nor LF
|
|
||||||
* 1: The previous character was CR
|
|
||||||
* 2: The previous character was LF
|
|
||||||
*/
|
|
||||||
int state = 0;
|
|
||||||
|
|
||||||
//Start looping through each of the character
|
|
||||||
|
|
||||||
for (int index = 0; index < headerValue.length(); index ++) {
|
|
||||||
char character = headerValue.charAt(index);
|
|
||||||
|
|
||||||
//Check the absolutely prohibited characters.
|
|
||||||
switch (character) {
|
|
||||||
case 0x0b: // Vertical tab
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Header value contains a prohibited character '\\v': " + headerValue);
|
|
||||||
case '\f':
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Header value contains a prohibited character '\\f': " + headerValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the CRLF (HT | SP) pattern
|
|
||||||
switch (state) {
|
|
||||||
case 0:
|
|
||||||
switch (character) {
|
|
||||||
case '\r':
|
|
||||||
state = 1;
|
|
||||||
break;
|
|
||||||
case '\n':
|
|
||||||
state = 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
switch (character) {
|
|
||||||
case '\n':
|
|
||||||
state = 2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Only '\\n' is allowed after '\\r': " + headerValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
switch (character) {
|
|
||||||
case '\t': case ' ':
|
|
||||||
state = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Only ' ' and '\\t' are allowed after '\\n': " + headerValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state != 0) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Header value must not end with '\\r' or '\\n':" + headerValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see if the transfer encoding in a specified {@link HttpMessage} is chunked
|
* Checks to see if the transfer encoding in a specified {@link HttpMessage} is chunked
|
||||||
*
|
*
|
||||||
@ -1329,28 +1221,6 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hash(CharSequence name) {
|
|
||||||
if (name instanceof HttpHeaderEntity) {
|
|
||||||
return ((HttpHeaderEntity) name).hash();
|
|
||||||
}
|
|
||||||
int h = 0;
|
|
||||||
for (int i = name.length() - 1; i >= 0; i --) {
|
|
||||||
char c = name.charAt(i);
|
|
||||||
if (c >= 'A' && c <= 'Z') {
|
|
||||||
c += 32;
|
|
||||||
}
|
|
||||||
h = 31 * h + c;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (h > 0) {
|
|
||||||
return h;
|
|
||||||
} else if (h == Integer.MIN_VALUE) {
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
} else {
|
|
||||||
return -h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void encode(HttpHeaders headers, ByteBuf buf) {
|
static void encode(HttpHeaders headers, ByteBuf buf) {
|
||||||
if (headers instanceof DefaultHttpHeaders) {
|
if (headers instanceof DefaultHttpHeaders) {
|
||||||
((DefaultHttpHeaders) headers).encode(buf);
|
((DefaultHttpHeaders) headers).encode(buf);
|
||||||
@ -1361,7 +1231,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void encode(CharSequence key, CharSequence value, ByteBuf buf) {
|
private static void encode(CharSequence key, CharSequence value, ByteBuf buf) {
|
||||||
encodeAscii(key, buf);
|
encodeAscii(key, buf);
|
||||||
buf.writeBytes(HEADER_SEPERATOR);
|
buf.writeBytes(HEADER_SEPERATOR);
|
||||||
encodeAscii(value, buf);
|
encodeAscii(value, buf);
|
||||||
@ -1369,8 +1239,8 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void encodeAscii(CharSequence seq, ByteBuf buf) {
|
public static void encodeAscii(CharSequence seq, ByteBuf buf) {
|
||||||
if (seq instanceof HttpHeaderEntity) {
|
if (seq instanceof AsciiString) {
|
||||||
((HttpHeaderEntity) seq).encode(buf);
|
((AsciiString) seq).copy(0, buf, seq.length());
|
||||||
} else {
|
} else {
|
||||||
encodeAscii0(seq, buf);
|
encodeAscii0(seq, buf);
|
||||||
}
|
}
|
||||||
@ -1391,7 +1261,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
if (name == null) {
|
if (name == null) {
|
||||||
throw new NullPointerException("name");
|
throw new NullPointerException("name");
|
||||||
}
|
}
|
||||||
return new HttpHeaderEntity(name);
|
return new AsciiString(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpHeaders() { }
|
protected HttpHeaders() { }
|
||||||
@ -1621,14 +1491,14 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
/**
|
/**
|
||||||
* @see {@link #contains(CharSequence, CharSequence, boolean)}
|
* @see {@link #contains(CharSequence, CharSequence, boolean)}
|
||||||
*/
|
*/
|
||||||
public boolean contains(String name, String value, boolean ignoreCaseValue) {
|
public boolean contains(String name, String value, boolean ignoreCase) {
|
||||||
List<String> values = getAll(name);
|
List<String> values = getAll(name);
|
||||||
if (values.isEmpty()) {
|
if (values.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String v: values) {
|
for (String v: values) {
|
||||||
if (ignoreCaseValue) {
|
if (ignoreCase) {
|
||||||
if (equalsIgnoreCase(v, value)) {
|
if (equalsIgnoreCase(v, value)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1644,12 +1514,12 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
/**
|
/**
|
||||||
* Returns {@code true} if a header with the name and value exists.
|
* Returns {@code true} if a header with the name and value exists.
|
||||||
*
|
*
|
||||||
* @param name the headername
|
* @param name the headername
|
||||||
* @param value the value
|
* @param value the value
|
||||||
* @param ignoreCaseValue {@code true} if case should be ignored
|
* @param ignoreCase {@code true} if case should be ignored
|
||||||
* @return contains {@code true} if it contains it {@code false} otherwise
|
* @return contains {@code true} if it contains it {@code false} otherwise
|
||||||
*/
|
*/
|
||||||
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCaseValue) {
|
public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) {
|
||||||
return contains(name.toString(), value.toString(), ignoreCaseValue);
|
return contains(name.toString(), value.toString(), ignoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
|
import io.netty.handler.codec.TextHeaderProcessor;
|
||||||
|
|
||||||
|
final class HttpHeadersEncoder implements TextHeaderProcessor {
|
||||||
|
|
||||||
|
private final ByteBuf buf;
|
||||||
|
|
||||||
|
HttpHeadersEncoder(ByteBuf buf) {
|
||||||
|
this.buf = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean process(CharSequence name, CharSequence value) throws Exception {
|
||||||
|
final ByteBuf buf = this.buf;
|
||||||
|
final int nameLen = name.length();
|
||||||
|
final int valueLen = value.length();
|
||||||
|
final int entryLen = nameLen + valueLen + 4;
|
||||||
|
int offset = buf.writerIndex();
|
||||||
|
buf.ensureWritable(entryLen);
|
||||||
|
writeAscii(buf, offset, name, nameLen);
|
||||||
|
offset += nameLen;
|
||||||
|
buf.setByte(offset ++, ':');
|
||||||
|
buf.setByte(offset ++, ' ');
|
||||||
|
writeAscii(buf, offset, value, valueLen);
|
||||||
|
offset += valueLen;
|
||||||
|
buf.setByte(offset ++, '\r');
|
||||||
|
buf.setByte(offset ++, '\n');
|
||||||
|
buf.writerIndex(offset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) {
|
||||||
|
if (value instanceof AsciiString) {
|
||||||
|
writeAsciiString(buf, offset, (AsciiString) value, valueLen);
|
||||||
|
} else {
|
||||||
|
writeCharSequence(buf, offset, value, valueLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeAsciiString(ByteBuf buf, int offset, AsciiString value, int valueLen) {
|
||||||
|
value.copy(0, buf, offset, valueLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeCharSequence(ByteBuf buf, int offset, CharSequence value, int valueLen) {
|
||||||
|
for (int i = 0; i < valueLen; i ++) {
|
||||||
|
buf.setByte(offset ++, c2b(value.charAt(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int c2b(char ch) {
|
||||||
|
return ch < 256? (byte) ch : '?';
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ package io.netty.handler.codec.http;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpConstants.SP;
|
import static io.netty.handler.codec.http.HttpConstants.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The response code and its description of HTTP or its derived protocols, such as
|
* The response code and its description of HTTP or its derived protocols, such as
|
||||||
@ -453,6 +453,36 @@ public class HttpResponseStatus implements Comparable<HttpResponseStatus> {
|
|||||||
return new HttpResponseStatus(code, reasonPhrase + " (" + code + ')');
|
return new HttpResponseStatus(code, reasonPhrase + " (" + code + ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the specified HTTP status line into a {@link HttpResponseStatus}. The expected formats of the line are:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code statusCode} (e.g. 200)</li>
|
||||||
|
* <li>{@code statusCode} {@code reasonPhrase} (e.g. 404 Not Found)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if the specified status line is malformed
|
||||||
|
*/
|
||||||
|
public static HttpResponseStatus parseLine(CharSequence line) {
|
||||||
|
String status = line.toString();
|
||||||
|
try {
|
||||||
|
int space = status.indexOf(' ');
|
||||||
|
if (space == -1) {
|
||||||
|
return valueOf(Integer.parseInt(status));
|
||||||
|
} else {
|
||||||
|
int code = Integer.parseInt(status.substring(0, space));
|
||||||
|
String reasonPhrase = status.substring(space + 1);
|
||||||
|
HttpResponseStatus responseStatus = valueOf(code);
|
||||||
|
if (responseStatus.reasonPhrase().equals(reasonPhrase)) {
|
||||||
|
return responseStatus;
|
||||||
|
} else {
|
||||||
|
return new HttpResponseStatus(code, reasonPhrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("malformed status line: " + status, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
|
|
||||||
private final String reasonPhrase;
|
private final String reasonPhrase;
|
||||||
|
@ -15,365 +15,101 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.spdy;
|
package io.netty.handler.codec.spdy;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import io.netty.handler.codec.AsciiString;
|
||||||
import java.util.LinkedList;
|
import io.netty.handler.codec.DefaultTextHeaders;
|
||||||
import java.util.List;
|
import io.netty.handler.codec.TextHeaderProcessor;
|
||||||
import java.util.Map;
|
import io.netty.handler.codec.TextHeaders;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.NoSuchElementException;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
|
|
||||||
public class DefaultSpdyHeaders extends SpdyHeaders {
|
public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeaders {
|
||||||
|
@Override
|
||||||
private static final int BUCKET_SIZE = 17;
|
protected CharSequence convertName(CharSequence name) {
|
||||||
|
name = super.convertName(name);
|
||||||
private static int hash(String name) {
|
if (name instanceof AsciiString) {
|
||||||
int h = 0;
|
name = ((AsciiString) name).toLowerCase();
|
||||||
for (int i = name.length() - 1; i >= 0; i --) {
|
|
||||||
char c = name.charAt(i);
|
|
||||||
if (c >= 'A' && c <= 'Z') {
|
|
||||||
c += 32;
|
|
||||||
}
|
|
||||||
h = 31 * h + c;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (h > 0) {
|
|
||||||
return h;
|
|
||||||
} else if (h == Integer.MIN_VALUE) {
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
} else {
|
} else {
|
||||||
return -h;
|
name = name.toString().toLowerCase(Locale.US);
|
||||||
}
|
}
|
||||||
}
|
SpdyCodecUtil.validateHeaderName(name);
|
||||||
|
return name;
|
||||||
private static boolean eq(String name1, String name2) {
|
|
||||||
int nameLen = name1.length();
|
|
||||||
if (nameLen != name2.length()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = nameLen - 1; i >= 0; i --) {
|
|
||||||
char c1 = name1.charAt(i);
|
|
||||||
char c2 = name2.charAt(i);
|
|
||||||
if (c1 != c2) {
|
|
||||||
if (c1 >= 'A' && c1 <= 'Z') {
|
|
||||||
c1 += 32;
|
|
||||||
}
|
|
||||||
if (c2 >= 'A' && c2 <= 'Z') {
|
|
||||||
c2 += 32;
|
|
||||||
}
|
|
||||||
if (c1 != c2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int index(int hash) {
|
|
||||||
return hash % BUCKET_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
|
|
||||||
private final HeaderEntry head = new HeaderEntry(-1, null, null);
|
|
||||||
|
|
||||||
DefaultSpdyHeaders() {
|
|
||||||
head.before = head.after = head;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpdyHeaders add(final String name, final Object value) {
|
protected CharSequence convertValue(Object value) {
|
||||||
String lowerCaseName = name.toLowerCase();
|
if (value == null) {
|
||||||
SpdyCodecUtil.validateHeaderName(lowerCaseName);
|
throw new NullPointerException("value");
|
||||||
String strVal = toString(value);
|
}
|
||||||
SpdyCodecUtil.validateHeaderValue(strVal);
|
|
||||||
int h = hash(lowerCaseName);
|
|
||||||
int i = index(h);
|
|
||||||
add0(h, i, lowerCaseName, strVal);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add0(int h, int i, final String name, final String value) {
|
CharSequence seq;
|
||||||
// Update the hash table.
|
if (value instanceof CharSequence) {
|
||||||
HeaderEntry e = entries[i];
|
seq = (CharSequence) value;
|
||||||
HeaderEntry newEntry;
|
} else {
|
||||||
entries[i] = newEntry = new HeaderEntry(h, name, value);
|
seq = value.toString();
|
||||||
newEntry.next = e;
|
}
|
||||||
|
|
||||||
// Update the linked list.
|
SpdyCodecUtil.validateHeaderValue(seq);
|
||||||
newEntry.addBefore(head);
|
return seq;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpdyHeaders remove(final String name) {
|
public SpdyHeaders add(CharSequence name, Object value) {
|
||||||
if (name == null) {
|
super.add(name, value);
|
||||||
throw new NullPointerException("name");
|
|
||||||
}
|
|
||||||
String lowerCaseName = name.toLowerCase();
|
|
||||||
int h = hash(lowerCaseName);
|
|
||||||
int i = index(h);
|
|
||||||
remove0(h, i, lowerCaseName);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void remove0(int h, int i, String name) {
|
|
||||||
HeaderEntry e = entries[i];
|
|
||||||
if (e == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
if (e.hash == h && eq(name, e.key)) {
|
|
||||||
e.remove();
|
|
||||||
HeaderEntry next = e.next;
|
|
||||||
if (next != null) {
|
|
||||||
entries[i] = next;
|
|
||||||
e = next;
|
|
||||||
} else {
|
|
||||||
entries[i] = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
HeaderEntry next = e.next;
|
|
||||||
if (next == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (next.hash == h && eq(name, next.key)) {
|
|
||||||
e.next = next.next;
|
|
||||||
next.remove();
|
|
||||||
} else {
|
|
||||||
e = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpdyHeaders set(final String name, final Object value) {
|
|
||||||
String lowerCaseName = name.toLowerCase();
|
|
||||||
SpdyCodecUtil.validateHeaderName(lowerCaseName);
|
|
||||||
String strVal = toString(value);
|
|
||||||
SpdyCodecUtil.validateHeaderValue(strVal);
|
|
||||||
int h = hash(lowerCaseName);
|
|
||||||
int i = index(h);
|
|
||||||
remove0(h, i, lowerCaseName);
|
|
||||||
add0(h, i, lowerCaseName, strVal);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpdyHeaders set(final String name, final Iterable<?> values) {
|
public SpdyHeaders add(CharSequence name, Iterable<?> values) {
|
||||||
if (values == null) {
|
super.add(name, values);
|
||||||
throw new NullPointerException("values");
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
String lowerCaseName = name.toLowerCase();
|
@Override
|
||||||
SpdyCodecUtil.validateHeaderName(lowerCaseName);
|
public SpdyHeaders add(CharSequence name, Object... values) {
|
||||||
|
super.add(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
int h = hash(lowerCaseName);
|
@Override
|
||||||
int i = index(h);
|
public SpdyHeaders add(TextHeaders headers) {
|
||||||
|
super.add(headers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
remove0(h, i, lowerCaseName);
|
@Override
|
||||||
for (Object v: values) {
|
public SpdyHeaders set(CharSequence name, Object value) {
|
||||||
if (v == null) {
|
super.set(name, value);
|
||||||
break;
|
return this;
|
||||||
}
|
}
|
||||||
String strVal = toString(v);
|
|
||||||
SpdyCodecUtil.validateHeaderValue(strVal);
|
@Override
|
||||||
add0(h, i, lowerCaseName, strVal);
|
public SpdyHeaders set(CharSequence name, Object... values) {
|
||||||
}
|
super.set(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpdyHeaders set(CharSequence name, Iterable<?> values) {
|
||||||
|
super.set(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpdyHeaders set(TextHeaders headers) {
|
||||||
|
super.set(headers);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SpdyHeaders clear() {
|
public SpdyHeaders clear() {
|
||||||
for (int i = 0; i < entries.length; i ++) {
|
super.clear();
|
||||||
entries[i] = null;
|
|
||||||
}
|
|
||||||
head.before = head.after = head;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get(final String name) {
|
public SpdyHeaders forEachEntry(TextHeaderProcessor processor) {
|
||||||
if (name == null) {
|
super.forEachEntry(processor);
|
||||||
throw new NullPointerException("name");
|
|
||||||
}
|
|
||||||
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
HeaderEntry e = entries[i];
|
|
||||||
while (e != null) {
|
|
||||||
if (e.hash == h && eq(name, e.key)) {
|
|
||||||
return e.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
e = e.next;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getAll(final String name) {
|
|
||||||
if (name == null) {
|
|
||||||
throw new NullPointerException("name");
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkedList<String> values = new LinkedList<String>();
|
|
||||||
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
HeaderEntry e = entries[i];
|
|
||||||
while (e != null) {
|
|
||||||
if (e.hash == h && eq(name, e.key)) {
|
|
||||||
values.addFirst(e.value);
|
|
||||||
}
|
|
||||||
e = e.next;
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Map.Entry<String, String>> entries() {
|
|
||||||
List<Map.Entry<String, String>> all =
|
|
||||||
new LinkedList<Map.Entry<String, String>>();
|
|
||||||
|
|
||||||
HeaderEntry e = head.after;
|
|
||||||
while (e != head) {
|
|
||||||
all.add(e);
|
|
||||||
e = e.after;
|
|
||||||
}
|
|
||||||
return all;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<Map.Entry<String, String>> iterator() {
|
|
||||||
return new HeaderIterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean contains(String name) {
|
|
||||||
return get(name) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> names() {
|
|
||||||
Set<String> names = new TreeSet<String>();
|
|
||||||
|
|
||||||
HeaderEntry e = head.after;
|
|
||||||
while (e != head) {
|
|
||||||
names.add(e.key);
|
|
||||||
e = e.after;
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpdyHeaders add(String name, Iterable<?> values) {
|
|
||||||
SpdyCodecUtil.validateHeaderValue(name);
|
|
||||||
int h = hash(name);
|
|
||||||
int i = index(h);
|
|
||||||
for (Object v: values) {
|
|
||||||
String vstr = toString(v);
|
|
||||||
SpdyCodecUtil.validateHeaderValue(vstr);
|
|
||||||
add0(h, i, name, vstr);
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return head == head.after;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String toString(Object value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class HeaderIterator implements Iterator<Map.Entry<String, String>> {
|
|
||||||
|
|
||||||
private HeaderEntry current = head;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return current.after != head;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Entry<String, String> next() {
|
|
||||||
current = current.after;
|
|
||||||
|
|
||||||
if (current == head) {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class HeaderEntry implements Map.Entry<String, String> {
|
|
||||||
final int hash;
|
|
||||||
final String key;
|
|
||||||
String value;
|
|
||||||
HeaderEntry next;
|
|
||||||
HeaderEntry before, after;
|
|
||||||
|
|
||||||
HeaderEntry(int hash, String key, String value) {
|
|
||||||
this.hash = hash;
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove() {
|
|
||||||
before.after = after;
|
|
||||||
after.before = before;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addBefore(HeaderEntry e) {
|
|
||||||
after = e;
|
|
||||||
before = e.before;
|
|
||||||
before.after = this;
|
|
||||||
after.before = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getKey() {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String setValue(String value) {
|
|
||||||
if (value == null) {
|
|
||||||
throw new NullPointerException("value");
|
|
||||||
}
|
|
||||||
SpdyCodecUtil.validateHeaderValue(value);
|
|
||||||
String oldValue = this.value;
|
|
||||||
this.value = value;
|
|
||||||
return oldValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return key + '=' + value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -285,11 +285,11 @@ final class SpdyCodecUtil {
|
|||||||
/**
|
/**
|
||||||
* Validate a SPDY header name.
|
* Validate a SPDY header name.
|
||||||
*/
|
*/
|
||||||
static void validateHeaderName(String name) {
|
static void validateHeaderName(CharSequence name) {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
throw new NullPointerException("name");
|
throw new NullPointerException("name");
|
||||||
}
|
}
|
||||||
if (name.isEmpty()) {
|
if (name.length() == 0) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"name cannot be length zero");
|
"name cannot be length zero");
|
||||||
}
|
}
|
||||||
@ -315,7 +315,7 @@ final class SpdyCodecUtil {
|
|||||||
/**
|
/**
|
||||||
* Validate a SPDY header value. Does not validate max length.
|
* Validate a SPDY header value. Does not validate max length.
|
||||||
*/
|
*/
|
||||||
static void validateHeaderValue(String value) {
|
static void validateHeaderValue(CharSequence value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
throw new NullPointerException("value");
|
throw new NullPointerException("value");
|
||||||
}
|
}
|
||||||
|
@ -15,399 +15,75 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.spdy;
|
package io.netty.handler.codec.spdy;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.TextHeaderProcessor;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.TextHeaders;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the constants for the standard SPDY HTTP header names and commonly
|
* Provides the constants for the standard SPDY HTTP header names and commonly
|
||||||
* used utility methods that access a {@link SpdyHeadersFrame}.
|
* used utility methods that access a {@link SpdyHeadersFrame}.
|
||||||
*/
|
*/
|
||||||
public abstract class SpdyHeaders implements Iterable<Map.Entry<String, String>> {
|
public interface SpdyHeaders extends TextHeaders {
|
||||||
|
|
||||||
public static final SpdyHeaders EMPTY_HEADERS = new SpdyHeaders() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getAll(String name) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Map.Entry<String, String>> entries() {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean contains(String name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> names() {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpdyHeaders add(String name, Object value) {
|
|
||||||
throw new UnsupportedOperationException("read only");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpdyHeaders add(String name, Iterable<?> values) {
|
|
||||||
throw new UnsupportedOperationException("read only");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpdyHeaders set(String name, Object value) {
|
|
||||||
throw new UnsupportedOperationException("read only");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpdyHeaders set(String name, Iterable<?> values) {
|
|
||||||
throw new UnsupportedOperationException("read only");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpdyHeaders remove(String name) {
|
|
||||||
throw new UnsupportedOperationException("read only");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SpdyHeaders clear() {
|
|
||||||
throw new UnsupportedOperationException("read only");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<Map.Entry<String, String>> iterator() {
|
|
||||||
return entries().iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String get(String name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SPDY HTTP header names
|
* SPDY HTTP header names
|
||||||
*/
|
*/
|
||||||
public static final class HttpNames {
|
final class HttpNames {
|
||||||
/**
|
/**
|
||||||
* {@code ":host"}
|
* {@code ":host"}
|
||||||
*/
|
*/
|
||||||
public static final String HOST = ":host";
|
public static final AsciiString HOST = new AsciiString(":host");
|
||||||
/**
|
/**
|
||||||
* {@code ":method"}
|
* {@code ":method"}
|
||||||
*/
|
*/
|
||||||
public static final String METHOD = ":method";
|
public static final AsciiString METHOD = new AsciiString(":method");
|
||||||
/**
|
/**
|
||||||
* {@code ":path"}
|
* {@code ":path"}
|
||||||
*/
|
*/
|
||||||
public static final String PATH = ":path";
|
public static final AsciiString PATH = new AsciiString(":path");
|
||||||
/**
|
/**
|
||||||
* {@code ":scheme"}
|
* {@code ":scheme"}
|
||||||
*/
|
*/
|
||||||
public static final String SCHEME = ":scheme";
|
public static final AsciiString SCHEME = new AsciiString(":scheme");
|
||||||
/**
|
/**
|
||||||
* {@code ":status"}
|
* {@code ":status"}
|
||||||
*/
|
*/
|
||||||
public static final String STATUS = ":status";
|
public static final AsciiString STATUS = new AsciiString(":status");
|
||||||
/**
|
/**
|
||||||
* {@code ":version"}
|
* {@code ":version"}
|
||||||
*/
|
*/
|
||||||
public static final String VERSION = ":version";
|
public static final AsciiString VERSION = new AsciiString(":version");
|
||||||
|
|
||||||
private HttpNames() { }
|
private HttpNames() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the header value with the specified header name. If there are
|
SpdyHeaders add(CharSequence name, Object value);
|
||||||
* more than one header value for the specified header name, the first
|
|
||||||
* value is returned.
|
|
||||||
*
|
|
||||||
* @return the header value or {@code null} if there is no such header
|
|
||||||
*/
|
|
||||||
public static String getHeader(SpdyHeadersFrame frame, String name) {
|
|
||||||
return frame.headers().get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the header value with the specified header name. If there are
|
|
||||||
* more than one header value for the specified header name, the first
|
|
||||||
* value is returned.
|
|
||||||
*
|
|
||||||
* @return the header value or the {@code defaultValue} if there is no such
|
|
||||||
* header
|
|
||||||
*/
|
|
||||||
public static String getHeader(SpdyHeadersFrame frame, String name, String defaultValue) {
|
|
||||||
String value = frame.headers().get(name);
|
|
||||||
if (value == null) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a new header with the specified name and value. If there is an
|
|
||||||
* existing header with the same name, the existing header is removed.
|
|
||||||
*/
|
|
||||||
public static void setHeader(SpdyHeadersFrame frame, String name, Object value) {
|
|
||||||
frame.headers().set(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a new header with the specified name and values. If there is an
|
|
||||||
* existing header with the same name, the existing header is removed.
|
|
||||||
*/
|
|
||||||
public static void setHeader(SpdyHeadersFrame frame, String name, Iterable<?> values) {
|
|
||||||
frame.headers().set(name, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new header with the specified name and value.
|
|
||||||
*/
|
|
||||||
public static void addHeader(SpdyHeadersFrame frame, String name, Object value) {
|
|
||||||
frame.headers().add(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the SPDY host header.
|
|
||||||
*/
|
|
||||||
public static void removeHost(SpdyHeadersFrame frame) {
|
|
||||||
frame.headers().remove(HttpNames.HOST);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the SPDY host header.
|
|
||||||
*/
|
|
||||||
public static String getHost(SpdyHeadersFrame frame) {
|
|
||||||
return frame.headers().get(HttpNames.HOST);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the SPDY host header.
|
|
||||||
*/
|
|
||||||
public static void setHost(SpdyHeadersFrame frame, String host) {
|
|
||||||
frame.headers().set(HttpNames.HOST, host);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the HTTP method header.
|
|
||||||
*/
|
|
||||||
public static void removeMethod(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
frame.headers().remove(HttpNames.METHOD);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link HttpMethod} represented by the HTTP method header.
|
|
||||||
*/
|
|
||||||
public static HttpMethod getMethod(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
try {
|
|
||||||
return HttpMethod.valueOf(frame.headers().get(HttpNames.METHOD));
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the HTTP method header.
|
|
||||||
*/
|
|
||||||
public static void setMethod(int spdyVersion, SpdyHeadersFrame frame, HttpMethod method) {
|
|
||||||
frame.headers().set(HttpNames.METHOD, method.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the URL scheme header.
|
|
||||||
*/
|
|
||||||
public static void removeScheme(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
frame.headers().remove(HttpNames.SCHEME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the URL scheme header.
|
|
||||||
*/
|
|
||||||
public static String getScheme(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
return frame.headers().get(HttpNames.SCHEME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the URL scheme header.
|
|
||||||
*/
|
|
||||||
public static void setScheme(int spdyVersion, SpdyHeadersFrame frame, String scheme) {
|
|
||||||
frame.headers().set(HttpNames.SCHEME, scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the HTTP response status header.
|
|
||||||
*/
|
|
||||||
public static void removeStatus(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
frame.headers().remove(HttpNames.STATUS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link HttpResponseStatus} represented by the HTTP response status header.
|
|
||||||
*/
|
|
||||||
public static HttpResponseStatus getStatus(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
try {
|
|
||||||
String status = frame.headers().get(HttpNames.STATUS);
|
|
||||||
int space = status.indexOf(' ');
|
|
||||||
if (space == -1) {
|
|
||||||
return HttpResponseStatus.valueOf(Integer.parseInt(status));
|
|
||||||
} else {
|
|
||||||
int code = Integer.parseInt(status.substring(0, space));
|
|
||||||
String reasonPhrase = status.substring(space + 1);
|
|
||||||
HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(code);
|
|
||||||
if (responseStatus.reasonPhrase().equals(reasonPhrase)) {
|
|
||||||
return responseStatus;
|
|
||||||
} else {
|
|
||||||
return new HttpResponseStatus(code, reasonPhrase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the HTTP response status header.
|
|
||||||
*/
|
|
||||||
public static void setStatus(int spdyVersion, SpdyHeadersFrame frame, HttpResponseStatus status) {
|
|
||||||
frame.headers().set(HttpNames.STATUS, status.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the URL path header.
|
|
||||||
*/
|
|
||||||
public static void removeUrl(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
frame.headers().remove(HttpNames.PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the URL path header.
|
|
||||||
*/
|
|
||||||
public static String getUrl(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
return frame.headers().get(HttpNames.PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the URL path header.
|
|
||||||
*/
|
|
||||||
public static void setUrl(int spdyVersion, SpdyHeadersFrame frame, String path) {
|
|
||||||
frame.headers().set(HttpNames.PATH, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the HTTP version header.
|
|
||||||
*/
|
|
||||||
public static void removeVersion(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
frame.headers().remove(HttpNames.VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link HttpVersion} represented by the HTTP version header.
|
|
||||||
*/
|
|
||||||
public static HttpVersion getVersion(int spdyVersion, SpdyHeadersFrame frame) {
|
|
||||||
try {
|
|
||||||
return HttpVersion.valueOf(frame.headers().get(HttpNames.VERSION));
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the HTTP version header.
|
|
||||||
*/
|
|
||||||
public static void setVersion(int spdyVersion, SpdyHeadersFrame frame, HttpVersion httpVersion) {
|
|
||||||
frame.headers().set(HttpNames.VERSION, httpVersion.text());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Map.Entry<String, String>> iterator() {
|
SpdyHeaders add(CharSequence name, Iterable<?> values);
|
||||||
return entries().iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the header value with the specified header name. If there is
|
SpdyHeaders add(CharSequence name, Object... values);
|
||||||
* more than one header value for the specified header name, the first
|
|
||||||
* value is returned.
|
|
||||||
*
|
|
||||||
* @return the header value or {@code null} if there is no such header
|
|
||||||
*/
|
|
||||||
public abstract String get(String name);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the header values with the specified header name.
|
SpdyHeaders add(TextHeaders headers);
|
||||||
*
|
|
||||||
* @return the {@link List} of header values. An empty list if there is no
|
|
||||||
* such header.
|
|
||||||
*/
|
|
||||||
public abstract List<String> getAll(String name);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns all header names and values that this frame contains.
|
SpdyHeaders set(CharSequence name, Object value);
|
||||||
*
|
|
||||||
* @return the {@link List} of the header name-value pairs. An empty list
|
|
||||||
* if there is no header in this message.
|
|
||||||
*/
|
|
||||||
public abstract List<Map.Entry<String, String>> entries();
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns {@code true} if and only if there is a header with the specified
|
SpdyHeaders set(CharSequence name, Iterable<?> values);
|
||||||
* header name.
|
|
||||||
*/
|
|
||||||
public abstract boolean contains(String name);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Returns the {@link Set} of all header names that this frame contains.
|
SpdyHeaders set(CharSequence name, Object... values);
|
||||||
*/
|
|
||||||
public abstract Set<String> names();
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Adds a new header with the specified name and value.
|
SpdyHeaders set(TextHeaders headers);
|
||||||
*/
|
|
||||||
public abstract SpdyHeaders add(String name, Object value);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Adds a new header with the specified name and values. If there is an
|
SpdyHeaders clear();
|
||||||
* existing header with the same name, the existing header is removed.
|
|
||||||
*/
|
|
||||||
public abstract SpdyHeaders add(String name, Iterable<?> values);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Sets a new header with the specified name and value. If there is an
|
SpdyHeaders forEachEntry(TextHeaderProcessor processor);
|
||||||
* existing header with the same name, the existing header is removed.
|
|
||||||
*/
|
|
||||||
public abstract SpdyHeaders set(String name, Object value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a new header with the specified name and values. If there is an
|
|
||||||
* existing header with the same name, the existing header is removed.
|
|
||||||
*/
|
|
||||||
public abstract SpdyHeaders set(String name, Iterable<?> values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the header with the specified name.
|
|
||||||
*/
|
|
||||||
public abstract SpdyHeaders remove(String name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all headers from this frame.
|
|
||||||
*/
|
|
||||||
public abstract SpdyHeaders clear();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if no header exists.
|
|
||||||
*/
|
|
||||||
public abstract boolean isEmpty();
|
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,8 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s,
|
* Decodes {@link SpdySynStreamFrame}s, {@link SpdySynReplyFrame}s,
|
||||||
* and {@link SpdyDataFrame}s into {@link FullHttpRequest}s and {@link FullHttpResponse}s.
|
* and {@link SpdyDataFrame}s into {@link FullHttpRequest}s and {@link FullHttpResponse}s.
|
||||||
@ -111,7 +113,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String URL = SpdyHeaders.getUrl(spdyVersion, spdySynStreamFrame);
|
String URL = spdySynStreamFrame.headers().get(PATH);
|
||||||
|
|
||||||
// If a client receives a SYN_STREAM without a 'url' header
|
// If a client receives a SYN_STREAM without a 'url' header
|
||||||
// it must reply with a RST_STREAM with error code PROTOCOL_ERROR
|
// it must reply with a RST_STREAM with error code PROTOCOL_ERROR
|
||||||
@ -148,7 +150,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
// Response body will follow in a series of Data Frames
|
// Response body will follow in a series of Data Frames
|
||||||
putMessage(streamId, httpResponseWithEntity);
|
putMessage(streamId, httpResponseWithEntity);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) {
|
||||||
SpdyRstStreamFrame spdyRstStreamFrame =
|
SpdyRstStreamFrame spdyRstStreamFrame =
|
||||||
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
new DefaultSpdyRstStreamFrame(streamId, SpdyStreamStatus.PROTOCOL_ERROR);
|
||||||
ctx.writeAndFlush(spdyRstStreamFrame);
|
ctx.writeAndFlush(spdyRstStreamFrame);
|
||||||
@ -161,10 +163,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
if (spdySynStreamFrame.isTruncated()) {
|
if (spdySynStreamFrame.isTruncated()) {
|
||||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||||
spdySynReplyFrame.setLast(true);
|
spdySynReplyFrame.setLast(true);
|
||||||
SpdyHeaders.setStatus(spdyVersion,
|
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
|
||||||
spdySynReplyFrame,
|
frameHeaders.set(STATUS, HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE);
|
||||||
HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE);
|
frameHeaders.set(VERSION, HttpVersion.HTTP_1_0);
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
|
|
||||||
ctx.writeAndFlush(spdySynReplyFrame);
|
ctx.writeAndFlush(spdySynReplyFrame);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -187,8 +188,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
// Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
|
// Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid
|
||||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||||
spdySynReplyFrame.setLast(true);
|
spdySynReplyFrame.setLast(true);
|
||||||
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, HttpResponseStatus.BAD_REQUEST);
|
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, HttpVersion.HTTP_1_0);
|
frameHeaders.set(STATUS, HttpResponseStatus.BAD_REQUEST);
|
||||||
|
frameHeaders.set(VERSION, HttpVersion.HTTP_1_0);
|
||||||
ctx.writeAndFlush(spdySynReplyFrame);
|
ctx.writeAndFlush(spdySynReplyFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,22 +293,23 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
private static FullHttpRequest createHttpRequest(int spdyVersion, SpdyHeadersFrame requestFrame)
|
private static FullHttpRequest createHttpRequest(int spdyVersion, SpdyHeadersFrame requestFrame)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// Create the first line of the request from the name/value pairs
|
// Create the first line of the request from the name/value pairs
|
||||||
HttpMethod method = SpdyHeaders.getMethod(spdyVersion, requestFrame);
|
SpdyHeaders headers = requestFrame.headers();
|
||||||
String url = SpdyHeaders.getUrl(spdyVersion, requestFrame);
|
HttpMethod method = HttpMethod.valueOf(headers.get(METHOD));
|
||||||
HttpVersion httpVersion = SpdyHeaders.getVersion(spdyVersion, requestFrame);
|
String url = headers.get(PATH);
|
||||||
SpdyHeaders.removeMethod(spdyVersion, requestFrame);
|
HttpVersion httpVersion = HttpVersion.valueOf(headers.get(VERSION));
|
||||||
SpdyHeaders.removeUrl(spdyVersion, requestFrame);
|
headers.remove(METHOD);
|
||||||
SpdyHeaders.removeVersion(spdyVersion, requestFrame);
|
headers.remove(PATH);
|
||||||
|
headers.remove(VERSION);
|
||||||
|
|
||||||
FullHttpRequest req = new DefaultFullHttpRequest(httpVersion, method, url);
|
FullHttpRequest req = new DefaultFullHttpRequest(httpVersion, method, url);
|
||||||
|
|
||||||
// Remove the scheme header
|
// Remove the scheme header
|
||||||
SpdyHeaders.removeScheme(spdyVersion, requestFrame);
|
headers.remove(SCHEME);
|
||||||
|
|
||||||
if (spdyVersion >= 3) {
|
if (spdyVersion >= 3) {
|
||||||
// Replace the SPDY host header with the HTTP host header
|
// Replace the SPDY host header with the HTTP host header
|
||||||
String host = SpdyHeaders.getHost(requestFrame);
|
String host = headers.get(HOST);
|
||||||
SpdyHeaders.removeHost(requestFrame);
|
headers.remove(HOST);
|
||||||
HttpHeaders.setHost(req, host);
|
HttpHeaders.setHost(req, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,13 +326,15 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder<SpdyFrame> {
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FullHttpResponse createHttpResponse(int spdyVersion, SpdyHeadersFrame responseFrame)
|
private static FullHttpResponse createHttpResponse(
|
||||||
throws Exception {
|
int spdyVersion, SpdyHeadersFrame responseFrame) throws Exception {
|
||||||
|
|
||||||
// Create the first line of the response from the name/value pairs
|
// Create the first line of the response from the name/value pairs
|
||||||
HttpResponseStatus status = SpdyHeaders.getStatus(spdyVersion, responseFrame);
|
SpdyHeaders headers = responseFrame.headers();
|
||||||
HttpVersion version = SpdyHeaders.getVersion(spdyVersion, responseFrame);
|
HttpResponseStatus status = HttpResponseStatus.parseLine(headers.get(STATUS));
|
||||||
SpdyHeaders.removeStatus(spdyVersion, responseFrame);
|
HttpVersion version = HttpVersion.valueOf(headers.get(VERSION));
|
||||||
SpdyHeaders.removeVersion(spdyVersion, responseFrame);
|
headers.remove(STATUS);
|
||||||
|
headers.remove(VERSION);
|
||||||
|
|
||||||
FullHttpResponse res = new DefaultFullHttpResponse(version, status);
|
FullHttpResponse res = new DefaultFullHttpResponse(version, status);
|
||||||
for (Map.Entry<String, String> e: responseFrame.headers()) {
|
for (Map.Entry<String, String> e: responseFrame.headers()) {
|
||||||
|
@ -31,6 +31,8 @@ import io.netty.handler.codec.http.LastHttpContent;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.spdy.SpdyHeaders.HttpNames.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpContent}s
|
* Encodes {@link HttpRequest}s, {@link HttpResponse}s, and {@link HttpContent}s
|
||||||
* into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s.
|
* into {@link SpdySynStreamFrame}s and {@link SpdySynReplyFrame}s.
|
||||||
@ -227,17 +229,18 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
|||||||
new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority);
|
new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority);
|
||||||
|
|
||||||
// Unfold the first line of the message into name/value pairs
|
// Unfold the first line of the message into name/value pairs
|
||||||
|
SpdyHeaders frameHeaders = spdySynStreamFrame.headers();
|
||||||
if (httpMessage instanceof FullHttpRequest) {
|
if (httpMessage instanceof FullHttpRequest) {
|
||||||
HttpRequest httpRequest = (HttpRequest) httpMessage;
|
HttpRequest httpRequest = (HttpRequest) httpMessage;
|
||||||
SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod());
|
frameHeaders.set(METHOD, httpRequest.getMethod());
|
||||||
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri());
|
frameHeaders.set(PATH, httpRequest.getUri());
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
|
frameHeaders.set(VERSION, httpMessage.getProtocolVersion());
|
||||||
}
|
}
|
||||||
if (httpMessage instanceof HttpResponse) {
|
if (httpMessage instanceof HttpResponse) {
|
||||||
HttpResponse httpResponse = (HttpResponse) httpMessage;
|
HttpResponse httpResponse = (HttpResponse) httpMessage;
|
||||||
SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus());
|
frameHeaders.set(STATUS, httpResponse.getStatus());
|
||||||
SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL);
|
frameHeaders.set(PATH, URL);
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
|
frameHeaders.set(VERSION, httpMessage.getProtocolVersion());
|
||||||
spdySynStreamFrame.setUnidirectional(true);
|
spdySynStreamFrame.setUnidirectional(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,18 +248,18 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
|||||||
if (spdyVersion >= 3) {
|
if (spdyVersion >= 3) {
|
||||||
String host = HttpHeaders.getHost(httpMessage);
|
String host = HttpHeaders.getHost(httpMessage);
|
||||||
httpMessage.headers().remove(HttpHeaders.Names.HOST);
|
httpMessage.headers().remove(HttpHeaders.Names.HOST);
|
||||||
SpdyHeaders.setHost(spdySynStreamFrame, host);
|
frameHeaders.set(HOST, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the SPDY scheme header
|
// Set the SPDY scheme header
|
||||||
if (scheme == null) {
|
if (scheme == null) {
|
||||||
scheme = "https";
|
scheme = "https";
|
||||||
}
|
}
|
||||||
SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme);
|
frameHeaders.set(SCHEME, scheme);
|
||||||
|
|
||||||
// Transfer the remaining HTTP headers
|
// Transfer the remaining HTTP headers
|
||||||
for (Map.Entry<String, String> entry: httpMessage.headers()) {
|
for (Map.Entry<String, String> entry: httpMessage.headers()) {
|
||||||
spdySynStreamFrame.headers().add(entry.getKey(), entry.getValue());
|
frameHeaders.add(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
currentStreamId = spdySynStreamFrame.getStreamId();
|
currentStreamId = spdySynStreamFrame.getStreamId();
|
||||||
spdySynStreamFrame.setLast(isLast(httpMessage));
|
spdySynStreamFrame.setLast(isLast(httpMessage));
|
||||||
@ -278,10 +281,10 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
|
|||||||
httpResponse.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
httpResponse.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);
|
||||||
|
|
||||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
||||||
|
SpdyHeaders frameHeaders = spdySynReplyFrame.headers();
|
||||||
// Unfold the first line of the response into name/value pairs
|
// Unfold the first line of the response into name/value pairs
|
||||||
SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus());
|
frameHeaders.set(STATUS, httpResponse.getStatus());
|
||||||
SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion());
|
frameHeaders.set(VERSION, httpResponse.getProtocolVersion());
|
||||||
|
|
||||||
// Transfer the remaining HTTP headers
|
// Transfer the remaining HTTP headers
|
||||||
for (Map.Entry<String, String> entry: httpResponse.headers()) {
|
for (Map.Entry<String, String> entry: httpResponse.headers()) {
|
||||||
|
@ -15,13 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http;
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class HttpHeadersTest {
|
public class HttpHeadersTest {
|
||||||
|
|
||||||
@ -29,8 +28,9 @@ public class HttpHeadersTest {
|
|||||||
public void testRemoveTransferEncodingIgnoreCase() {
|
public void testRemoveTransferEncodingIgnoreCase() {
|
||||||
HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
|
HttpMessage message = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
|
||||||
message.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, "Chunked");
|
message.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, "Chunked");
|
||||||
|
assertFalse(message.headers().isEmpty());
|
||||||
HttpHeaders.removeTransferEncodingChunked(message);
|
HttpHeaders.removeTransferEncodingChunked(message);
|
||||||
Assert.assertTrue(message.headers().isEmpty());
|
assertTrue(message.headers().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for https://github.com/netty/netty/issues/1690
|
// Test for https://github.com/netty/netty/issues/1690
|
||||||
@ -40,12 +40,12 @@ public class HttpHeadersTest {
|
|||||||
headers.add("Foo", "1");
|
headers.add("Foo", "1");
|
||||||
headers.add("Foo", "2");
|
headers.add("Foo", "2");
|
||||||
|
|
||||||
Assert.assertEquals("1", headers.get("Foo"));
|
assertEquals("1", headers.get("Foo"));
|
||||||
|
|
||||||
List<String> values = headers.getAll("Foo");
|
List<String> values = headers.getAll("Foo");
|
||||||
Assert.assertEquals(2, values.size());
|
assertEquals(2, values.size());
|
||||||
Assert.assertEquals("1", values.get(0));
|
assertEquals("1", values.get(0));
|
||||||
Assert.assertEquals("2", values.get(1));
|
assertEquals("2", values.get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.handler.codec.stomp;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.DefaultTextHeaders;
|
||||||
|
import io.netty.handler.codec.TextHeaderProcessor;
|
||||||
|
import io.netty.handler.codec.TextHeaders;
|
||||||
|
|
||||||
|
public class DefaultStompHeaders extends DefaultTextHeaders implements StompHeaders {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders add(CharSequence name, Object value) {
|
||||||
|
super.add(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders add(CharSequence name, Iterable<?> values) {
|
||||||
|
super.add(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders add(CharSequence name, Object... values) {
|
||||||
|
super.add(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders add(TextHeaders headers) {
|
||||||
|
super.add(headers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders set(CharSequence name, Object value) {
|
||||||
|
super.set(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders set(CharSequence name, Object... values) {
|
||||||
|
super.set(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders set(CharSequence name, Iterable<?> values) {
|
||||||
|
super.set(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders set(TextHeaders headers) {
|
||||||
|
super.set(headers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders clear() {
|
||||||
|
super.clear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StompHeaders forEachEntry(TextHeaderProcessor processor) {
|
||||||
|
super.forEachEntry(processor);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ public class DefaultStompHeadersSubframe implements StompHeadersSubframe {
|
|||||||
|
|
||||||
protected final StompCommand command;
|
protected final StompCommand command;
|
||||||
protected DecoderResult decoderResult = DecoderResult.SUCCESS;
|
protected DecoderResult decoderResult = DecoderResult.SUCCESS;
|
||||||
protected final StompHeaders headers = new StompHeaders();
|
protected final StompHeaders headers = new DefaultStompHeaders();
|
||||||
|
|
||||||
public DefaultStompHeadersSubframe(StompCommand command) {
|
public DefaultStompHeadersSubframe(StompCommand command) {
|
||||||
if (command == null) {
|
if (command == null) {
|
||||||
|
@ -15,93 +15,63 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.stomp;
|
package io.netty.handler.codec.stomp;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import io.netty.handler.codec.AsciiString;
|
||||||
import java.util.Arrays;
|
import io.netty.handler.codec.TextHeaderProcessor;
|
||||||
import java.util.LinkedHashMap;
|
import io.netty.handler.codec.TextHeaders;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the constants for the standard STOMP header names and values and
|
* The multimap data structure for the STOMP header names and values. It also provides the constants for the standard
|
||||||
* commonly used utility methods that accesses an {@link StompHeadersSubframe}.
|
* STOMP header names and values.
|
||||||
*/
|
*/
|
||||||
public class StompHeaders {
|
public interface StompHeaders extends TextHeaders {
|
||||||
|
|
||||||
public static final String ACCEPT_VERSION = "accept-version";
|
AsciiString ACCEPT_VERSION = new AsciiString("accept-version");
|
||||||
public static final String HOST = "host";
|
AsciiString HOST = new AsciiString("host");
|
||||||
public static final String LOGIN = "login";
|
AsciiString LOGIN = new AsciiString("login");
|
||||||
public static final String PASSCODE = "passcode";
|
AsciiString PASSCODE = new AsciiString("passcode");
|
||||||
public static final String HEART_BEAT = "heart-beat";
|
AsciiString HEART_BEAT = new AsciiString("heart-beat");
|
||||||
public static final String VERSION = "version";
|
AsciiString VERSION = new AsciiString("version");
|
||||||
public static final String SESSION = "session";
|
AsciiString SESSION = new AsciiString("session");
|
||||||
public static final String SERVER = "server";
|
AsciiString SERVER = new AsciiString("server");
|
||||||
public static final String DESTINATION = "destination";
|
AsciiString DESTINATION = new AsciiString("destination");
|
||||||
public static final String ID = "id";
|
AsciiString ID = new AsciiString("id");
|
||||||
public static final String ACK = "ack";
|
AsciiString ACK = new AsciiString("ack");
|
||||||
public static final String TRANSACTION = "transaction";
|
AsciiString TRANSACTION = new AsciiString("transaction");
|
||||||
public static final String RECEIPT = "receipt";
|
AsciiString RECEIPT = new AsciiString("receipt");
|
||||||
public static final String MESSAGE_ID = "message-id";
|
AsciiString MESSAGE_ID = new AsciiString("message-id");
|
||||||
public static final String SUBSCRIPTION = "subscription";
|
AsciiString SUBSCRIPTION = new AsciiString("subscription");
|
||||||
public static final String RECEIPT_ID = "receipt-id";
|
AsciiString RECEIPT_ID = new AsciiString("receipt-id");
|
||||||
public static final String MESSAGE = "message";
|
AsciiString MESSAGE = new AsciiString("message");
|
||||||
public static final String CONTENT_LENGTH = "content-length";
|
AsciiString CONTENT_LENGTH = new AsciiString("content-length");
|
||||||
public static final String CONTENT_TYPE = "content-type";
|
AsciiString CONTENT_TYPE = new AsciiString("content-type");
|
||||||
|
|
||||||
private final Map<String, List<String>> headers = new LinkedHashMap<String, List<String>>();
|
|
||||||
|
|
||||||
public boolean has(String key) {
|
|
||||||
List<String> values = headers.get(key);
|
|
||||||
return values != null && !values.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String get(String key) {
|
|
||||||
List<String> values = headers.get(key);
|
|
||||||
if (values != null && !values.isEmpty()) {
|
|
||||||
return values.get(0);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(String key, String value) {
|
|
||||||
List<String> values = headers.get(key);
|
|
||||||
if (values == null) {
|
|
||||||
values = new ArrayList<String>();
|
|
||||||
headers.put(key, values);
|
|
||||||
}
|
|
||||||
values.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
|
|
||||||
public void set(String key, String value) {
|
|
||||||
headers.put(key, Arrays.asList(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getAll(String key) {
|
|
||||||
List<String> values = headers.get(key);
|
|
||||||
if (values != null) {
|
|
||||||
return new ArrayList<String>(values);
|
|
||||||
} else {
|
|
||||||
return new ArrayList<String>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> keySet() {
|
|
||||||
return headers.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
StompHeaders add(CharSequence name, Object value);
|
||||||
return "StompHeaders{" +
|
|
||||||
headers +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(StompHeaders headers) {
|
@Override
|
||||||
for (String key: headers.keySet()) {
|
StompHeaders add(CharSequence name, Iterable<?> values);
|
||||||
List<String> values = headers.getAll(key);
|
|
||||||
this.headers.put(key, values);
|
@Override
|
||||||
}
|
StompHeaders add(CharSequence name, Object... values);
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
StompHeaders add(TextHeaders headers);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
StompHeaders set(CharSequence name, Object value);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
StompHeaders set(CharSequence name, Iterable<?> values);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
StompHeaders set(CharSequence name, Object... values);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
StompHeaders set(TextHeaders headers);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
StompHeaders clear();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
StompHeaders forEachEntry(TextHeaderProcessor processor);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.stomp;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelPipeline;
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.MessageAggregator;
|
import io.netty.handler.codec.MessageAggregator;
|
||||||
import io.netty.handler.codec.TooLongFrameException;
|
import io.netty.handler.codec.TooLongFrameException;
|
||||||
|
|
||||||
@ -64,12 +65,17 @@ public class StompSubframeAggregator
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean hasContentLength(StompHeadersSubframe start) throws Exception {
|
protected boolean hasContentLength(StompHeadersSubframe start) throws Exception {
|
||||||
return start.headers().has(StompHeaders.CONTENT_LENGTH);
|
return start.headers().contains(StompHeaders.CONTENT_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected long contentLength(StompHeadersSubframe start) throws Exception {
|
protected long contentLength(StompHeadersSubframe start) throws Exception {
|
||||||
return Long.parseLong(start.headers().get(StompHeaders.CONTENT_LENGTH));
|
CharSequence value = start.headers().get(StompHeaders.CONTENT_LENGTH);
|
||||||
|
if (value instanceof AsciiString) {
|
||||||
|
return ((AsciiString) value).parseLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Long.parseLong(value.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.stomp;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.DecoderException;
|
import io.netty.handler.codec.DecoderException;
|
||||||
import io.netty.handler.codec.DecoderResult;
|
import io.netty.handler.codec.DecoderResult;
|
||||||
import io.netty.handler.codec.ReplayingDecoder;
|
import io.netty.handler.codec.ReplayingDecoder;
|
||||||
@ -199,7 +200,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
long contentLength = -1;
|
long contentLength = -1;
|
||||||
if (headers.has(StompHeaders.CONTENT_LENGTH)) {
|
if (headers.contains(StompHeaders.CONTENT_LENGTH)) {
|
||||||
contentLength = getContentLength(headers, 0);
|
contentLength = getContentLength(headers, 0);
|
||||||
} else {
|
} else {
|
||||||
int globalIndex = indexOf(buffer, buffer.readerIndex(),
|
int globalIndex = indexOf(buffer, buffer.readerIndex(),
|
||||||
@ -219,10 +220,14 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static long getContentLength(StompHeaders headers, long defaultValue) {
|
private static long getContentLength(StompHeaders headers, long defaultValue) {
|
||||||
String contentLength = headers.get(StompHeaders.CONTENT_LENGTH);
|
CharSequence contentLength = headers.get(StompHeaders.CONTENT_LENGTH);
|
||||||
if (contentLength != null) {
|
if (contentLength != null) {
|
||||||
try {
|
try {
|
||||||
return Long.parseLong(contentLength);
|
if (contentLength instanceof AsciiString) {
|
||||||
|
return ((AsciiString) contentLength).parseLong();
|
||||||
|
} else {
|
||||||
|
return Long.parseLong(contentLength.toString());
|
||||||
|
}
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,9 @@ package io.netty.handler.codec.stomp;
|
|||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.AsciiHeadersEncoder;
|
||||||
|
import io.netty.handler.codec.AsciiHeadersEncoder.NewlineType;
|
||||||
|
import io.netty.handler.codec.AsciiHeadersEncoder.SeparatorType;
|
||||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
@ -61,18 +64,9 @@ public class StompSubframeEncoder extends MessageToMessageEncoder<StompSubframe>
|
|||||||
ByteBuf buf = ctx.alloc().buffer();
|
ByteBuf buf = ctx.alloc().buffer();
|
||||||
|
|
||||||
buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII));
|
buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII));
|
||||||
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
|
buf.writeByte(StompConstants.LF);
|
||||||
|
frame.headers().forEachEntry(new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF));
|
||||||
StompHeaders headers = frame.headers();
|
buf.writeByte(StompConstants.LF);
|
||||||
for (String k: headers.keySet()) {
|
|
||||||
List<String> values = headers.getAll(k);
|
|
||||||
for (String v: values) {
|
|
||||||
buf.writeBytes(k.getBytes(CharsetUtil.US_ASCII)).
|
|
||||||
writeByte(StompConstants.COLON).writeBytes(v.getBytes(CharsetUtil.US_ASCII));
|
|
||||||
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.writeByte(StompConstants.CR).writeByte(StompConstants.LF);
|
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,29 +17,29 @@ package io.netty.handler.codec.stomp;
|
|||||||
|
|
||||||
public final class StompTestConstants {
|
public final class StompTestConstants {
|
||||||
public static final String CONNECT_FRAME =
|
public static final String CONNECT_FRAME =
|
||||||
"CONNECT\r\n" +
|
"CONNECT\n" +
|
||||||
"host:stomp.github.org\r\n" +
|
"host:stomp.github.org\n" +
|
||||||
"accept-version:1.1,1.2\r\n" +
|
"accept-version:1.1,1.2\n" +
|
||||||
"\r\n" +
|
'\n' +
|
||||||
'\0';
|
'\0';
|
||||||
public static final String CONNECTED_FRAME =
|
public static final String CONNECTED_FRAME =
|
||||||
"CONNECTED\r\n" +
|
"CONNECTED\n" +
|
||||||
"version:1.2\n" +
|
"version:1.2\n" +
|
||||||
"\r\n" +
|
'\n' +
|
||||||
"\0\n";
|
"\0\n";
|
||||||
public static final String SEND_FRAME_1 =
|
public static final String SEND_FRAME_1 =
|
||||||
"SEND\r\n" +
|
"SEND\n" +
|
||||||
"destination:/queue/a\n" +
|
"destination:/queue/a\n" +
|
||||||
"content-type:text/plain\n" +
|
"content-type:text/plain\n" +
|
||||||
"\r\n" +
|
'\n' +
|
||||||
"hello, queue a!" +
|
"hello, queue a!" +
|
||||||
"\0\n";
|
"\0\n";
|
||||||
public static final String SEND_FRAME_2 =
|
public static final String SEND_FRAME_2 =
|
||||||
"SEND\r\n" +
|
"SEND\n" +
|
||||||
"destination:/queue/a\n" +
|
"destination:/queue/a\n" +
|
||||||
"content-type:text/plain\n" +
|
"content-type:text/plain\n" +
|
||||||
"content-length:17\n" +
|
"content-length:17\n" +
|
||||||
"\r\n" +
|
'\n' +
|
||||||
"hello, queue a!!!" +
|
"hello, queue a!!!" +
|
||||||
"\0\n";
|
"\0\n";
|
||||||
|
|
||||||
|
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
public final class AsciiHeadersEncoder implements TextHeaderProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The separator characters to insert between a header name and a header value.
|
||||||
|
*/
|
||||||
|
public enum SeparatorType {
|
||||||
|
/**
|
||||||
|
* {@code ':'}
|
||||||
|
*/
|
||||||
|
COLON,
|
||||||
|
/**
|
||||||
|
* {@code ': '}
|
||||||
|
*/
|
||||||
|
COLON_SPACE,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The newline characters to insert between header entries.
|
||||||
|
*/
|
||||||
|
public enum NewlineType {
|
||||||
|
/**
|
||||||
|
* {@code '\n'}
|
||||||
|
*/
|
||||||
|
LF,
|
||||||
|
/**
|
||||||
|
* {@code '\r\n'}
|
||||||
|
*/
|
||||||
|
CRLF
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ByteBuf buf;
|
||||||
|
private final SeparatorType separatorType;
|
||||||
|
private final NewlineType newlineType;
|
||||||
|
|
||||||
|
public AsciiHeadersEncoder(ByteBuf buf) {
|
||||||
|
this(buf, SeparatorType.COLON_SPACE, NewlineType.CRLF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsciiHeadersEncoder(ByteBuf buf, SeparatorType separatorType, NewlineType newlineType) {
|
||||||
|
if (buf == null) {
|
||||||
|
throw new NullPointerException("buf");
|
||||||
|
}
|
||||||
|
if (separatorType == null) {
|
||||||
|
throw new NullPointerException("separatorType");
|
||||||
|
}
|
||||||
|
if (newlineType == null) {
|
||||||
|
throw new NullPointerException("newlineType");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buf = buf;
|
||||||
|
this.separatorType = separatorType;
|
||||||
|
this.newlineType = newlineType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean process(CharSequence name, CharSequence value) throws Exception {
|
||||||
|
final ByteBuf buf = this.buf;
|
||||||
|
final int nameLen = name.length();
|
||||||
|
final int valueLen = value.length();
|
||||||
|
final int entryLen = nameLen + valueLen + 4;
|
||||||
|
int offset = buf.writerIndex();
|
||||||
|
buf.ensureWritable(entryLen);
|
||||||
|
writeAscii(buf, offset, name, nameLen);
|
||||||
|
offset += nameLen;
|
||||||
|
|
||||||
|
switch (separatorType) {
|
||||||
|
case COLON:
|
||||||
|
buf.setByte(offset ++, ':');
|
||||||
|
break;
|
||||||
|
case COLON_SPACE:
|
||||||
|
buf.setByte(offset ++, ':');
|
||||||
|
buf.setByte(offset ++, ' ');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
writeAscii(buf, offset, value, valueLen);
|
||||||
|
offset += valueLen;
|
||||||
|
|
||||||
|
switch (newlineType) {
|
||||||
|
case LF:
|
||||||
|
buf.setByte(offset ++, '\n');
|
||||||
|
break;
|
||||||
|
case CRLF:
|
||||||
|
buf.setByte(offset ++, '\r');
|
||||||
|
buf.setByte(offset ++, '\n');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.writerIndex(offset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) {
|
||||||
|
if (value instanceof AsciiString) {
|
||||||
|
writeAsciiString(buf, offset, (AsciiString) value, valueLen);
|
||||||
|
} else {
|
||||||
|
writeCharSequence(buf, offset, value, valueLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeAsciiString(ByteBuf buf, int offset, AsciiString value, int valueLen) {
|
||||||
|
value.copy(0, buf, offset, valueLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeCharSequence(ByteBuf buf, int offset, CharSequence value, int valueLen) {
|
||||||
|
for (int i = 0; i < valueLen; i ++) {
|
||||||
|
buf.setByte(offset ++, c2b(value.charAt(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int c2b(char ch) {
|
||||||
|
return ch < 256? (byte) ch : '?';
|
||||||
|
}
|
||||||
|
}
|
1411
codec/src/main/java/io/netty/handler/codec/AsciiString.java
Normal file
1411
codec/src/main/java/io/netty/handler/codec/AsciiString.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,803 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.ParsePosition;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class DefaultTextHeaders implements TextHeaders {
|
||||||
|
|
||||||
|
private static final int BUCKET_SIZE = 17;
|
||||||
|
|
||||||
|
private static int index(int hash) {
|
||||||
|
return Math.abs(hash % BUCKET_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
|
||||||
|
private final HeaderEntry head = new HeaderEntry(this);
|
||||||
|
private final boolean ignoreCase;
|
||||||
|
int size;
|
||||||
|
|
||||||
|
public DefaultTextHeaders() {
|
||||||
|
this(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultTextHeaders(boolean ignoreCase) {
|
||||||
|
head.before = head.after = head;
|
||||||
|
this.ignoreCase = ignoreCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int hashCode(CharSequence name) {
|
||||||
|
return AsciiString.hashCode(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CharSequence convertName(CharSequence name) {
|
||||||
|
if (name == null) {
|
||||||
|
throw new NullPointerException("name");
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected CharSequence convertValue(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException("value");
|
||||||
|
}
|
||||||
|
if (value instanceof CharSequence) {
|
||||||
|
return (CharSequence) value;
|
||||||
|
}
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean nameEquals(CharSequence a, CharSequence b) {
|
||||||
|
return equals(a, b, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean valueEquals(CharSequence a, CharSequence b, boolean ignoreCase) {
|
||||||
|
return equals(a, b, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean equals(CharSequence a, CharSequence b, boolean ignoreCase) {
|
||||||
|
if (a == b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a instanceof AsciiString) {
|
||||||
|
AsciiString aa = (AsciiString) a;
|
||||||
|
if (ignoreCase) {
|
||||||
|
return aa.equalsIgnoreCase(b);
|
||||||
|
} else {
|
||||||
|
return aa.equals(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b instanceof AsciiString) {
|
||||||
|
AsciiString ab = (AsciiString) b;
|
||||||
|
if (ignoreCase) {
|
||||||
|
return ab.equalsIgnoreCase(a);
|
||||||
|
} else {
|
||||||
|
return ab.equals(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreCase) {
|
||||||
|
return a.toString().equalsIgnoreCase(b.toString());
|
||||||
|
} else {
|
||||||
|
return a.equals(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders add(CharSequence name, Object value) {
|
||||||
|
name = convertName(name);
|
||||||
|
CharSequence convertedVal = convertValue(value);
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
add0(h, i, name, convertedVal);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders add(CharSequence name, Iterable<?> values) {
|
||||||
|
name = convertName(name);
|
||||||
|
if (values == null) {
|
||||||
|
throw new NullPointerException("values");
|
||||||
|
}
|
||||||
|
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
for (Object v: values) {
|
||||||
|
if (v == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CharSequence convertedVal = convertValue(v);
|
||||||
|
add0(h, i, name, convertedVal);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders add(CharSequence name, Object... values) {
|
||||||
|
name = convertName(name);
|
||||||
|
if (values == null) {
|
||||||
|
throw new NullPointerException("values");
|
||||||
|
}
|
||||||
|
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
for (Object v: values) {
|
||||||
|
if (v == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CharSequence convertedVal = convertValue(v);
|
||||||
|
add0(h, i, name, convertedVal);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add0(int h, int i, CharSequence name, CharSequence value) {
|
||||||
|
// Update the hash table.
|
||||||
|
HeaderEntry e = entries[i];
|
||||||
|
HeaderEntry newEntry;
|
||||||
|
entries[i] = newEntry = new HeaderEntry(this, h, name, value);
|
||||||
|
newEntry.next = e;
|
||||||
|
|
||||||
|
// Update the linked list.
|
||||||
|
newEntry.addBefore(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders add(TextHeaders headers) {
|
||||||
|
if (headers == null) {
|
||||||
|
throw new NullPointerException("headers");
|
||||||
|
}
|
||||||
|
|
||||||
|
add0(headers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add0(TextHeaders headers) {
|
||||||
|
if (headers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers instanceof DefaultTextHeaders) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
DefaultTextHeaders m = (DefaultTextHeaders) headers;
|
||||||
|
HeaderEntry e = m.head.after;
|
||||||
|
while (e != m.head) {
|
||||||
|
CharSequence name = e.name;
|
||||||
|
name = convertName(name);
|
||||||
|
add(name, convertValue(e.value));
|
||||||
|
e = e.after;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Entry<CharSequence, CharSequence> e: headers.unconvertedEntries()) {
|
||||||
|
add(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(CharSequence name) {
|
||||||
|
if (name == null) {
|
||||||
|
throw new NullPointerException("name");
|
||||||
|
}
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
return remove0(h, i, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean remove0(int h, int i, CharSequence name) {
|
||||||
|
HeaderEntry e = entries[i];
|
||||||
|
if (e == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean removed = false;
|
||||||
|
for (;;) {
|
||||||
|
if (e.hash == h && nameEquals(e.name, name)) {
|
||||||
|
e.remove();
|
||||||
|
HeaderEntry next = e.next;
|
||||||
|
if (next != null) {
|
||||||
|
entries[i] = next;
|
||||||
|
e = next;
|
||||||
|
} else {
|
||||||
|
entries[i] = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
removed = true;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
HeaderEntry next = e.next;
|
||||||
|
if (next == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (next.hash == h && nameEquals(next.name, name)) {
|
||||||
|
e.next = next.next;
|
||||||
|
next.remove();
|
||||||
|
removed = true;
|
||||||
|
} else {
|
||||||
|
e = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders set(CharSequence name, Object value) {
|
||||||
|
name = convertName(name);
|
||||||
|
CharSequence convertedVal = convertValue(value);
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
remove0(h, i, name);
|
||||||
|
add0(h, i, name, convertedVal);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders set(CharSequence name, Iterable<?> values) {
|
||||||
|
name = convertName(name);
|
||||||
|
if (values == null) {
|
||||||
|
throw new NullPointerException("values");
|
||||||
|
}
|
||||||
|
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
|
||||||
|
remove0(h, i, name);
|
||||||
|
for (Object v: values) {
|
||||||
|
if (v == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CharSequence convertedVal = convertValue(v);
|
||||||
|
add0(h, i, name, convertedVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders set(CharSequence name, Object... values) {
|
||||||
|
name = convertName(name);
|
||||||
|
if (values == null) {
|
||||||
|
throw new NullPointerException("values");
|
||||||
|
}
|
||||||
|
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
|
||||||
|
remove0(h, i, name);
|
||||||
|
for (Object v: values) {
|
||||||
|
if (v == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CharSequence convertedVal = convertValue(v);
|
||||||
|
add0(h, i, name, convertedVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders set(TextHeaders headers) {
|
||||||
|
if (headers == null) {
|
||||||
|
throw new NullPointerException("headers");
|
||||||
|
}
|
||||||
|
|
||||||
|
clear();
|
||||||
|
add0(headers);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders clear() {
|
||||||
|
Arrays.fill(entries, null);
|
||||||
|
head.before = head.after = head;
|
||||||
|
size = 0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getUnconverted(CharSequence name) {
|
||||||
|
if (name == null) {
|
||||||
|
throw new NullPointerException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
HeaderEntry e = entries[i];
|
||||||
|
CharSequence value = null;
|
||||||
|
// loop until the first header was found
|
||||||
|
while (e != null) {
|
||||||
|
if (e.hash == h && nameEquals(e.name, name)) {
|
||||||
|
value = e.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
e = e.next;
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(CharSequence name) {
|
||||||
|
CharSequence v = getUnconverted(name);
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return v.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(CharSequence name, String defaultValue) {
|
||||||
|
CharSequence v = getUnconverted(name);
|
||||||
|
if (v == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return v.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInt(CharSequence name, int defaultValue) {
|
||||||
|
CharSequence v = getUnconverted(name);
|
||||||
|
if (v == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (v instanceof AsciiString) {
|
||||||
|
return ((AsciiString) v).parseInt();
|
||||||
|
} else {
|
||||||
|
return Integer.parseInt(v.toString());
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLong(CharSequence name, long defaultValue) {
|
||||||
|
CharSequence v = getUnconverted(name);
|
||||||
|
if (v == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (v instanceof AsciiString) {
|
||||||
|
return ((AsciiString) v).parseLong();
|
||||||
|
} else {
|
||||||
|
return Long.parseLong(v.toString());
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeMillis(CharSequence name, long defaultValue) {
|
||||||
|
CharSequence v = getUnconverted(name);
|
||||||
|
if (v == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpHeaderDateFormat.get().parse(v.toString(), defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CharSequence> getAllUnconverted(CharSequence name) {
|
||||||
|
if (name == null) {
|
||||||
|
throw new NullPointerException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CharSequence> values = new ArrayList<CharSequence>(4);
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
HeaderEntry e = entries[i];
|
||||||
|
while (e != null) {
|
||||||
|
if (e.hash == h && nameEquals(e.name, name)) {
|
||||||
|
values.add(e.getValue());
|
||||||
|
}
|
||||||
|
e = e.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.reverse(values);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAll(CharSequence name) {
|
||||||
|
if (name == null) {
|
||||||
|
throw new NullPointerException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> values = new ArrayList<String>(4);
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
HeaderEntry e = entries[i];
|
||||||
|
while (e != null) {
|
||||||
|
if (e.hash == h && nameEquals(e.name, name)) {
|
||||||
|
values.add(e.getValue().toString());
|
||||||
|
}
|
||||||
|
e = e.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.reverse(values);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Map.Entry<String, String>> entries() {
|
||||||
|
int cnt = 0;
|
||||||
|
int size = size();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map.Entry<String, String>[] all = new Map.Entry[size];
|
||||||
|
|
||||||
|
HeaderEntry e = head.after;
|
||||||
|
while (e != head) {
|
||||||
|
all[cnt ++] = new StringHeaderEntry(e);
|
||||||
|
e = e.after;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert size == cnt;
|
||||||
|
return Arrays.asList(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Map.Entry<CharSequence, CharSequence>> unconvertedEntries() {
|
||||||
|
int cnt = 0;
|
||||||
|
int size = size();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map.Entry<CharSequence, CharSequence>[] all = new Map.Entry[size];
|
||||||
|
|
||||||
|
HeaderEntry e = head.after;
|
||||||
|
while (e != head) {
|
||||||
|
all[cnt ++] = e;
|
||||||
|
e = e.after;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert size == cnt;
|
||||||
|
return Arrays.asList(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Entry<String, String>> iterator() {
|
||||||
|
return new StringHeaderIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Entry<CharSequence, CharSequence>> unconvertedIterator() {
|
||||||
|
return new HeaderIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(CharSequence name) {
|
||||||
|
return getUnconverted(name) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return head == head.after;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(CharSequence name, Object value) {
|
||||||
|
return contains(name, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(CharSequence name, Object value, boolean ignoreCase) {
|
||||||
|
if (name == null) {
|
||||||
|
throw new NullPointerException("name");
|
||||||
|
}
|
||||||
|
|
||||||
|
int h = hashCode(name);
|
||||||
|
int i = index(h);
|
||||||
|
CharSequence convertedVal = convertValue(value);
|
||||||
|
HeaderEntry e = entries[i];
|
||||||
|
while (e != null) {
|
||||||
|
if (e.hash == h && nameEquals(e.name, name)) {
|
||||||
|
if (valueEquals(e.value, convertedVal, ignoreCase)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e = e.next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<CharSequence> unconvertedNames() {
|
||||||
|
Set<CharSequence> names = new LinkedHashSet<CharSequence>(size());
|
||||||
|
HeaderEntry e = head.after;
|
||||||
|
while (e != head) {
|
||||||
|
names.add(e.getKey());
|
||||||
|
e = e.after;
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> names() {
|
||||||
|
Set<String> names = new LinkedHashSet<String>(size());
|
||||||
|
HeaderEntry e = head.after;
|
||||||
|
while (e != head) {
|
||||||
|
names.add(e.getKey().toString());
|
||||||
|
e = e.after;
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders forEachEntry(TextHeaderProcessor processor) {
|
||||||
|
HeaderEntry e = head.after;
|
||||||
|
try {
|
||||||
|
while (e != head) {
|
||||||
|
if (!processor.process(e.getKey(), e.getValue())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
e = e.after;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PlatformDependent.throwException(ex);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class HeaderEntry implements Map.Entry<CharSequence, CharSequence> {
|
||||||
|
private final DefaultTextHeaders parent;
|
||||||
|
final int hash;
|
||||||
|
final CharSequence name;
|
||||||
|
CharSequence value;
|
||||||
|
HeaderEntry next;
|
||||||
|
HeaderEntry before, after;
|
||||||
|
|
||||||
|
HeaderEntry(DefaultTextHeaders parent, int hash, CharSequence name, CharSequence value) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.hash = hash;
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderEntry(DefaultTextHeaders parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
hash = -1;
|
||||||
|
name = null;
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove() {
|
||||||
|
before.after = after;
|
||||||
|
after.before = before;
|
||||||
|
parent.size --;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addBefore(HeaderEntry e) {
|
||||||
|
after = e;
|
||||||
|
before = e.before;
|
||||||
|
before.after = this;
|
||||||
|
after.before = this;
|
||||||
|
parent.size ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getKey() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence setValue(CharSequence value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException("value");
|
||||||
|
}
|
||||||
|
value = parent.convertValue(value);
|
||||||
|
CharSequence oldValue = this.value;
|
||||||
|
this.value = value;
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name.toString() + '=' + value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StringHeaderEntry implements Entry<String, String> {
|
||||||
|
private final Entry<CharSequence, CharSequence> entry;
|
||||||
|
private String name;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
StringHeaderEntry(Entry<CharSequence, CharSequence> entry) {
|
||||||
|
this.entry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKey() {
|
||||||
|
if (name == null) {
|
||||||
|
name = entry.getKey().toString();
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue() {
|
||||||
|
if (value == null) {
|
||||||
|
value = entry.getValue().toString();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String setValue(String value) {
|
||||||
|
return entry.setValue(value).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return entry.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class HeaderIterator implements Iterator<Map.Entry<CharSequence, CharSequence>> {
|
||||||
|
|
||||||
|
private HeaderEntry current = head;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return current.after != head;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entry<CharSequence, CharSequence> next() {
|
||||||
|
current = current.after;
|
||||||
|
|
||||||
|
if (current == head) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class StringHeaderIterator implements Iterator<Map.Entry<String, String>> {
|
||||||
|
|
||||||
|
private HeaderEntry current = head;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return current.after != head;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entry<String, String> next() {
|
||||||
|
current = current.after;
|
||||||
|
|
||||||
|
if (current == head) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StringHeaderEntry(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This DateFormat decodes 3 formats of {@link java.util.Date}, but only encodes the one,
|
||||||
|
* the first:
|
||||||
|
* <ul>
|
||||||
|
* <li>Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with
|
||||||
|
* valid generation</li>
|
||||||
|
* <li>Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification</li>
|
||||||
|
* <li>Sun Nov 6 08:49:37 1994: obsolete specification</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
static final class HttpHeaderDateFormat {
|
||||||
|
|
||||||
|
private static final ParsePosition parsePos = new ParsePosition(0);
|
||||||
|
private static final ThreadLocal<HttpHeaderDateFormat> dateFormatThreadLocal =
|
||||||
|
new ThreadLocal<HttpHeaderDateFormat>() {
|
||||||
|
@Override
|
||||||
|
protected HttpHeaderDateFormat initialValue() {
|
||||||
|
return new HttpHeaderDateFormat();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static HttpHeaderDateFormat get() {
|
||||||
|
return dateFormatThreadLocal.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard date format:
|
||||||
|
* <pre>Sun, 06 Nov 1994 08:49:37 GMT -> E, d MMM yyyy HH:mm:ss z</pre>
|
||||||
|
*/
|
||||||
|
private final DateFormat dateFormat1 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
|
||||||
|
/**
|
||||||
|
* First obsolete format:
|
||||||
|
* <pre>Sunday, 06-Nov-94 08:49:37 GMT -> E, d-MMM-y HH:mm:ss z</pre>
|
||||||
|
*/
|
||||||
|
private final DateFormat dateFormat2 = new SimpleDateFormat("E, dd-MMM-yy HH:mm:ss z", Locale.ENGLISH);
|
||||||
|
/**
|
||||||
|
* Second obsolete format
|
||||||
|
* <pre>Sun Nov 6 08:49:37 1994 -> EEE, MMM d HH:mm:ss yyyy</pre>
|
||||||
|
*/
|
||||||
|
private final DateFormat dateFormat3 = new SimpleDateFormat("E MMM d HH:mm:ss yyyy", Locale.ENGLISH);
|
||||||
|
|
||||||
|
private HttpHeaderDateFormat() {
|
||||||
|
TimeZone tz = TimeZone.getTimeZone("GMT");
|
||||||
|
dateFormat1.setTimeZone(tz);
|
||||||
|
dateFormat2.setTimeZone(tz);
|
||||||
|
dateFormat3.setTimeZone(tz);
|
||||||
|
}
|
||||||
|
|
||||||
|
long parse(String text, long defaultValue) {
|
||||||
|
Date date = dateFormat1.parse(text, parsePos);
|
||||||
|
if (date == null) {
|
||||||
|
date = dateFormat2.parse(text, parsePos);
|
||||||
|
}
|
||||||
|
if (date == null) {
|
||||||
|
date = dateFormat3.parse(text, parsePos);
|
||||||
|
}
|
||||||
|
if (date == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return date.getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
178
codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java
Normal file
178
codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class EmptyTextHeaders implements TextHeaders {
|
||||||
|
|
||||||
|
protected EmptyTextHeaders() { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(CharSequence name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(CharSequence name, String defaultValue) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInt(CharSequence name, int defaultValue) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLong(CharSequence name, long defaultValue) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTimeMillis(CharSequence name, long defaultValue) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getUnconverted(CharSequence name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAll(CharSequence name) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CharSequence> getAllUnconverted(CharSequence name) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Entry<String, String>> entries() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Entry<CharSequence, CharSequence>> unconvertedEntries() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(CharSequence name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> names() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<CharSequence> unconvertedNames() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders add(CharSequence name, Object value) {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders add(CharSequence name, Iterable<?> values) {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders add(CharSequence name, Object... values) {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders add(TextHeaders headers) {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders set(CharSequence name, Object value) {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders set(CharSequence name, Iterable<?> values) {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders set(CharSequence name, Object... values) {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders set(TextHeaders headers) {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(CharSequence name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders clear() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(CharSequence name, Object value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(CharSequence name, Object value, boolean ignoreCase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Entry<String, String>> iterator() {
|
||||||
|
return entries().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Entry<CharSequence, CharSequence>> unconvertedIterator() {
|
||||||
|
return unconvertedEntries().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextHeaders forEachEntry(TextHeaderProcessor processor) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
|
public interface TextHeaderProcessor {
|
||||||
|
boolean process(CharSequence name, CharSequence value) throws Exception;
|
||||||
|
}
|
236
codec/src/main/java/io/netty/handler/codec/TextHeaders.java
Normal file
236
codec/src/main/java/io/netty/handler/codec/TextHeaders.java
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.netty.handler.codec;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A typical string multimap used by text protocols such as HTTP for the representation of arbitrary key-value data.
|
||||||
|
* One thing to note is that it uses {@link CharSequence} as its primary key and value type rather than {@link String}.
|
||||||
|
* When you invoke the operations that produce {@link String}s such as {@link #get(CharSequence)},
|
||||||
|
* a {@link CharSequence} is implicitly converted to a {@link String}. This is particularly useful for speed
|
||||||
|
* optimization because this multimap can hold a special {@link CharSequence} implementation that a codec can
|
||||||
|
* treat specially, such as {@link AsciiString}.
|
||||||
|
*/
|
||||||
|
public interface TextHeaders extends Iterable<Map.Entry<String, String>> {
|
||||||
|
/**
|
||||||
|
* Returns the value of a header with the specified name. If there are
|
||||||
|
* more than one values for the specified name, the first value is returned.
|
||||||
|
*
|
||||||
|
* @param name The name of the header to search
|
||||||
|
* @return The first header value or {@code null} if there is no such header
|
||||||
|
*/
|
||||||
|
String get(CharSequence name);
|
||||||
|
|
||||||
|
String get(CharSequence name, String defaultValue);
|
||||||
|
int getInt(CharSequence name, int defaultValue);
|
||||||
|
long getLong(CharSequence name, long defaultValue);
|
||||||
|
long getTimeMillis(CharSequence name, long defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of a header with the specified name. If there are
|
||||||
|
* more than one values for the specified name, the first value is returned.
|
||||||
|
*
|
||||||
|
* @param name The name of the header to search
|
||||||
|
* @return The first header value or {@code null} if there is no such header
|
||||||
|
*/
|
||||||
|
CharSequence getUnconverted(CharSequence name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the values of headers with the specified name
|
||||||
|
*
|
||||||
|
* @param name The name of the headers to search
|
||||||
|
* @return A {@link List} of header values which will be empty if no values are found
|
||||||
|
*/
|
||||||
|
List<String> getAll(CharSequence name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the values of headers with the specified name
|
||||||
|
*
|
||||||
|
* @param name The name of the headers to search
|
||||||
|
* @return A {@link List} of header values which will be empty if no values are found
|
||||||
|
*/
|
||||||
|
List<CharSequence> getAllUnconverted(CharSequence name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link List} that contains all headers in this object. Note that modifying the
|
||||||
|
* returned {@link List} will not affect the state of this object. If you intend to enumerate over the header
|
||||||
|
* entries only, use {@link #iterator()} instead, which has much less overhead.
|
||||||
|
*/
|
||||||
|
List<Entry<String, String>> entries();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link List} that contains all headers in this object. Note that modifying the
|
||||||
|
* returned {@link List} will not affect the state of this object. If you intend to enumerate over the header
|
||||||
|
* entries only, use {@link #iterator()} instead, which has much less overhead.
|
||||||
|
*/
|
||||||
|
List<Entry<CharSequence, CharSequence>> unconvertedEntries();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if there is a header with the specified name
|
||||||
|
*
|
||||||
|
* @param name The name of the header to search for
|
||||||
|
* @return True if at least one header is found
|
||||||
|
*/
|
||||||
|
boolean contains(CharSequence name);
|
||||||
|
|
||||||
|
int size();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if no header exists.
|
||||||
|
*/
|
||||||
|
boolean isEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link Set} that contains the names of all headers in this object. Note that modifying the
|
||||||
|
* returned {@link Set} will not affect the state of this object. If you intend to enumerate over the header
|
||||||
|
* entries only, use {@link #iterator()} instead, which has much less overhead.
|
||||||
|
*/
|
||||||
|
Set<String> names();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new {@link Set} that contains the names of all headers in this object. Note that modifying the
|
||||||
|
* returned {@link Set} will not affect the state of this object. If you intend to enumerate over the header
|
||||||
|
* entries only, use {@link #iterator()} instead, which has much less overhead.
|
||||||
|
*/
|
||||||
|
Set<CharSequence> unconvertedNames();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new header with the specified name and value.
|
||||||
|
*
|
||||||
|
* If the specified value is not a {@link String}, it is converted
|
||||||
|
* into a {@link String} by {@link Object#toString()}, except in the cases
|
||||||
|
* of {@link java.util.Date} and {@link java.util.Calendar}, which are formatted to the date
|
||||||
|
* format defined in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
|
||||||
|
*
|
||||||
|
* @param name The name of the header being added
|
||||||
|
* @param value The value of the header being added
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
TextHeaders add(CharSequence name, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new header with the specified name and values.
|
||||||
|
*
|
||||||
|
* This getMethod can be represented approximately as the following code:
|
||||||
|
* <pre>
|
||||||
|
* for (Object v: values) {
|
||||||
|
* if (v == null) {
|
||||||
|
* break;
|
||||||
|
* }
|
||||||
|
* headers.add(name, v);
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param name The name of the headepublic abstract rs being set
|
||||||
|
* @param values The values of the headers being set
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
TextHeaders add(CharSequence name, Iterable<?> values);
|
||||||
|
|
||||||
|
TextHeaders add(CharSequence name, Object... values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds all header entries of the specified {@code headers}.
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
TextHeaders add(TextHeaders headers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a header with the specified name and value.
|
||||||
|
*
|
||||||
|
* If there is an existing header with the same name, it is removed.
|
||||||
|
* If the specified value is not a {@link String}, it is converted into a
|
||||||
|
* {@link String} by {@link Object#toString()}, except for {@link java.util.Date}
|
||||||
|
* and {@link java.util.Calendar}, which are formatted to the date format defined in
|
||||||
|
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">RFC2616</a>.
|
||||||
|
*
|
||||||
|
* @param name The name of the header being set
|
||||||
|
* @param value The value of the header being set
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
TextHeaders set(CharSequence name, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a header with the specified name and values.
|
||||||
|
*
|
||||||
|
* If there is an existing header with the same name, it is removed.
|
||||||
|
* This getMethod can be represented approximately as the following code:
|
||||||
|
* <pre>
|
||||||
|
* headers.remove(name);
|
||||||
|
* for (Object v: values) {
|
||||||
|
* if (v == null) {
|
||||||
|
* break;
|
||||||
|
* }
|
||||||
|
* headers.add(name, v);
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param name The name of the headers being set
|
||||||
|
* @param values The values of the headers being set
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
TextHeaders set(CharSequence name, Iterable<?> values);
|
||||||
|
|
||||||
|
TextHeaders set(CharSequence name, Object... values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleans the current header entries and copies all header entries of the specified {@code headers}.
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
TextHeaders set(TextHeaders headers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the header with the specified name.
|
||||||
|
*
|
||||||
|
* @param name The name of the header to remove
|
||||||
|
* @return {@code true} if and only if at least one entry has been removed
|
||||||
|
*/
|
||||||
|
boolean remove(CharSequence name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all headers.
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
TextHeaders clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if a header with the name and value exists.
|
||||||
|
*
|
||||||
|
* @param name the headername
|
||||||
|
* @param value the value
|
||||||
|
* @return {@code true} if it contains it {@code false} otherwise
|
||||||
|
*/
|
||||||
|
boolean contains(CharSequence name, Object value);
|
||||||
|
|
||||||
|
boolean contains(CharSequence name, Object value, boolean ignoreCase);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Iterator<Entry<String, String>> iterator();
|
||||||
|
|
||||||
|
Iterator<Entry<CharSequence, CharSequence>> unconvertedIterator();
|
||||||
|
|
||||||
|
TextHeaders forEachEntry(TextHeaderProcessor processor);
|
||||||
|
}
|
@ -22,6 +22,7 @@ import java.security.cert.X509Certificate;
|
|||||||
public final class EmptyArrays {
|
public final class EmptyArrays {
|
||||||
|
|
||||||
public static final byte[] EMPTY_BYTES = new byte[0];
|
public static final byte[] EMPTY_BYTES = new byte[0];
|
||||||
|
public static final char[] EMPTY_CHARS = new char[0];
|
||||||
public static final boolean[] EMPTY_BOOLEANS = new boolean[0];
|
public static final boolean[] EMPTY_BOOLEANS = new boolean[0];
|
||||||
public static final double[] EMPTY_DOUBLES = new double[0];
|
public static final double[] EMPTY_DOUBLES = new double[0];
|
||||||
public static final float[] EMPTY_FLOATS = new float[0];
|
public static final float[] EMPTY_FLOATS = new float[0];
|
||||||
|
@ -19,11 +19,11 @@ import io.netty.buffer.Unpooled;
|
|||||||
import io.netty.channel.ChannelFutureListener;
|
import io.netty.channel.ChannelFutureListener;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.handler.codec.AsciiString;
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||||
import io.netty.handler.codec.http.FullHttpResponse;
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaders.*;
|
import static io.netty.handler.codec.http.HttpHeaders.*;
|
||||||
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
import static io.netty.handler.codec.http.HttpResponseStatus.*;
|
||||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||||
@ -31,6 +31,11 @@ import static io.netty.handler.codec.http.HttpVersion.*;
|
|||||||
public class HttpHelloWorldServerHandler extends ChannelInboundHandlerAdapter {
|
public class HttpHelloWorldServerHandler extends ChannelInboundHandlerAdapter {
|
||||||
private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };
|
private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };
|
||||||
|
|
||||||
|
private static final AsciiString CONTENT_TYPE = new AsciiString("Content-Type");
|
||||||
|
private static final AsciiString CONTENT_LENGTH = new AsciiString("Content-Length");
|
||||||
|
private static final AsciiString CONNECTION = new AsciiString("Connection");
|
||||||
|
private static final AsciiString KEEP_ALIVE = new AsciiString("keep-alive");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||||
ctx.flush();
|
ctx.flush();
|
||||||
@ -52,7 +57,7 @@ public class HttpHelloWorldServerHandler extends ChannelInboundHandlerAdapter {
|
|||||||
if (!keepAlive) {
|
if (!keepAlive) {
|
||||||
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
|
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
|
||||||
} else {
|
} else {
|
||||||
response.headers().set(CONNECTION, Values.KEEP_ALIVE);
|
response.headers().set(CONNECTION, KEEP_ALIVE);
|
||||||
ctx.write(response);
|
ctx.write(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
177
license/LICENSE.harmony.txt
Normal file
177
license/LICENSE.harmony.txt
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
Loading…
Reference in New Issue
Block a user