com.firenio.baseio.codec.http11.HttpCodec Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of baseio-all Show documentation
Show all versions of baseio-all Show documentation
The all in one project of baseio
/*
* Copyright 2015 The Baseio 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.baseio.codec.http11;
import static com.firenio.baseio.codec.http11.HttpHeader.Content_Length;
import static com.firenio.baseio.codec.http11.HttpHeader.Content_Type;
import static com.firenio.baseio.codec.http11.HttpHeader.Cookie;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.firenio.baseio.Develop;
import com.firenio.baseio.buffer.ByteBuf;
import com.firenio.baseio.collection.IntMap;
import com.firenio.baseio.common.ByteUtil;
import com.firenio.baseio.common.Util;
import com.firenio.baseio.component.Channel;
import com.firenio.baseio.component.FastThreadLocal;
import com.firenio.baseio.component.Frame;
import com.firenio.baseio.component.NioEventLoop;
import com.firenio.baseio.component.ProtocolCodec;
/**
* @author wangkai
*
*/
public class HttpCodec extends ProtocolCodec {
static final byte[] CONNECTION_CLOSE = b("\r\nConnection: close");
static final byte[] CONNECTION_KEEP = b("\r\nConnection: keep-alive");
static final byte[] CONNECTIONS = b("\r\nConnection: keep-alive");
static final byte[] CONTENT_LENGTH_MATCH = b("Content-Length:");
static final byte[] CONTENT_TYPE = b("\r\nContent-Type: ");
static final byte[] DATE = b("\r\nDate: ");
static final int decode_state_body = 2;
static final int decode_state_complate = 3;
static final int decode_state_header = 1;
static final int decode_state_line_one = 0;
static final int encode_bytes_arrays_index = nextIndexedVariablesIndex();
static final int content_len_index = nextIndexedVariablesIndex();
static final String FRAME_BUFFER_KEY = "_HTTP_FRAME_BUFFER_KEY";
static final String FRAME_DECODE_KEY = "_HTTP_FRAME_DECODE_KEY";
static final KMPUtil KMP_BOUNDARY = new KMPUtil("boundary=");
static final byte N = '\n';
public static final IOException OVER_LIMIT = EXCEPTION("over limit");
static final byte R = '\r';
static final byte[] SET_COOKIE = b("Set-Cookie:");
static final byte SPACE = ' ';
private int bodyLimit = 1024 * 512;
private final byte[][] CONTENT_LENGTHS = new byte[1024][];
private int headerLimit = 1024 * 8;
private int httpFrameBuffer = 0;
private boolean lite;
private ByteBuffer contentLenBuf;
public HttpCodec() {
this(0);
}
public HttpCodec(int httpFrameBuffer) {
this(null, httpFrameBuffer);
}
public HttpCodec(String server) {
this(server, 0);
}
public HttpCodec(String server, int httpFrameBuffer) {
this(server, httpFrameBuffer, false);
}
public HttpCodec(String server, int httpFrameBuffer, boolean lite) {
this.lite = lite;
this.httpFrameBuffer = httpFrameBuffer;
ByteBuffer temp = ByteBuffer.allocate(128);
if (server == null) {
temp.put(b("\r\nContent-Length: "));
} else {
temp.put(b("\r\nServer: " + server + "\r\nContent-Length: "));
}
contentLenBuf = temp.duplicate();
contentLenBuf.flip();
int p = temp.position();
for (int i = 0; i < CONTENT_LENGTHS.length; i++) {
temp.clear().position(p);
temp.put(String.valueOf(i).getBytes());
temp.flip();
CONTENT_LENGTHS[i] = new byte[temp.limit()];
temp.get(CONTENT_LENGTHS[i]);
}
}
HttpFrame allocHttpFrame(Channel ch) {
HttpFrame f = (HttpFrame) ch.getAttribute(FRAME_DECODE_KEY);
if (f == null) {
if (httpFrameBuffer > 0) {
NioEventLoop el = ch.getEventLoop();
Frame res = el.getFrameFromBuffer(ch, FRAME_BUFFER_KEY, httpFrameBuffer);
if (res == null) {
return new HttpFrame();
} else {
return (HttpFrame) res.reset();
}
}
return new HttpFrame();
}
return f;
}
@Override
public Frame decode(Channel ch, ByteBuf src) throws Exception {
HttpFrame f = allocHttpFrame(ch);
int decode_state = f.getDecodeState();
if (lite) {
if (decode_state == decode_state_line_one) {
int ln = src.indexOf(N);
if (ln != -1) {
int p = src.absPos();
f.incrementHeaderLength(ln - p);
decode_state = decode_state_header;
ln = findN(src, ln);
int skip;
if (src.absByte(p) == 'G') {
f.setMethod(HttpMethod.GET);
skip = 4;
} else {
f.setMethod(HttpMethod.POST);
skip = 5;
}
StringBuilder line = FastThreadLocal.get().getStringBuilder();
int count = ln - 9;
for (int i = p + skip; i < count; i++) {
line.append((char) (src.absByte(i) & 0xff));
}
int qmask = Util.indexOf(line, '?');
if (qmask > -1) {
parse_kv(f.getRequestParams(), line, qmask + 1, line.length(), '=', '&');
f.setRequestURL((String) line.subSequence(0, qmask));
} else {
f.setRequestURL(line.toString());
}
src.absPos(ln + 1);
}
}
if (decode_state == decode_state_header) {
for (;;) {
int ps = src.absPos();
int pe = read_line_range(src, f.getHeaderLength(), headerLimit);
if (pe == -1) {
break;
}
int size = pe - ps;
f.incrementHeaderLength(size);
if (size == 0) {
if (f.getContentLength() < 1) {
decode_state = decode_state_complate;
} else {
if (f.getContentLength() > bodyLimit) {
throw OVER_LIMIT;
}
decode_state = decode_state_body;
}
break;
} else {
if (!f.isGet()) {
if (startWith(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 ctLen = 0;
for (int i = cps; i < pe; i++) {
ctLen = (src.absByte(i) - '0') + ctLen * 10;
}
f.setContentLength(ctLen);
}
}
}
}
}
if (decode_state == decode_state_body) {
int contentLength = f.getContentLength();
int remain = src.remaining();
if (remain > contentLength) {
src.markL();
src.limit(src.position() + contentLength);
f.setContent(src.getBytes());
src.resetL();
decode_state = decode_state_complate;
} else if (remain == contentLength) {
f.setContent(src.getBytes());
decode_state = decode_state_complate;
}
}
if (decode_state == decode_state_complate) {
ch.removeAttribute(FRAME_DECODE_KEY);
return f;
} else {
f.setDecodeState(decode_state);
ch.setAttribute(FRAME_DECODE_KEY, f);
return null;
}
} else {
StringBuilder line = FastThreadLocal.get().getStringBuilder();
if (decode_state == decode_state_line_one) {
if (read_line(line, src, 0, headerLimit)) {
f.incrementHeaderLength(line.length());
decode_state = decode_state_header;
parse_line_one(f, line);
}
}
if (decode_state == decode_state_header) {
for (;;) {
line.setLength(0);
if (!read_line(line, src, f.getHeaderLength(), headerLimit)) {
break;
}
f.incrementHeaderLength(line.length());
if (line.length() == 0) {
decode_state = onHeaderReadComplete(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);
}
}
}
if (decode_state == decode_state_body) {
decode_state = decodeRemainBody(ch, src, f);
}
if (decode_state == decode_state_complate) {
ch.removeAttribute(FRAME_DECODE_KEY);
return f;
} else {
f.setDecodeState(decode_state);
ch.setAttribute(FRAME_DECODE_KEY, f);
return null;
}
}
}
int decodeRemainBody(Channel ch, ByteBuf src, HttpFrame f) {
int contentLength = f.getContentLength();
int remain = src.remaining();
byte[] ontent = null;
if (remain < contentLength) {
return decode_state_body;
} else if (remain == contentLength) {
ontent = src.getBytes();
} else {
src.markL();
src.limit(src.position() + contentLength);
ontent = src.getBytes();
src.resetL();
}
if (!f.isForm()) {
String param = new String(ontent, ch.getCharset());
parse_kv(f.getRequestParams(), param, 0, param.length(), '=', '&');
} else {
f.setContent(ontent);
}
return decode_state_complate;
}
private byte[] getContentLenBuf(FastThreadLocal l) {
byte[] bb = (byte[]) l.getIndexedVariable(content_len_index);
if (bb == null) {
int limit = contentLenBuf.limit();
bb = new byte[contentLenBuf.limit() + 16];
contentLenBuf.get(bb, 0, limit);
contentLenBuf.clear().limit(limit);
l.setIndexedVariable(content_len_index, bb);
}
return bb;
}
@Override
public ByteBuf encode(final Channel ch, Frame frame) throws IOException {
HttpFrame f = (HttpFrame) frame;
FastThreadLocal l = FastThreadLocal.get();
List encode_bytes_array = getEncodeBytesArray(l);
Object content = f.getContent();
ByteBuf contentBuf = null;
byte[] contentArray = 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 isArray = false;
int write_size = 0;
if (content instanceof ByteBuf) {
contentBuf = ((ByteBuf) content).flip();
write_size = contentBuf.limit();
} else if (content instanceof byte[]) {
isArray = true;
contentArray = (byte[]) content;
write_size = contentArray.length;
}
byte[] clen_bytes;
int clen;
if (write_size < 1024) {
clen_bytes = CONTENT_LENGTHS[write_size];
clen = clen_bytes.length;
} else {
clen_bytes = getContentLenBuf(l);
int tmp_len = contentLenBuf.limit();
int len_idx = Util.valueOf(write_size, clen_bytes);
int num_len = clen_bytes.length - len_idx;
System.arraycopy(clen_bytes, len_idx, clen_bytes, tmp_len, num_len);
clen = tmp_len + num_len;
}
int hlen = head_bytes.length;
int tlen = type_bytes == null ? 0 : type_bytes.length;
int conn_len = conn_bytes == null ? 0 : conn_bytes.length;
int dlen = date_bytes == null ? 0 : date_bytes.length + DATE.length;
int len = hlen + clen + dlen + 2 + conn_len + tlen;
int header_size = 0;
int cookie_size = 0;
IntMap headers = f.getResponseHeaders();
if (headers != null) {
for (headers.scan(); headers.hasNext();) {
byte[] k = HttpHeader.get(headers.nextKey()).getBytes();
byte[] v = headers.value();
header_size++;
encode_bytes_array.add(k);
encode_bytes_array.add(v);
len += 4;
len += k.length;
len += v.length;
}
}
List cookieList = f.getCookieList();
if (cookieList != null) {
for (Cookie c : cookieList) {
byte[] bytes = c.toString().getBytes();
cookie_size++;
encode_bytes_array.add(bytes);
len += SET_COOKIE.length + 2;
len += bytes.length;
}
}
len += 2;
if (isArray) {
len += write_size;
}
ByteBuf buf;
if (Develop.BUF_DEBUG) {
buf = ch.allocate();
} else {
buf = ch.alloc().allocate(len);
}
buf.put(head_bytes);
buf.put(clen_bytes, 0, clen);
if (conn_bytes != null) {
buf.put(conn_bytes);
}
if (type_bytes != null) {
buf.put(type_bytes);
}
if (date_bytes != null) {
buf.put(DATE);
buf.put(date_bytes);
}
buf.putByte(R);
buf.putByte(N);
int j = 0;
if (header_size > 0) {
for (int i = 0; i < header_size; i++) {
buf.put(encode_bytes_array.get(j++));
buf.putByte((byte) ':');
buf.putByte(SPACE);
buf.put(encode_bytes_array.get(j++));
buf.putByte(R);
buf.putByte(N);
}
}
if (cookie_size > 0) {
for (int i = 0; i < cookie_size; i++) {
buf.put(SET_COOKIE);
buf.put(encode_bytes_array.get(j++));
buf.putByte(R);
buf.putByte(N);
}
}
buf.putByte(R);
buf.putByte(N);
if (write_size > 0) {
if (isArray) {
buf.put(contentArray);
} else {
ch.write(buf.flip());
ch.write(contentBuf);
return null;
}
}
return buf.flip();
}
public int getBodyLimit() {
return bodyLimit;
}
public int getHeaderLimit() {
return headerLimit;
}
public int getHttpFrameStackSize() {
return httpFrameBuffer;
}
@Override
public String getProtocolId() {
return "HTTP1.1";
}
@Override
public int headerLength() {
return 0;
}
int onHeaderReadComplete(HttpFrame f) throws IOException {
int contentLength = 0;
String clength = f.getRequestHeader(Content_Length);
String ctype = f.getRequestHeader(Content_Type);
String cookie = f.getRequestHeader(Cookie);
f.setForm(ctype == null ? false : ctype.startsWith("multipart/form-data;"));
if (!Util.isNullOrBlank(clength)) {
contentLength = Integer.parseInt(clength);
f.setContentLength(contentLength);
}
if (!Util.isNullOrBlank(cookie)) {
parse_cookies(f, cookie);
}
if (contentLength < 1) {
return decode_state_complate;
} else {
if (contentLength > bodyLimit) {
throw OVER_LIMIT;
}
return decode_state_body;
}
}
private void parse_cookies(HttpFrame f, String line) {
Map cookies = f.getCookies();
if (cookies == null) {
cookies = new HashMap<>();
f.setCookies(cookies);
}
parse_kv(cookies, line, 0, line.length(), '=', ';');
}
void parse_kv(Map map, CharSequence line, int start, int end, char kvSplitor,
char eSplitor) {
int state_findKey = 0;
int state_findValue = 1;
int state = state_findKey;
int count = end;
int i = start;
int ks = start;
int vs = 0;
CharSequence key = null;
CharSequence value = null;
for (; i != count;) {
char c = line.charAt(i++);
if (state == state_findKey) {
if (c == kvSplitor) {
key = line.subSequence(ks, i - 1);
state = state_findValue;
vs = i;
continue;
}
} else if (state == state_findValue) {
if (c == eSplitor) {
value = line.subSequence(vs, i - 1);
state = state_findKey;
ks = i;
map.put((String) key, (String) value);
continue;
}
}
}
if (state == state_findValue && end > vs) {
map.put((String) key, (String) line.subSequence(vs, end));
}
}
protected void parse_line_one(HttpFrame f, CharSequence line) {
if (line.charAt(0) == 'G' && line.charAt(1) == 'E' && line.charAt(2) == 'T') {
f.setMethod(HttpMethod.GET);
parseRequestURL(f, 4, line);
} else {
f.setMethod(HttpMethod.POST);
parseRequestURL(f, 5, line);
}
}
protected void parseRequestURL(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));
}
}
@Override
public void release(NioEventLoop eventLoop, Frame frame) {
eventLoop.releaseFrame(FRAME_BUFFER_KEY, frame);
}
class DBsH {
long time;
byte[] value;
}
static class HttpDateTimeClock implements Runnable {
volatile Thread owner;
volatile boolean running;
volatile long time;
@Override
public void run() {
owner = Thread.currentThread();
running = true;
for (; running;) {
time = System.currentTimeMillis();
Util.sleep(1000);
}
}
void stop() {
running = false;
Thread t = owner;
if (t != null) {
t.interrupt();
}
}
}
static byte[] b(String s) {
return s.getBytes();
}
private static int findN(ByteBuf src, int p) {
src.absPos(p + 1);
p--;
if (src.absByte(p) == R) {
return p;
} else {
return ++p;
}
}
@SuppressWarnings("unchecked")
static List getEncodeBytesArray(FastThreadLocal l) {
return (List) l.getList(encode_bytes_arrays_index);
}
protected static String parseBoundary(String contentType) {
int index = KMP_BOUNDARY.match(contentType);
if (index != -1) {
return contentType.substring(index + 9);
}
return null;
}
private static boolean read_line(StringBuilder line, ByteBuf src, int length, int limit)
throws IOException {
src.markP();
int maybeRead = limit - length;
if (src.remaining() > maybeRead) {
int count = src.absPos() + maybeRead;
for (int i = src.absPos(); i < count; i++) {
byte b = src.absByte(i);
if (b == N) {
int p = line.length() - 1;
if (line.charAt(p) == R) {
line.setLength(p);
}
src.absPos(i + 1);
return true;
} else {
line.append((char) (b & 0xff));
}
}
throw OVER_LIMIT;
} else {
int count = src.absPos() + src.remaining();
for (int i = src.absPos(); i < count; i++) {
byte b = src.absByte(i);
if (b == N) {
int p = line.length() - 1;
if (line.charAt(p) == R) {
line.setLength(p);
}
src.absPos(i + 1);
return true;
} else {
line.append((char) (b & 0xff));
}
}
}
src.resetP();
return false;
}
private static int read_line_range(ByteBuf src, int length, int limit) throws IOException {
src.markP();
int p;
int maybeRead = limit - length;
if (src.remaining() > maybeRead) {
p = src.indexOf(N, src.absPos(), maybeRead);
if (p == -1) {
throw OVER_LIMIT;
}
} else {
p = src.indexOf(N);
if (p == -1) {
src.resetP();
return -1;
}
}
return findN(src, p);
}
private static boolean startWith(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.absByte(ps + i) != match[i]) {
return false;
}
}
return true;
}
}