mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
Fix tests
This commit is contained in:
+2
-2
@@ -1,3 +1,3 @@
|
||||
language: go
|
||||
go: 1.5.1
|
||||
script: sudo -E bash -c "source /etc/profile && eval '$(gimme 1.5.1)' && export GOPATH=$HOME/gopath:$GOPATH && go get && GORACE='halt_on_error=1' go test ./... -v -timeout 60s -race"
|
||||
go: 1.6
|
||||
script: sudo -E bash -c "source /etc/profile && eval '$(gimme 1.6)' && export GOPATH=$HOME/gopath:$GOPATH && go get && GORACE='halt_on_error=1' go test ./... -v -timeout 60s -race"
|
||||
@@ -18,7 +18,5 @@ ADD . /go/src/github.com/buger/gor/
|
||||
|
||||
RUN javac -cp /tmp/commons-io-2.4/commons-io-2.4.jar ./examples/middleware/echo.java
|
||||
|
||||
RUN apt-get install graphviz -y
|
||||
|
||||
RUN go get -u github.com/golang/lint/golint
|
||||
RUN go get
|
||||
@@ -1,56 +1,59 @@
|
||||
SOURCE = emitter.go gor.go gor_stat.go input_dummy.go input_file.go input_raw.go input_tcp.go limiter.go output_dummy.go output_file.go input_http.go output_http.go output_tcp.go plugins.go settings.go test_input.go elasticsearch.go http_modifier.go http_modifier_settings.go http_client.go middleware.go protocol.go
|
||||
|
||||
SOURCE_PATH = /gopath/src/github.com/buger/gor/
|
||||
SOURCE_PATH = /go/src/github.com/buger/gor/
|
||||
RUN = docker run -v `pwd`:$(SOURCE_PATH) -p 0.0.0.0:8000:8000 -t -i gor
|
||||
|
||||
release: release-x86 release-x64
|
||||
|
||||
release-x64:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t --env GOOS=linux --env GOARCH=amd64 --env CGO_ENABLED=0 -i gor go build -ldflags "-X main.VERSION $(VERSION)"&& tar -czf gor_$(VERSION)_x64.tar.gz gor && rm gor
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go build -ldflags "-X main.VERSION=$(VERSION)"&& tar -czf gor_$(VERSION)_x64.tar.gz gor && rm gor
|
||||
|
||||
release-x86:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t --env GOOS=linux --env GOARCH=386 --env CGO_ENABLED=0 -i gor go build -ldflags "-X main.VERSION $(VERSION)" && tar -czf gor_$(VERSION)_x86.tar.gz gor && rm gor
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i --env GOOS=linux --env GOARCH=386 --env CGO_ENABLED=0 -i gor go build -ldflags "-X main.VERSION=$(VERSION)" && tar -czf gor_$(VERSION)_x86.tar.gz gor && rm gor
|
||||
|
||||
dbuild:
|
||||
build:
|
||||
docker build -t gor .
|
||||
|
||||
|
||||
profile:
|
||||
go build && ./gor --output-http="http://localhost:9000" --input-dummy 0 --input-raw :9000 --input-http :9000 --memprofile=./mem.out --cpuprofile=./cpu.out --stats --output-http-stats --output-http-timeout 100ms
|
||||
|
||||
dlint:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i --env GORACE="halt_on_error=1" gor golint $(PKG)
|
||||
lint:
|
||||
$(RUN) golint $(PKG)
|
||||
|
||||
drace:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i --env GORACE="halt_on_error=1" gor go test ./... $(ARGS) -v -race -timeout 15s
|
||||
race:
|
||||
$(RUN) go test ./... $(ARGS) -v -race -timeout 15s
|
||||
|
||||
dtest:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go test ./... -timeout 60s $(ARGS) -v
|
||||
test:
|
||||
$(RUN) go test ./... -timeout 10s $(ARGS) -v
|
||||
|
||||
dcover:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i --env GORACE="halt_on_error=1" gor go test $(ARGS) -race -v -timeout 15s -coverprofile=coverage.out
|
||||
testone:
|
||||
$(RUN) go test ./... -timeout 4s -run $(TEST) $(ARGS) -v
|
||||
|
||||
cover:
|
||||
$(RUN) go test $(ARGS) -race -v -timeout 15s -coverprofile=coverage.out
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
dfmt:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go fmt ./...
|
||||
fmt:
|
||||
$(RUN) go fmt ./...
|
||||
|
||||
dvet:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go vet
|
||||
vet:
|
||||
$(RUN) go vet
|
||||
|
||||
dbench:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go test -v -run NOT_EXISTING -bench HTTP
|
||||
bench:
|
||||
$(RUN) go test -v -run NOT_EXISTING -bench HTTP
|
||||
|
||||
# Used mainly for debugging, because docker container do not have access to parent machine ports
|
||||
drun:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go run $(SOURCE) --input-dummy=0 --output-http="http://localhost:9000" --input-raw :9000 --input-http :9000 --verbose --debug --middleware "./examples/middleware/echo.sh"
|
||||
run:
|
||||
$(RUN) go run $(SOURCE) --input-dummy=0 --output-http="http://localhost:9000" --input-raw :9000 --input-http :9000 --verbose --debug --middleware "./examples/middleware/echo.sh"
|
||||
|
||||
drun-2:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go run $(SOURCE) --input-file ./fixtures/requests.gor --output-dummy=0
|
||||
run-2:
|
||||
$(RUN) go run $(SOURCE) --input-file ./fixtures/requests.gor --output-dummy=0
|
||||
|
||||
drecord:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go run $(SOURCE) --input-dummy=0 --output-file=requests.gor --verbose --debug
|
||||
record:
|
||||
$(RUN) go run $(SOURCE) --input-dummy=0 --output-file=requests.gor --verbose --debug
|
||||
|
||||
dreplay:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -t -i gor go run $(SOURCE) --input-file=requests.bin --output-tcp=:9000 --verbose -h
|
||||
replay:
|
||||
$(RUN) go run $(SOURCE) --input-file=requests.bin --output-tcp=:9000 --verbose -h
|
||||
|
||||
dbash:
|
||||
docker run -v `pwd`:$(SOURCE_PATH) -p 0.0.0.0:8000:8000 -t -i gor /bin/bash
|
||||
bash:
|
||||
$(RUN) /bin/bash
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ func (i *RAWInput) listen(address string) {
|
||||
log.Fatal("input-raw: error while parsing address", err)
|
||||
}
|
||||
|
||||
i.listener = raw.NewListener(host, port, i.expire, true)
|
||||
i.listener = raw.NewListener(host, port, i.expire)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
+2
-3
@@ -68,13 +68,12 @@ func TestInputRAW100Expect(t *testing.T) {
|
||||
wg := new(sync.WaitGroup)
|
||||
quit := make(chan int)
|
||||
|
||||
fileContent, _ := ioutil.ReadFile("README.md")
|
||||
fileContent, _ := ioutil.ReadFile("LICENSE.txt")
|
||||
|
||||
// Origing and Replay server initialization
|
||||
origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
ioutil.ReadAll(r.Body)
|
||||
|
||||
wg.Done()
|
||||
}))
|
||||
defer origin.Close()
|
||||
@@ -119,7 +118,7 @@ func TestInputRAW100Expect(t *testing.T) {
|
||||
|
||||
// Origin + Response/Request Test Output + Request Http Output
|
||||
wg.Add(4)
|
||||
curl := exec.Command("curl", "http://"+originAddr, "--data-binary", "@README.md")
|
||||
curl := exec.Command("curl", "http://"+originAddr, "--data-binary", "@LICENSE.txt")
|
||||
err := curl.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
+4
-3
@@ -148,6 +148,7 @@ func TestEchoMiddleware(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTokenMiddleware(t *testing.T) {
|
||||
Settings.verbose = true
|
||||
var resp, token []byte
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
@@ -194,15 +195,15 @@ func TestTokenMiddleware(t *testing.T) {
|
||||
// Should receive 2 requests from original + 2 from replayed
|
||||
wg.Add(4)
|
||||
|
||||
client := NewHTTPClient(from.URL, &HTTPClientConfig{Debug: false})
|
||||
client := NewHTTPClient(from.URL, &HTTPClientConfig{Debug: true})
|
||||
|
||||
// Sending traffic to original service
|
||||
resp, _ = client.Get("/token")
|
||||
token = proto.Body(resp)
|
||||
|
||||
// When delay is too smal, middleware does not always rewrite requests in time
|
||||
// Hopefuly client will have delay more then 10ms :)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// Hopefuly client will have delay more then 100ms :)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
resp, _ = client.Get("/secure?token=" + string(token))
|
||||
if !bytes.Equal(proto.Status(resp), []byte("202")) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
// Listener handle traffic capture
|
||||
type Listener struct {
|
||||
// buffer of TCPMessages waiting to be send
|
||||
// ID -> TCPMessage
|
||||
messages map[string]*TCPMessage
|
||||
|
||||
// Expect: 100-continue request is send in 2 tcp messages
|
||||
@@ -34,8 +35,12 @@ type Listener struct {
|
||||
// To get ACK of second message we need to compute its Seq and wait for them message
|
||||
seqWithData map[uint32]uint32
|
||||
|
||||
// Ack -> Req
|
||||
respAliases map[uint32]*request
|
||||
|
||||
// Ack -> ID
|
||||
respWithoutReq map[uint32]string
|
||||
|
||||
// Messages ready to be send to client
|
||||
packetsChan chan *TCPPacket
|
||||
|
||||
@@ -47,8 +52,6 @@ type Listener struct {
|
||||
|
||||
messageExpire time.Duration
|
||||
|
||||
captureResponse bool
|
||||
|
||||
conn net.PacketConn
|
||||
quit chan bool
|
||||
}
|
||||
@@ -59,8 +62,8 @@ type request struct {
|
||||
}
|
||||
|
||||
// NewListener creates and initializes new Listener object
|
||||
func NewListener(addr string, port string, expire time.Duration, captureResponse bool) (l *Listener) {
|
||||
l = &Listener{captureResponse: captureResponse}
|
||||
func NewListener(addr string, port string, expire time.Duration) (l *Listener) {
|
||||
l = &Listener{}
|
||||
|
||||
l.packetsChan = make(chan *TCPPacket, 10000)
|
||||
l.messagesChan = make(chan *TCPMessage, 10000)
|
||||
@@ -70,6 +73,7 @@ func NewListener(addr string, port string, expire time.Duration, captureResponse
|
||||
l.ackAliases = make(map[uint32]uint32)
|
||||
l.seqWithData = make(map[uint32]uint32)
|
||||
l.respAliases = make(map[uint32]*request)
|
||||
l.respWithoutReq = make(map[uint32]string)
|
||||
|
||||
l.addr = addr
|
||||
_port, _ := strconv.Atoi(port)
|
||||
@@ -82,7 +86,11 @@ func NewListener(addr string, port string, expire time.Duration, captureResponse
|
||||
l.messageExpire = expire
|
||||
|
||||
go l.listen()
|
||||
go l.readRAWSocket()
|
||||
|
||||
// Special case for testing
|
||||
if l.port != 0 {
|
||||
go l.readRAWSocket()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -93,10 +101,17 @@ func (t *Listener) listen() {
|
||||
for {
|
||||
select {
|
||||
case <-t.quit:
|
||||
t.conn.Close()
|
||||
if t.conn != nil {
|
||||
t.conn.Close()
|
||||
}
|
||||
return
|
||||
// We need to use channels to process each packet to avoid data races
|
||||
case packet := <-t.packetsChan:
|
||||
maxLen := len(packet.Data)
|
||||
if maxLen > 500 {
|
||||
maxLen = 500
|
||||
}
|
||||
|
||||
t.processTCPPacket(packet)
|
||||
|
||||
case <- gcTicker:
|
||||
@@ -115,14 +130,30 @@ func (t *Listener) dispatchMessage(message *TCPMessage) {
|
||||
delete(t.ackAliases, message.Ack)
|
||||
delete(t.messages, message.ID)
|
||||
|
||||
if !message.IsIncoming {
|
||||
if message.IsIncoming {
|
||||
// If there were response before request
|
||||
if respID, ok := t.respWithoutReq[message.ResponseAck]; ok {
|
||||
if resp, rok := t.messages[respID]; rok {
|
||||
if resp.RequestAck == 0 {
|
||||
resp.RequestAck = message.Ack
|
||||
resp.RequestStart = message.Start
|
||||
|
||||
if resp.IsFinished() {
|
||||
defer t.dispatchMessage(resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete(t.respAliases, message.Ack)
|
||||
delete(t.respWithoutReq, message.Ack)
|
||||
|
||||
// Do not track responses which have no associated requests
|
||||
if message.RequestAck == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.messagesChan <- message
|
||||
}
|
||||
|
||||
@@ -146,7 +177,6 @@ func (t *Listener) readRAWSocket() {
|
||||
if strings.HasSuffix(err.Error(), "closed network connection") {
|
||||
return
|
||||
} else {
|
||||
log.Println("Raw listener error:", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -171,7 +201,7 @@ func (t *Listener) isValidPacket(buf []byte) bool {
|
||||
srcPort := binary.BigEndian.Uint16(buf[0:2])
|
||||
|
||||
// Because RAW_SOCKET can't be bound to port, we have to control it by ourself
|
||||
if destPort == t.port || (t.captureResponse && srcPort == t.port) {
|
||||
if destPort == t.port || srcPort == t.port {
|
||||
// Get the 'data offset' (size of the TCP header in 32-bit words)
|
||||
dataOffset := (buf[12] & 0xF0) >> 4
|
||||
|
||||
@@ -206,6 +236,7 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) {
|
||||
|
||||
if parentAck, ok := t.seqWithData[packet.Seq]; ok {
|
||||
t.ackAliases[packet.Ack] = parentAck
|
||||
packet.Ack = parentAck
|
||||
delete(t.seqWithData, packet.Seq)
|
||||
}
|
||||
|
||||
@@ -215,7 +246,7 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) {
|
||||
|
||||
var responseRequest *request
|
||||
|
||||
if t.captureResponse && !isIncoming {
|
||||
if !isIncoming {
|
||||
responseRequest, _ = t.respAliases[packet.Ack]
|
||||
}
|
||||
|
||||
@@ -224,12 +255,16 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) {
|
||||
message, ok := t.messages[mID]
|
||||
|
||||
if !ok {
|
||||
message = NewTCPMessage(mID, packet.Ack, isIncoming)
|
||||
message = NewTCPMessage(mID, packet.Seq, packet.Ack, isIncoming)
|
||||
t.messages[mID] = message
|
||||
|
||||
if !isIncoming && responseRequest != nil {
|
||||
message.RequestStart = responseRequest.start
|
||||
message.RequestAck = responseRequest.ack
|
||||
if !isIncoming {
|
||||
if responseRequest != nil {
|
||||
message.RequestStart = responseRequest.start
|
||||
message.RequestAck = responseRequest.ack
|
||||
} else {
|
||||
t.respWithoutReq[packet.Ack] = mID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,21 +272,36 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) {
|
||||
if len(packet.Data) > 4 && bytes.Equal(packet.Data[0:4], bPOST) {
|
||||
// reading last 20 bytes (not counting CRLF): last header value (if no body presented)
|
||||
if bytes.Equal(packet.Data[len(packet.Data)-24:len(packet.Data)-4], bExpect100ContinueCheck) {
|
||||
t.seqWithData[packet.Seq+uint32(len(packet.Data))] = packet.Ack
|
||||
seq := packet.Seq+uint32(len(packet.Data))
|
||||
t.seqWithData[seq] = packet.Ack
|
||||
|
||||
// In case if sequence packet came first
|
||||
for _id, m := range t.messages {
|
||||
if m.Seq == seq {
|
||||
t.ackAliases[m.Ack] = packet.Ack
|
||||
|
||||
for _, pkt := range m.packets {
|
||||
message.AddPacket(pkt)
|
||||
}
|
||||
|
||||
delete(t.messages, _id)
|
||||
}
|
||||
}
|
||||
|
||||
// Removing `Expect: 100-continue` header
|
||||
packet.Data = append(packet.Data[:len(packet.Data)-24], packet.Data[len(packet.Data)-2:]...)
|
||||
}
|
||||
}
|
||||
|
||||
if t.captureResponse && isIncoming {
|
||||
if isIncoming {
|
||||
// If message have multiple packets, delete previous alias
|
||||
if len(message.packets) > 0 {
|
||||
delete(t.respAliases, message.ResponseAck)
|
||||
}
|
||||
|
||||
responseAck := packet.Seq + uint32(len(packet.Data))
|
||||
responseAck := packet.Seq + uint32(message.BodySize()) + uint32(len(packet.Data))
|
||||
t.respAliases[responseAck] = &request{message.Start, message.Ack}
|
||||
|
||||
message.ResponseAck = responseAck
|
||||
}
|
||||
|
||||
@@ -259,7 +309,7 @@ func (t *Listener) processTCPPacket(packet *TCPPacket) {
|
||||
message.AddPacket(packet)
|
||||
|
||||
// If message contains only single packet immediately dispatch it
|
||||
if !message.IsMultipart() {
|
||||
if message.IsFinished() {
|
||||
t.dispatchMessage(message)
|
||||
}
|
||||
}
|
||||
@@ -271,6 +321,8 @@ func (t *Listener) Receive() *TCPMessage {
|
||||
|
||||
func (t *Listener) Close() {
|
||||
close(t.quit)
|
||||
t.conn.Close()
|
||||
if t.conn != nil {
|
||||
t.conn.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package rawSocket
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
"bytes"
|
||||
_ "log"
|
||||
)
|
||||
|
||||
func TestRawListenerInput(t *testing.T) {
|
||||
var req, resp *TCPMessage
|
||||
|
||||
listener := NewListener("", "0", 10 * time.Millisecond)
|
||||
defer listener.Close()
|
||||
|
||||
reqPacket := buildPacket(true, 1, 1, []byte("GET / HTTP/1.1"))
|
||||
|
||||
listener.packetsChan <- reqPacket
|
||||
|
||||
respAck := reqPacket.Seq + uint32(len(reqPacket.Data))
|
||||
respPacket := buildPacket(false, respAck, reqPacket.Seq + 1, []byte("HTTP/1.1 200 OK"))
|
||||
listener.packetsChan <- respPacket
|
||||
|
||||
|
||||
select {
|
||||
case req = <- listener.messagesChan:
|
||||
case <- time.After(time.Millisecond):
|
||||
t.Error("Should return respose immediately")
|
||||
return
|
||||
}
|
||||
|
||||
if !req.IsIncoming {
|
||||
t.Error("Should be request")
|
||||
}
|
||||
|
||||
select {
|
||||
case resp = <- listener.messagesChan:
|
||||
case <- time.After(time.Millisecond):
|
||||
t.Error("Should return response immediately")
|
||||
return
|
||||
}
|
||||
|
||||
if resp.IsIncoming {
|
||||
t.Error("Should be response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawListenerResponse(t *testing.T) {
|
||||
var req, resp *TCPMessage
|
||||
|
||||
listener := NewListener("", "0", 10 * time.Millisecond)
|
||||
defer listener.Close()
|
||||
|
||||
reqPacket := buildPacket(true, 1, 1, []byte("GET / HTTP/1.1"))
|
||||
respPacket := buildPacket(false, 1 + uint32(len(reqPacket.Data)), 2, []byte("HTTP/1.1 200 OK"))
|
||||
|
||||
// If response packet comes before request
|
||||
listener.packetsChan <- respPacket
|
||||
listener.packetsChan <- reqPacket
|
||||
|
||||
select {
|
||||
case req = <- listener.messagesChan:
|
||||
case <- time.After(time.Millisecond):
|
||||
t.Error("Should return respose immediately")
|
||||
return
|
||||
}
|
||||
|
||||
if !req.IsIncoming {
|
||||
t.Error("Should be request")
|
||||
}
|
||||
|
||||
select {
|
||||
case resp = <- listener.messagesChan:
|
||||
case <- time.After(time.Millisecond):
|
||||
t.Error("Should return response immediately")
|
||||
return
|
||||
}
|
||||
|
||||
if resp.IsIncoming {
|
||||
t.Error("Should be response")
|
||||
}
|
||||
|
||||
if !bytes.Equal(resp.UUID(), req.UUID()) {
|
||||
t.Error("Resp and Req UUID should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawListener100Continue(t *testing.T) {
|
||||
var req, resp *TCPMessage
|
||||
|
||||
listener := NewListener("", "0", 10 * time.Millisecond)
|
||||
defer listener.Close()
|
||||
|
||||
reqPacket1 := buildPacket(true, 1, 1, []byte("POST / HTTP/1.1\r\nContent-Length: 2\r\nExpect: 100-continue\r\n\r\n"))
|
||||
|
||||
// Packet with data have different Seq
|
||||
reqPacket2 := buildPacket(true, 2, reqPacket1.Seq + uint32(len(reqPacket1.Data)), []byte("a"))
|
||||
reqPacket3 := buildPacket(true, 2, reqPacket2.Seq + 1, []byte("b"))
|
||||
|
||||
respPacket1 := buildPacket(false, 10, 3, []byte("HTTP/1.1 100 Continue\r\n"))
|
||||
|
||||
// panic(int(uint32(len(reqPacket1.Data)) + uint32(len(reqPacket2.Data)) + uint32(len(reqPacket3.Data))))
|
||||
respPacket2 := buildPacket(false, reqPacket3.Seq + 2 /* len of data */, 2, []byte("HTTP/1.1 200 OK\r\n"))
|
||||
|
||||
listener.processTCPPacket(reqPacket1)
|
||||
listener.processTCPPacket(reqPacket2)
|
||||
listener.processTCPPacket(reqPacket3)
|
||||
|
||||
listener.processTCPPacket(respPacket1)
|
||||
listener.processTCPPacket(respPacket2)
|
||||
|
||||
select {
|
||||
case req = <- listener.messagesChan:
|
||||
break
|
||||
case <- time.After(11 * time.Millisecond):
|
||||
t.Error("Should return response after expire time")
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(req.Bytes(), []byte("POST / HTTP/1.1\r\nContent-Length: 2\r\n\r\nab")) {
|
||||
t.Error("Should receive full message", string(req.Bytes()))
|
||||
}
|
||||
|
||||
if !req.IsIncoming {
|
||||
t.Error("Should be request")
|
||||
}
|
||||
|
||||
select {
|
||||
case resp = <- listener.messagesChan:
|
||||
break
|
||||
case <- time.After(100 * time.Millisecond):
|
||||
t.Error("Should return response after expire time")
|
||||
return
|
||||
}
|
||||
|
||||
if resp.IsIncoming {
|
||||
t.Error("Should be response")
|
||||
}
|
||||
|
||||
if !bytes.Equal(resp.UUID(), req.UUID()) {
|
||||
t.Error("Resp and Req UUID should be equal")
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
// Message is received if we didn't receive any packets for 2000ms
|
||||
type TCPMessage struct {
|
||||
ID string // Message ID
|
||||
Seq uint32
|
||||
Ack uint32
|
||||
ResponseAck uint32
|
||||
RequestStart time.Time
|
||||
@@ -32,8 +33,8 @@ type TCPMessage struct {
|
||||
}
|
||||
|
||||
// NewTCPMessage pointer created from a Acknowledgment number and a channel of messages readuy to be deleted
|
||||
func NewTCPMessage(ID string, Ack uint32, IsIncoming bool) (msg *TCPMessage) {
|
||||
msg = &TCPMessage{ID: ID, Ack: Ack, IsIncoming: IsIncoming}
|
||||
func NewTCPMessage(ID string, Seq, Ack uint32, IsIncoming bool) (msg *TCPMessage) {
|
||||
msg = &TCPMessage{ID: ID, Seq: Seq, Ack: Ack, IsIncoming: IsIncoming}
|
||||
msg.Start = time.Now()
|
||||
|
||||
return
|
||||
@@ -48,8 +49,12 @@ func (t *TCPMessage) Bytes() (output []byte) {
|
||||
return output
|
||||
}
|
||||
|
||||
// Size returns total size of message
|
||||
func (t *TCPMessage) Size() (size int) {
|
||||
// Size returns total body size
|
||||
func (t *TCPMessage) BodySize() (size int) {
|
||||
if len(t.packets) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
size += len(proto.Body(t.packets[0].Data))
|
||||
|
||||
for _, p := range t.packets[1:] {
|
||||
@@ -59,6 +64,19 @@ func (t *TCPMessage) Size() (size int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Size returns total size of message
|
||||
func (t *TCPMessage) Size() (size int) {
|
||||
if len(t.packets) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
for _, p := range t.packets {
|
||||
size += len(p.Data)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AddPacket to the message and ensure packet uniqueness
|
||||
// TCP allows that packet can be re-send multiple times
|
||||
func (t *TCPMessage) AddPacket(packet *TCPPacket) {
|
||||
@@ -86,18 +104,14 @@ func (t *TCPMessage) AddPacket(packet *TCPPacket) {
|
||||
}
|
||||
|
||||
// isMultipart returns true if message contains from multiple tcp packets
|
||||
func (t *TCPMessage) IsMultipart() bool {
|
||||
if len(t.packets) > 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *TCPMessage) IsFinished() bool {
|
||||
payload := t.packets[0].Data
|
||||
m := payload[:4]
|
||||
|
||||
if t.IsIncoming {
|
||||
// If one GET, OPTIONS, or HEAD request
|
||||
if bytes.Equal(m, []byte("GET ")) || bytes.Equal(m, []byte("OPTI")) || bytes.Equal(m, []byte("HEAD")) {
|
||||
return false
|
||||
return true
|
||||
} else {
|
||||
// Sometimes header comes after the body :(
|
||||
if bytes.Equal(m, []byte("POST")) || bytes.Equal(m, []byte("PUT ")) || bytes.Equal(m, []byte("PATC")) {
|
||||
@@ -105,28 +119,38 @@ func (t *TCPMessage) IsMultipart() bool {
|
||||
l, _ := strconv.Atoi(string(length))
|
||||
|
||||
// If content-length equal current body length
|
||||
if l > 0 && l == t.Size() {
|
||||
return false
|
||||
if l > 0 && l == t.BodySize() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Request not found
|
||||
// Can be because response came first or request request was just missing
|
||||
if t.RequestAck == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if length := proto.Header(payload, []byte("Content-Length")); len(length) > 0 {
|
||||
if length[0] == '0' {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
l, _ := strconv.Atoi(string(length))
|
||||
|
||||
// If content-length equal current body length
|
||||
if l > 0 && l == t.Size() {
|
||||
return false
|
||||
if l > 0 && l == t.BodySize() {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if enc := proto.Header(payload, []byte("Transfer-Encoding")); len(enc) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *TCPMessage) UUID() []byte {
|
||||
@@ -146,3 +170,4 @@ func (t *TCPMessage) UUID() []byte {
|
||||
|
||||
return uuid
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package rawSocket
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"net"
|
||||
"strconv"
|
||||
"bytes"
|
||||
_ "log"
|
||||
)
|
||||
|
||||
func buildPacket(isIncoming bool, Ack, Seq uint32, Data []byte) (packet *TCPPacket) {
|
||||
packet = &TCPPacket{
|
||||
Addr: &net.IPAddr{net.IP{}, ""},
|
||||
Ack: Ack,
|
||||
Seq: Seq,
|
||||
Data: Data,
|
||||
}
|
||||
|
||||
// For tests `listening` port is 0
|
||||
if isIncoming {
|
||||
packet.SrcPort = 1
|
||||
} else {
|
||||
packet.DestPort = 1
|
||||
}
|
||||
|
||||
return packet
|
||||
}
|
||||
|
||||
func buildMessage(p *TCPPacket) *TCPMessage {
|
||||
id := p.Addr.String() + strconv.Itoa(int(p.DestPort)) + strconv.Itoa(int(p.Ack))
|
||||
|
||||
isIncoming := false
|
||||
if p.SrcPort == 1 {
|
||||
isIncoming = true
|
||||
}
|
||||
|
||||
m := NewTCPMessage(id, p.Seq, p.Ack, isIncoming)
|
||||
m.AddPacket(p)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func TestTCPMessagePacketsOrder(t *testing.T) {
|
||||
msg := buildMessage(buildPacket(true, 1, 1, []byte("a")))
|
||||
msg.AddPacket(buildPacket(true, 1, 2, []byte("b")))
|
||||
|
||||
if !bytes.Equal(msg.Bytes(), []byte("ab")) {
|
||||
t.Error("Should contatenate packets in right order")
|
||||
}
|
||||
|
||||
// When first packet have wrong order (Seq)
|
||||
msg = buildMessage(buildPacket(true, 1, 2, []byte("b")))
|
||||
msg.AddPacket(buildPacket(true, 1, 1, []byte("a")))
|
||||
|
||||
if !bytes.Equal(msg.Bytes(), []byte("ab")) {
|
||||
t.Error("Should contatenate packets in right order")
|
||||
}
|
||||
|
||||
// Should ignore packets with same sequence
|
||||
msg = buildMessage(buildPacket(true, 1, 1, []byte("a")))
|
||||
msg.AddPacket(buildPacket(true, 1, 1, []byte("a")))
|
||||
|
||||
if !bytes.Equal(msg.Bytes(), []byte("a")) {
|
||||
t.Error("Should ignore packet with same Seq")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTCPMessageSize(t *testing.T) {
|
||||
msg := buildMessage(buildPacket(true, 1, 1, []byte("POST / HTTP/1.1\r\nContent-Length: 2\r\n\r\na")))
|
||||
msg.AddPacket(buildPacket(true, 1, 2, []byte("b")))
|
||||
|
||||
if msg.BodySize() != 2 {
|
||||
t.Error("Should count only body", msg.BodySize())
|
||||
}
|
||||
|
||||
if msg.Size() != 40 {
|
||||
t.Error("Should count all sizes", msg.Size())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestTCPMessageIsFinished(t *testing.T) {
|
||||
methodsWithoutBodies := []string{"GET","OPTIONS","HEAD"}
|
||||
|
||||
for _, m := range methodsWithoutBodies {
|
||||
msg := buildMessage(buildPacket(true, 1, 1, []byte(m + " / HTTP/1.1")))
|
||||
|
||||
if !msg.IsFinished() {
|
||||
t.Error(m, " request should be finished")
|
||||
}
|
||||
}
|
||||
|
||||
methodsWithBodies := []string{"POST","PUT","PATCH"}
|
||||
|
||||
for _, m := range methodsWithBodies {
|
||||
msg := buildMessage(buildPacket(true, 1, 1, []byte(m + " / HTTP/1.1\r\nContent-Length: 1\r\n\r\na")))
|
||||
|
||||
if !msg.IsFinished() {
|
||||
t.Error(m, " should be finished as body length == content length")
|
||||
}
|
||||
|
||||
msg = buildMessage(buildPacket(true, 1, 1, []byte(m + " / HTTP/1.1\r\nContent-Length: 2\r\n\r\na")))
|
||||
|
||||
if msg.IsFinished() {
|
||||
t.Error(m, " should not be finished as body length != content length")
|
||||
}
|
||||
}
|
||||
|
||||
msg := buildMessage(buildPacket(true, 1, 1, []byte("UNKNOWN / HTTP/1.1\r\n\r\n")))
|
||||
if msg.IsFinished() {
|
||||
t.Error("non http or wrong methods considered as not finished")
|
||||
}
|
||||
|
||||
// Responses
|
||||
msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\n\r\n")))
|
||||
msg.RequestAck = 1
|
||||
if !msg.IsFinished() {
|
||||
t.Error("Should mark simple response as finished")
|
||||
}
|
||||
|
||||
msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\n\r\n")))
|
||||
msg.RequestAck = 0
|
||||
if msg.IsFinished() {
|
||||
t.Error("Should not mark responses without associated requests")
|
||||
}
|
||||
|
||||
msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n")))
|
||||
msg.RequestAck = 1
|
||||
|
||||
if msg.IsFinished() {
|
||||
t.Error("Should mark chunked response as non finished")
|
||||
}
|
||||
|
||||
msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")))
|
||||
msg.RequestAck = 1
|
||||
|
||||
if !msg.IsFinished() {
|
||||
t.Error("Should mark Content-Length: 0 respones as finished")
|
||||
}
|
||||
|
||||
msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\na")))
|
||||
msg.RequestAck = 1
|
||||
|
||||
if !msg.IsFinished() {
|
||||
t.Error("Should mark valid Content-Length respones as finished")
|
||||
}
|
||||
|
||||
|
||||
msg = buildMessage(buildPacket(false, 1, 1, []byte("HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\na")))
|
||||
msg.RequestAck = 1
|
||||
|
||||
if msg.IsFinished() {
|
||||
t.Error("Should not mark not valid Content-Length respones as finished")
|
||||
}
|
||||
}
|
||||
@@ -70,8 +70,8 @@ func (t *TCPPacket) ParseBasic() {
|
||||
// String output for a TCP Packet
|
||||
func (t *TCPPacket) String() string {
|
||||
maxLen := len(t.Data)
|
||||
if maxLen > 500 {
|
||||
maxLen = 500
|
||||
if maxLen > 200 {
|
||||
maxLen = 200
|
||||
}
|
||||
|
||||
return strings.Join([]string{
|
||||
@@ -99,9 +99,3 @@ func (t *TCPPacket) String() string {
|
||||
"Data:" + string(t.Data[:maxLen]),
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
type sortBySeq []*TCPPacket
|
||||
|
||||
func (a sortBySeq) Len() int { return len(a) }
|
||||
func (a sortBySeq) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a sortBySeq) Less(i, j int) bool { return a[i].Seq < a[j].Seq }
|
||||
|
||||
Reference in New Issue
Block a user