Skip to content

Commit

Permalink
bugfix #1 #2
Browse files Browse the repository at this point in the history
  • Loading branch information
guonaihong committed Feb 2, 2021
1 parent 6d6bedc commit ce71109
Show file tree
Hide file tree
Showing 5 changed files with 319 additions and 27 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ make example.run
```
### return value
* err != nil 错误
* sucess== len(data) 所有数据成功解析
* sucess == len(data) 所有数据成功解析
* sucess < len(data) 只解析部分数据,未解析的数据需再送一次

### 吞吐量
Expand Down
72 changes: 49 additions & 23 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ func (p *Parser) Init(t ReqOrRsp) {
// 响应行
// https://tools.ietf.org/html/rfc7230#section-3.1.2 状态行
// status-line = HTTP-version SP status-code SP reason-phrase CRLF
// 注意:
// 调用必须保证status-line的数据包是完整的,不需要担心读不全status-line的情况基本不会发生
// (mtu 大约是1530左右,而status-line不会超过1个mtu)。

// 请求行
// https://tools.ietf.org/html/rfc7230#section-3.1.1
// method SP request-target SP HTTP-version CRLF

// 设计思路修改
// 为了适应流量解析的场景,状态机的状态会更碎一点

func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error) {
currState := p.currState

Expand All @@ -107,9 +107,11 @@ func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error)

i := 0
c := byte(0)

for ; i < len(buf); i++ {
c = buf[i]
next:

reExec:
switch currState {
case startReqOrRsp:
if c == 'H' {
Expand All @@ -120,11 +122,12 @@ func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error)
continue
}
currState = startReq
goto next
fallthrough
case startReq:
if token[c] == 0 {
return 0, ErrReqMethod
}

currState = reqMethod
if setting.MessageBegin != nil {
setting.MessageBegin()
Expand Down Expand Up @@ -157,9 +160,9 @@ func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error)

case reqURLAfterSP:
if c != ' ' && c != '\t' {
currState = reqHTTP
currState = reqHTTPVersion
}
case reqHTTP:
case reqHTTPVersion:
if c == '\r' {
currState = reqRequestLineAlomstDone
}
Expand Down Expand Up @@ -218,7 +221,7 @@ func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error)
}

currState = rspStatus
goto next
goto reExec
case rspStatus:
start := i

Expand All @@ -234,6 +237,7 @@ func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error)
return 0, ErrRspStatusLine
}

//TODO单独状态
switch {
case buf[end] == '\r' && buf[end+1] == '\n':
i = end + 1
Expand All @@ -258,6 +262,8 @@ func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error)
if int32(len(buf[i:])) > p.maxHeaderSize {
return 0, ErrHeaderOverflow
}

p.currState = headerField
return i, nil
}

Expand Down Expand Up @@ -286,20 +292,19 @@ func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error)
currState = headerValueDiscardWs
case headerValueDiscardWs:
// 只跳过一个' ' or '\t'
currState = headerValue
if c == ' ' || c == '\t' {
currState = headerValue
continue
}

currState = headerValue

// 解析http value
case headerValue:
end := bytes.IndexAny(buf[i:], "\r\n")
if end == -1 {
if int32(len(buf[i:])) > p.maxHeaderSize {
return 0, ErrHeaderOverflow
}
p.currState = headerValueDiscardWs
return i, nil
}

Expand All @@ -326,15 +331,28 @@ func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error)
p.hasTransferEncoding = true
}

// TODO 这里的\r\n 可以单独拎一个状态出来
i += end
switch {
case buf[i] == '\r' && buf[i+1] == '\n':
i++
case buf[i] == '\r' || buf[i] == '\n':

c = buf[i]
currState = headerValueStartOWS
// 进入header value的OWS
fallthrough
case headerValueStartOWS:
if c == '\r' {
currState = headerValueOWS
continue
}

// 不是'\r'的情况,继续往下判断
fallthrough
case headerValueOWS:
currState = headerField
if c == '\n' {
continue
}

// 不是'\n'也许是headerField的数据
goto reExec

case headerDone:
if c != '\n' {
Expand Down Expand Up @@ -447,12 +465,15 @@ func (p *Parser) Execute(setting *Setting, buf []byte) (success int, err error)
currState = chunkedDataDone
case chunkedDataDone:
currState = chunkedSizeStart
//case messageAlmostDone:
// currState = messageDone
//case messageDone:
// if setting.MessageComplete != nil {
// setting.MessageComplete()
// }
case messageDone:
// 规范的chunked包是以\r\n结尾的
if c == '\r' || c == '\n' {
continue
}

currState = startReqOrRsp
p.Reset()
goto reExec
}

}
Expand All @@ -467,7 +488,7 @@ func (p *Parser) SetMaxHeaderSize(size int32) {
}

func (p *Parser) Reset() {
//p.currState =
p.currState = startReqOrRsp
p.headerCurrState = hGeneral
p.major = 0
p.minor = 0
Expand All @@ -478,6 +499,11 @@ func (p *Parser) Reset() {
p.hasTransferEncoding = false
}

// debug专用
func (p *Parser) Status() string {
return stateTab[p.currState]
}

func (p *Parser) Eof() bool {
return p.currState == messageDone
}
Expand Down
140 changes: 140 additions & 0 deletions parser_issue_fix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package httparser

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func Test_Isuse1(t *testing.T) {
var data = []byte(
"POST /joyent/http-parser HTTP/1.1\r\n" +
"Host: github.com\r\n" +
"DNT: 1\r\n" +
"Accept-Encoding: gzip, deflate, sdch\r\n" +
"Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n" +
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/39.0.2171.65 Safari/537.36\r\n" +
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9," +
"image/webp,*/*;q=0.8\r\n" +
"Referer: https://github.com/joyent/http-parser\r\n" +
"Connection: keep-alive\r\n" +
"Transfer-Encoding: chunked\r\n" +
"Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n" +

"POST /joyent/http-parser HTTP/1.1\r\n" +
"Host: github.com\r\n" +
"DNT: 1\r\n" +
"Accept-Encoding: gzip, deflate, sdch\r\n" +
"Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n" +
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/39.0.2171.65 Safari/537.36\r\n" +
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9," +
"image/webp,*/*;q=0.8\r\n" +
"Referer: https://github.com/joyent/http-parser\r\n" +
"Connection: keep-alive\r\n" +
"Transfer-Encoding: chunked\r\n" +
"Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n")

body := []byte{}
var setting = Setting{
MessageBegin: func() {
fmt.Println("---- begin")
},
URL: func(buf []byte) {
},
Status: func([]byte) {
// 响应包才需要用到
},
HeaderField: func(buf []byte) {
},
HeaderValue: func(buf []byte) {
},
HeadersComplete: func() {
},
Body: func(buf []byte) {
body = append(body, buf...)
},
MessageComplete: func() {
},
}

p := New(REQUEST)
fmt.Printf("req_len=%d\n", len(data)/2)
// 一个POST 518,一共两个POST,第一次解析600字节,第二次解析剩余的
data1, data2 := data[:600], data[600:]
_, err := p.Execute(&setting, data1)
if err != nil {
panic(err.Error())
}

_, err = p.Execute(&setting, data2)
if err != nil {
panic(err.Error())
}

assert.Equal(t, body, []byte("hello worldhello world"))
p.Reset()

}

func Test_Issue2(t *testing.T) {

var data = []byte(
"POST /joyent/http-parser HTTP/1.1\r\n" +
"Host: github.com\r\n" +
"DNT: 1\r\n" +
"Accept-Encoding: gzip, deflate, sdch\r\n" +
"Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n" +
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/39.0.2171.65 Safari/537.36\r\n" +
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9," +
"image/webp,*/*;q=0.8\r\n" +
"Referer: https://github.com/joyent/http-parser\r\n" +
"Connection: keep-alive\r\n" +
"Transfer-Encoding: chunked\r\n" +
"Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n")

var body []byte
var setting = Setting{
MessageBegin: func() {
},
URL: func(buf []byte) {
},
Status: func([]byte) {
},
HeaderField: func(buf []byte) {
},
HeaderValue: func(buf []byte) {
},
HeadersComplete: func() {
},
Body: func(buf []byte) {
body = append(body, buf...)
},
MessageComplete: func() {
fmt.Println("---- complete")
},
}

p := New(REQUEST)
fmt.Printf("req_len=%d\n", len(data))
// 一个POST 518,一共两个POST,第一次解析600字节,第二次解析剩余的
data1, data2 := data[:300], data[300:]
sucess, err := p.Execute(&setting, data1)
if err != nil {
panic(err.Error())
}

sucess, err = p.Execute(&setting, append(data1[sucess:], data2...))
if err != nil {
panic(err.Error())
}

p.Reset()

}
Loading

0 comments on commit ce71109

Please sign in to comment.