使用 JavaScript 实现一个基于 FSM 的 http 请求头 parser

目标

HTTP/1.1 200 OK
Server: nginx
Date: Sun, 05 Jan 2020 17:39:08 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.10
Content-Length: 6

123Abc

--> parse -->

{
    "reqline": "HTTP/1.1 200 OK",
    "headers": {
       "server": "nginx",
        "date": "Sun, 05 Jan 2020 17:39:08 GMT",
        "content-type": "text/html; charset=UTF-8",
        "connection": "close",
        "vary": "Accept-Encoding",
        "x-powered-by": "PHP/5.6.10",
        "content-length": "6",
     }
    "body": "123Abc"
}

状态机

http parser fsm.jpg

代码实现

var httpParser = {};
(function () {

    function Stream (str) {
        this.pos = 0;
        this.str = str;
    }
    Stream.prototype.hasNext = function () {
        return this.pos < this.str.length;
    }
    Stream.prototype.next =  function () {
        return this.str[this.pos++];
    }

    const STATE = {
        BEGIN : 0,
        LINE_ALMOST_DONE : 1,
        LINE_DONE : 2,
        KEY_DONE : 3,
        HEADER_ALMOST_DONE : 4,
        HEADER_DONE : 5,
        END : 6
    };

    httpParser.state = STATE.BEGIN;

    function updateState(st){
        httpParser.state = st;
    }

    function currentState(){
        return httpParser.state;
    }

    httpParser.parse = function (str) {
        if (str.length < 4) return -1;
        updateState(STATE.BEGIN);
        var result = {};
        result["headers"] = {};
        var lastword = "";
        var word = "";
        var stream = new Stream(str);
        while (stream.hasNext()) {
            var ch = stream.next();
            switch (currentState()) {
                case STATE.BEGIN:
                    // they might be just sending \n instead of \r\n so this would be
                    // the second \n to denote the end of headers
                    if (ch == '\r' || ch == '\n') {
                        updateState(STATE.LINE_ALMOST_DONE);
                        result["reqline"] = word;
                        word = "";
                        break;
                    }
                    word += ch;
                    break;
                case STATE.LINE_ALMOST_DONE:
                    if (ch == '\n') {
                        updateState(STATE.LINE_DONE);
                    }else{
                        return -1;
                    }
                    break;
                case STATE.LINE_DONE:
                    if (ch == ':') {
                        updateState(STATE.KEY_DONE);
                        lastword = word;
                        word = "";
                        break;
                    }
                    if (ch == '\r' || ch == '\n') {
                        updateState(STATE.HEADER_ALMOST_DONE);
                        lastword = "";
                        word = "";
                        break;
                    }
                    word += ch;
                    break;
                case STATE.KEY_DONE:
                    if (ch == '\r' || ch == '\n') {
                        updateState(STATE.LINE_ALMOST_DONE);
                        result["headers"][lastword.toLowerCase()] = word;
                        lastword = "";
                        word = "";
                        break;
                    }
                    if (word == "" && ch == ' ') break;
                    word += ch;
                    break;
                case STATE.HEADER_ALMOST_DONE:
                    if (ch == '\n') {
                        updateState(STATE.HEADER_DONE);
                    }else{
                        return -1;
                    }
                    break;
                //Need to optimize
                case STATE.HEADER_DONE:
                    word += ch;
                    while (stream.hasNext()){
                        word += stream.next();
                    }
                    result["body"] = word;
                    word = "";
                    break;
                default:
                    return -2;
            }
        }
        return result;
    }

})();
本文链接: http://frostmiku.com/archives/17/
1 + 8 =
快来做第一个评论的人吧~