com.firenio.codec.http11.HttpCodec Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of firenio-all Show documentation
Show all versions of firenio-all Show documentation
The all in one project of firenio
/*
* Copyright 2015 The FireNio Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.firenio.codec.http11;
import com.firenio.Develop;
import com.firenio.buffer.ByteBuf;
import com.firenio.collection.ByteTree;
import com.firenio.collection.IntMap;
import com.firenio.common.ByteUtil;
import com.firenio.common.Util;
import com.firenio.component.Channel;
import com.firenio.component.FastThreadLocal;
import com.firenio.component.Frame;
import com.firenio.component.NioEventLoop;
import com.firenio.component.ProtocolCodec;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
/**
* @author wangkai
*/
public class HttpCodec extends ProtocolCodec {
protected static final byte[] CONTENT_LENGTH_MATCH = ByteUtil.b("Content-Length:");
protected static final int decode_state_body = 2;
protected static final int decode_state_complete = 3;
protected static final int decode_state_header = 1;
protected static final int decode_state_line_one = 0;
protected static final int FRAME_CACHE_KEY = NioEventLoop.nextAttributeKey();
protected static final byte N = '\n';
protected static final IOException OVER_LIMIT = EXCEPTION("over writeIndex");
protected static final IOException ILLEGAL_METHOD = EXCEPTION("illegal http method");
protected static final byte R = '\r';
protected static final byte SPACE = ' ';
protected static final int NUM_GET = ByteUtil.getIntLE("GET ".getBytes(), 0);
protected static final int NUM_POST = ByteUtil.getIntLE("POST".getBytes(), 0);
protected static final int NUM_HEAD = ByteUtil.getIntLE("HEAD".getBytes(), 0);
protected final int blimit;
protected final byte[][] cl_bytes = new byte[1024][];
protected final int hlimit;
protected final int fcache;
protected final boolean lite;
protected final int cthreshold;
protected final ByteBuffer cl_buf;
protected final ByteTree cached_urls;
public HttpCodec() {
this(0);
}
public HttpCodec(int frameCache) {
this(null, frameCache);
}
public HttpCodec(String server) {
this(server, 0);
}
public HttpCodec(String server, int frameCache) {
this(server, frameCache, false);
}
public HttpCodec(String server, int frameCache, boolean lite) {
this(server, frameCache, 1024 * 8, 1024 * 256, 1024 * 64, lite, null);
}
public HttpCodec(String server, int frameCache, boolean lite, ByteTree cachedUrls) {
this(server, frameCache, 1024 * 8, 1024 * 256, 1024 * 64, lite, cachedUrls);
}
public HttpCodec(String server, int fcache, int hlimit, int blimit, int cthreshold, boolean lite, ByteTree cachedUrls) {
this.lite = lite;
this.hlimit = hlimit;
this.blimit = blimit;
this.fcache = fcache;
this.cthreshold = cthreshold;
this.cached_urls = cachedUrls;
ByteBuffer temp = ByteBuffer.allocate(128);
if (server == null) {
temp.put(ByteUtil.b("\r\nContent-Length: "));
} else {
temp.put(ByteUtil.b("\r\nServer: " + server + "\r\nContent-Length: "));
}
cl_buf = temp.duplicate();
cl_buf.flip();
int p = temp.position();
for (int i = 0; i < cl_bytes.length; i++) {
temp.clear().position(p);
temp.put(String.valueOf(i).getBytes());
temp.flip();
cl_bytes[i] = new byte[temp.limit()];
temp.get(cl_bytes[i]);
}
}
private static String parse_url(ByteBuf src, int url_start, int url_end) {
StringBuilder line = FastThreadLocal.get().getStringBuilder();
for (int i = url_start; i < url_end; i++) {
line.append((char) (src.getByteAbs(i) & 0xff));
}
return line.toString();
}
static void parse_kv(Map map, CharSequence line, int start, int end, char kvSplitor, char eSplitor) {
boolean find_key = true;
int i = start;
int ks = start;
int vs = 0;
CharSequence key = null;
CharSequence value = null;
for (; i != end; ) {
char c = line.charAt(i++);
if (find_key) {
if (c == kvSplitor) {
ks = Util.skip(line, ' ', ks);
key = line.subSequence(ks, i - 1);
find_key = false;
vs = i;
}
} else {
if (c == eSplitor) {
vs = Util.skip(line, ' ', vs);
value = line.subSequence(vs, i - 1);
find_key = true;
ks = i;
map.put((String) key, (String) value);
}
}
}
if (!find_key && end > vs) {
map.put((String) key, (String) line.subSequence(vs, end));
}
}
protected static void parse_url(HttpFrame f, int skip, CharSequence line) {
int index = Util.indexOf(line, '?');
int lastSpace = Util.lastIndexOf(line, ' ');
if (index > -1) {
parse_kv(f.getRequestParams(), line, index + 1, lastSpace, '=', '&');
f.setRequestURL((String) line.subSequence(skip, index));
} else {
f.setRequestURL((String) line.subSequence(skip, lastSpace));
}
}
private static int read_line_range(StringBuilder line, ByteBuf src, int abs_pos, int h_len, int limit) throws IOException {
int to = abs_pos + (limit - h_len);
int write_index = src.writeIndex();
if (to > write_index) {
return append_line(line, src, abs_pos, write_index);
} else {
int index = append_line(line, src, abs_pos, to);
if (index == -1) {
throw OVER_LIMIT;
}
return index;
}
}
private static int append_line(StringBuilder line, ByteBuf src, int abs_pos, int abs_limit) {
for (int i = abs_pos; i < abs_limit; i++) {
byte b = src.getByteAbs(i);
if (b == N) {
line.setLength(line.length() - 1);
return i + 1;
} else {
line.append((char) (b & 0xff));
}
}
return -1;
}
private static int read_line_range(ByteBuf src, int abs_pos, int h_len, int limit) throws IOException {
int to = abs_pos + (limit - h_len);
int write_index = src.writeIndex();
if (to > write_index) {
return src.indexOf(N, abs_pos, write_index);
} else {
int index = src.indexOf(N, abs_pos, to);
if (index == -1) {
throw OVER_LIMIT;
}
return index;
}
}
private static boolean start_with(ByteBuf src, int ps, int pe, byte[] match) {
if (pe - ps < match.length) {
return false;
}
for (int i = 0; i < match.length; i++) {
if (src.getByteAbs(ps + i) != match[i]) {
return false;
}
}
return true;
}
HttpFrame new_frame() {
return new HttpFrame();
}
private HttpFrame alloc_frame(NioEventLoop el) {
if (fcache > 0) {
Frame res = (Frame) el.getCache(FRAME_CACHE_KEY, fcache);
if (res == null) {
return new_frame();
} else {
return (HttpFrame) res.reset();
}
}
return new_frame();
}
private int decode_lite(ByteBuf src, HttpFrame f) throws IOException {
int decode_state = f.getDecodeState();
int abs_pos = src.absReadIndex();
int h_len = f.getHeaderLength();
if (decode_state == decode_state_line_one) {
int l_end = read_line_range(src, abs_pos, h_len, hlimit);
if (l_end == -1) {
return decode_state_line_one;
} else {
h_len += (l_end - abs_pos);
decode_state = decode_state_header;
int url_start = abs_pos;
int num = src.getIntLE(abs_pos);
if (num == NUM_GET) {
f.setMethod(HttpMethod.GET);
url_start += 4;
} else if (num == NUM_POST) {
f.setMethod(HttpMethod.POST);
url_start += 5;
} else if (num == NUM_HEAD) {
f.setMethod(HttpMethod.HEAD);
url_start += 5;
} else {
throw ILLEGAL_METHOD;
}
int url_end = l_end - 10;
int qmark = src.indexOf((byte) '?', url_start, url_end);
if (qmark == -1) {
String url;
if (cached_urls != null) {
url = cached_urls.getString(src, url_start, url_end);
if (url == null) {
url = parse_url(src, url_start, url_end);
}
} else {
url = parse_url(src, url_start, url_end);
}
f.setRequestURL(url);
} else {
StringBuilder line = FastThreadLocal.get().getStringBuilder();
for (int i = url_start; i < url_end; i++) {
line.append((char) (src.getByteAbs(i) & 0xff));
}
int re_qmark = qmark - url_start;
parse_kv(f.getRequestParams(), line, re_qmark + 1, line.length(), '=', '&');
f.setRequestURL((String) line.subSequence(0, re_qmark));
}
abs_pos = l_end + 1;
}
}
if (decode_state == decode_state_header) {
for (; ; ) {
int ps = abs_pos;
int pe = read_line_range(src, ps, h_len, hlimit);
if (pe == -1) {
f.setHeaderLength(h_len);
src.absReadIndex(abs_pos);
break;
}
abs_pos = pe-- + 1;
int size = pe - ps;
h_len += size;
if (size == 0) {
if (f.getContentLength() < 1) {
decode_state = decode_state_complete;
} else {
if (f.getContentLength() > blimit) {
throw OVER_LIMIT;
}
decode_state = decode_state_body;
}
src.absReadIndex(abs_pos);
break;
} else {
if (!f.isGet()) {
if (start_with(src, ps, pe, CONTENT_LENGTH_MATCH)) {
int cp = ps + CONTENT_LENGTH_MATCH.length;
int cps = ByteUtil.skip(src, cp, pe, SPACE);
if (cps == -1) {
throw OVER_LIMIT;
}
int ct_len = 0;
for (int i = cps; i < pe; i++) {
ct_len = (src.getByteAbs(i) - '0') + ct_len * 10;
}
f.setContentLength(ct_len);
}
}
}
}
}
return decode_state;
}
private int decode_full(ByteBuf src, HttpFrame f) throws IOException {
StringBuilder line = FastThreadLocal.get().getStringBuilder();
int decode_state = f.getDecodeState();
int h_len = f.getHeaderLength();
int abs_pos = src.absReadIndex();
if (decode_state == decode_state_line_one) {
int l_end = read_line_range(line, src, abs_pos, 0, hlimit);
if (l_end == -1) {
return decode_state_line_one;
} else {
abs_pos = l_end;
h_len += line.length();
decode_state = decode_state_header;
parse_line_one(f, line);
}
}
if (decode_state == decode_state_header) {
for (; ; ) {
line.setLength(0);
int pn = read_line_range(line, src, abs_pos, h_len, hlimit);
if (pn == -1) {
src.absReadIndex(abs_pos);
f.setHeaderLength(h_len);
break;
}
abs_pos = pn;
h_len += line.length();
if (line.length() == 0) {
src.absReadIndex(abs_pos);
decode_state = header_complete(f);
break;
} else {
int p = Util.indexOf(line, ':');
if (p == -1) {
continue;
}
int rp = Util.skip(line, ' ', p + 1);
String name = line.substring(0, p);
String value = line.substring(rp);
f.setReadHeader(name, value);
}
}
}
return decode_state;
}
@Override
public Frame decode(Channel ch, ByteBuf src) throws Exception {
boolean remove = false;
HttpAttachment att = (HttpAttachment) ch.getAttachment();
HttpFrame f = att.getUncompletedFrame();
if (f == null) {
f = alloc_frame(ch.getEventLoop());
} else {
remove = true;
}
int decode_state;
if (lite) {
decode_state = decode_lite(src, f);
} else {
decode_state = decode_full(src, f);
}
if (decode_state == decode_state_body) {
decode_state = decode_remain_body(ch, src, f);
}
if (decode_state == decode_state_complete) {
if (remove) {
att.setUncompletedFrame(null);
}
return f;
} else {
f.setDecodeState(decode_state);
att.setUncompletedFrame(f);
return null;
}
}
int decode_remain_body(Channel ch, ByteBuf src, HttpFrame f) {
int contentLength = f.getContentLength();
int remain = src.readableBytes();
if (remain < contentLength) {
return decode_state_body;
} else {
byte[] content = new byte[contentLength];
src.readBytes(content);
if (f.isForm()) {
String param = new String(content, ch.getCharset());
parse_kv(f.getRequestParams(), param, 0, param.length(), '=', '&');
} else {
f.setContent(content);
}
return decode_state_complete;
}
}
@Override
public ByteBuf encode(final Channel ch, Frame frame) {
HttpFrame f = (HttpFrame) frame;
FastThreadLocal l = FastThreadLocal.get();
List bytes_array = (List) l.getList();
Object content = f.getContent();
ByteBuf content_buf = null;
byte[] content_array = null;
byte[] head_bytes = f.getStatus().getLine();
byte[] conn_bytes = f.getConnection().getLine();
byte[] type_bytes = f.getContentType().getLine();
byte[] date_bytes = f.getDate();
boolean is_array = false;
int write_size = 0;
if (content instanceof ByteBuf) {
content_buf = (ByteBuf) content;
write_size = content_buf.readableBytes();
} else if (content instanceof byte[]) {
is_array = true;
content_array = (byte[]) content;
write_size = content_array.length;
}
byte[] cl_len_bytes;
int cl_len;
if (write_size < 1024) {
cl_len_bytes = cl_bytes[write_size];
cl_len = cl_len_bytes.length;
} else {
cl_len_bytes = cl_buf.array();
int tmp_len = cl_buf.limit();
int len_idx = Util.valueOf(write_size, cl_len_bytes);
int num_len = cl_len_bytes.length - len_idx;
System.arraycopy(cl_len_bytes, len_idx, cl_len_bytes, tmp_len, num_len);
cl_len = tmp_len + num_len;
}
int hlen = head_bytes.length;
int tlen = type_bytes == null ? 0 : type_bytes.length;
int clen = conn_bytes == null ? 0 : conn_bytes.length;
int dlen = date_bytes == null ? 0 : date_bytes.length;
int len = hlen + cl_len + dlen + 2 + clen + tlen;
int h_size = 0;
IntMap headers = f.getResponseHeaders();
if (headers != null) {
for (headers.scan(); headers.hasNext(); ) {
byte[] k = HttpHeader.get(headers.key()).getBytes();
byte[] v = headers.value();
h_size++;
bytes_array.add(k);
bytes_array.add(v);
len += 4;
len += k.length;
len += v.length;
}
}
len += 2;
if (write_size <= cthreshold) {
len += write_size;
}
ByteBuf buf;
if (Develop.BUF_DEBUG) {
buf = ch.alloc().allocate(1);
} else {
buf = ch.alloc().allocate(len);
}
buf.writeBytes(head_bytes);
buf.writeBytes(cl_len_bytes, 0, cl_len);
if (conn_bytes != null) {
buf.writeBytes(conn_bytes);
}
if (type_bytes != null) {
buf.writeBytes(type_bytes);
}
if (date_bytes != null) {
buf.writeBytes(date_bytes);
}
buf.writeByte(R);
buf.writeByte(N);
if (h_size > 0) {
put_headers(buf, bytes_array, h_size);
}
buf.writeByte(R);
buf.writeByte(N);
if (write_size > cthreshold) {
ch.write(buf);
if (is_array) {
ch.write(ByteBuf.wrap(content_array));
} else {
ch.write(content_buf);
}
return null;
} else {
if (write_size > 0) {
if (is_array) {
buf.writeBytes(content_array);
} else {
buf.writeBytes(content_buf);
}
}
return buf;
}
}
public void encode(final Channel ch, ByteBuf buf, Frame frame) {
HttpFrame f = (HttpFrame) frame;
FastThreadLocal l = FastThreadLocal.get();
List bytes_list = (List) l.getList();
byte[] content = (byte[]) f.getContent();
byte[] head_bytes = f.getStatus().getLine();
byte[] conn_bytes = f.getConnection().getLine();
byte[] type_bytes = f.getContentType().getLine();
byte[] date_bytes = f.getDate();
int write_size = content.length;
byte[] cl_len_bytes;
int cl_len;
if (write_size < 1024) {
cl_len_bytes = cl_bytes[write_size];
cl_len = cl_len_bytes.length;
} else {
cl_len_bytes = cl_buf.array();
int tmp_len = cl_buf.limit();
int len_idx = Util.valueOf(write_size, cl_len_bytes);
int num_len = cl_len_bytes.length - len_idx;
System.arraycopy(cl_len_bytes, len_idx, cl_len_bytes, tmp_len, num_len);
cl_len = tmp_len + num_len;
}
int h_size = 0;
IntMap headers = f.getResponseHeaders();
if (headers != null) {
for (headers.scan(); headers.hasNext(); ) {
byte[] k = HttpHeader.get(headers.key()).getBytes();
byte[] v = headers.value();
h_size++;
bytes_list.add(k);
bytes_list.add(v);
}
}
buf.writeBytes(head_bytes);
buf.writeBytes(cl_len_bytes, 0, cl_len);
if (conn_bytes != null) {
buf.writeBytes(conn_bytes);
}
if (type_bytes != null) {
buf.writeBytes(type_bytes);
}
if (date_bytes != null) {
buf.writeBytes(date_bytes);
}
buf.writeByte(R);
buf.writeByte(N);
if (h_size > 0) {
put_headers(buf, bytes_list, h_size);
}
buf.writeByte(R);
buf.writeByte(N);
buf.writeBytes(content);
}
protected void put_headers(ByteBuf buf, List encode_bytes_array, int header_size) {
int j = 0;
for (int i = 0; i < header_size; i++) {
buf.writeBytes(encode_bytes_array.get(j++));
buf.writeByte((byte) ':');
buf.writeByte(SPACE);
buf.writeBytes(encode_bytes_array.get(j++));
buf.writeByte(R);
buf.writeByte(N);
}
}
public int getBodyLimit() {
return blimit;
}
public int getHeaderLimit() {
return hlimit;
}
public int getHttpFrameStackSize() {
return fcache;
}
@Override
public String getProtocolId() {
return "HTTP1.1";
}
@Override
public int getHeaderLength() {
return 0;
}
int header_complete(HttpFrame f) throws IOException {
int contentLength = 0;
String c_length = f.getRequestHeader(HttpHeader.Content_Length);
String c_type = f.getRequestHeader(HttpHeader.Content_Type);
f.setForm(isForm(c_type));
if (!Util.isNullOrBlank(c_length)) {
contentLength = Integer.parseInt(c_length);
f.setContentLength(contentLength);
}
if (contentLength < 1) {
return decode_state_complete;
} else {
if (contentLength > blimit) {
throw OVER_LIMIT;
}
return decode_state_body;
}
}
private boolean isForm(String c_type) {
//TODO complete me
if (c_type == null) {
return false;
}
return c_type.startsWith("multipart/form-data;");
}
protected void parse_line_one(HttpFrame f, CharSequence line) throws IOException {
int v = (line.charAt(0) << 0) | (line.charAt(1) << 8) | (line.charAt(2) << 16) | (line.charAt(3) << 24);
if (v == NUM_GET) {
f.setMethod(HttpMethod.GET);
parse_url(f, 4, line);
} else if (v == NUM_POST) {
f.setMethod(HttpMethod.POST);
parse_url(f, 5, line);
} else if (v == NUM_HEAD) {
f.setMethod(HttpMethod.HEAD);
parse_url(f, 5, line);
} else {
throw ILLEGAL_METHOD;
}
}
@Override
public void release(NioEventLoop eventLoop, Frame frame) {
eventLoop.release(FRAME_CACHE_KEY, frame);
}
@Override
protected Object newAttachment() {
return new HttpAttachment();
}
}