All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.firenio.baseio.codec.http11.HttpCodecLite Maven / Gradle / Ivy

There is a newer version: 3.2.9.beta11
Show newest version
/*
 * 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 java.io.IOException;
import java.util.Map;

import com.firenio.baseio.buffer.ByteBuf;
import com.firenio.baseio.collection.IntMap;
import com.firenio.baseio.common.ByteUtil;
import com.firenio.baseio.common.DateUtil;
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 HttpCodecLite extends ProtocolCodec {

    static final byte[]             CONNECTION            = b("\r\nConnection: ");
    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                dbshIndex             = nextIndexedVariablesIndex();
    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 String             FRAME_BUFFER_KEY      = "_HTTP_FRAME_BUFFER_KEY";
    static final String             FRAME_DECODE_KEY      = "_HTTP_FRAME_DECODE_KEY";
    static final HttpDateTimeClock  httpDateTimeClock     = new HttpDateTimeClock();
    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               SPACE                 = ' ';

    private int                     bodyLimit             = 1024 * 512;
    private int                     frameBuffer           = 0;
    private int                     headerLimit           = 1024 * 8;
    private final byte[]            PROTOCOL;

    public HttpCodecLite() {
        this(0);
    }

    public HttpCodecLite(int frameBuffer) {
        this(null, frameBuffer);
    }

    public HttpCodecLite(String server) {
        this(server, 0);
    }

    public HttpCodecLite(String server, int frameBuffer) {
        this.frameBuffer = frameBuffer;
        if (server == null) {
            this.PROTOCOL = b("HTTP/1.1 200 OK\r\nContent-Length: ");
        } else {
            this.PROTOCOL = b("HTTP/1.1 200 OK\r\nServer: " + server + "\r\nContent-Length: ");
        }
    }

    private HttpFrameLite allocHttpFrame(Channel ch) {
        HttpFrameLite f = (HttpFrameLite) ch.getAttribute(FRAME_DECODE_KEY);
        if (f == null) {
            if (frameBuffer > 0) {
                NioEventLoop el = ch.getEventLoop();
                Frame res = el.getFrameFromBuffer(ch, FRAME_BUFFER_KEY, frameBuffer);
                if (res == null) {
                    return new HttpFrameLite();
                } else {
                    return (HttpFrameLite) res.reset();
                }
            }
            return new HttpFrameLite();
        }
        return f;
    }

    @Override
    public Frame decode(Channel ch, ByteBuf src) throws IOException {
        HttpFrameLite f = allocHttpFrame(ch);
        int decode_state = f.getDecodeState();
        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;
        }
    }

    @Override
    protected void doStart() throws Exception {
        Util.exec(httpDateTimeClock, "HttpDateTimeClock");
    }

    @Override
    protected void doStop() {
        httpDateTimeClock.stop();
    }

    @Override
    public ByteBuf encode(final Channel ch, Frame frame) throws IOException {
        HttpFrameLite f = (HttpFrameLite) frame;
        FastThreadLocal l = FastThreadLocal.get();
        Object content = f.getContent();
        ByteBuf contentBuf = null;
        byte[] contentArray = null;
        byte[] byte32 = l.getBytes32();
        byte[] date_bytes = getHttpDateBytes();
        byte[] conn_bytes = f.getConnection();
        byte[] type_bytes = f.getContentType();
        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;
        }
        int len_idx = Util.valueOf(write_size, byte32);
        int len_len = 32 - len_idx;
        int plen = PROTOCOL.length;
        int dlen = DATE.length;
        int conn_len = conn_bytes == null ? 0 : conn_bytes.length + CONNECTION.length;
        int type_len = type_bytes == null ? 0 : type_bytes.length + CONTENT_TYPE.length;
        int len = plen + len_len + dlen + date_bytes.length + 2 + conn_len + type_len;
        IntMap headers = f.getResponseHeaders();
        if (headers != null) {
            for (headers.scan();headers.hasNext();) {
                byte[] k = HttpHeader.get(headers.nextKey()).getBytes();
                byte[] v = headers.value();
                len += 4;
                len += k.length;
                len += v.length;
            }
        }
        len += 2;
        if (isArray) {
            len += write_size;
        }
        ByteBuf buf = ch.alloc().allocate(len);
        buf.put(PROTOCOL);
        buf.put(byte32, len_idx, len_len);
        buf.put(DATE);
        buf.put(date_bytes);
        if (conn_bytes != null) {
            buf.put(CONNECTION);
            buf.put(conn_bytes);
        }
        if (type_bytes != null) {
            buf.put(CONTENT_TYPE);
            buf.put(type_bytes);
        }
        buf.putByte(R);
        buf.putByte(N);
        if (headers != null) {
            for (headers.scan();headers.hasNext();) {
                byte[] k = HttpHeader.get(headers.nextKey()).getBytes();
                byte[] v = headers.value();
                buf.put(k);
                buf.putByte((byte) ':');
                buf.putByte(SPACE);
                buf.put(v);
                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;
    }

    private byte[] getHttpDateBytes() {
        DBsH h = (DBsH) FastThreadLocal.get().getIndexedVariable(dbshIndex);
        if (h == null) {
            h = new DBsH();
            FastThreadLocal.get().setIndexedVariable(dbshIndex, h);
        }
        long now = httpDateTimeClock.time;
        if (now > h.time) {
            h.time = now + 1000;
            h.value = DateUtil.get().formatHttpBytes(now);
        }
        return h.value;
    }

    public int getHttpFrameStackSize() {
        return frameBuffer;
    }

    @Override
    public String getProtocolId() {
        return "HTTP1.1";
    }

    @Override
    public int headerLength() {
        return 0;
    }

    public void release(NioEventLoop eventLoop, Frame frame) {
        eventLoop.releaseFrame(FRAME_BUFFER_KEY, frame);
    }

    private 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;
    }

    private class DBsH {
        long   time;
        byte[] value;
    }

    private static class HttpDateTimeClock implements Runnable {

        volatile boolean running;

        volatile long    time;

        @Override
        public void run() {
            running = true;
            for (; running;) {
                time = System.currentTimeMillis();
                Util.sleep(1000);
            }
        }

        void stop() {
            running = false;
            Thread.currentThread().interrupt();
        }

    }

    private 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;
        }
    }

    public static 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));
        }
    }

    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);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy