├── go.mod ├── error_methods.go ├── init_test.go ├── transmit.go ├── .gitignore ├── cmd ├── stompngo │ └── main.go └── sng_showsvr │ └── main.go ├── version.go ├── message_methods.go ├── deadline_test.go ├── nack_test.go ├── abort.go ├── begin.go ├── commit.go ├── send.go ├── sendbytes.go ├── frame_methods.go ├── utils_exp.go ├── ack.go ├── connbv_test.go ├── nack.go ├── senv ├── senv_test.go └── senv.go ├── send_test.go ├── README.md ├── sendbytes_test.go ├── deadline_data.go ├── elttime.go ├── doc.go ├── unsubscribe.go ├── logger_test.go ├── utils.go ├── disconnect.go ├── unsub_test.go ├── header_methods.go ├── trans_test.go ├── connect.go ├── data_test.go ├── shovel_dupe_headers_test.go ├── CONTRIBUTORS.md ├── suppress_test.go ├── codec_test.go ├── connect_helpers.go ├── heartbeats.go ├── subscribe.go ├── connection.go ├── headers_test.go ├── utils_test.go ├── sub_test.go ├── reader.go ├── ack_test.go ├── SENV.md ├── writer.go ├── misc_test.go └── conndisc_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gmallard/stompngo 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /error_methods.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | Error returns a string for a particular Error. 21 | */ 22 | func (e Error) Error() string { 23 | return string(e) 24 | } 25 | -------------------------------------------------------------------------------- /init_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2017-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Veridon 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permisidons and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | "testing" 23 | ) 24 | 25 | func TestMain(m *testing.M) { 26 | flag.Parse() 27 | packageInit() 28 | os.Exit(m.Run()) 29 | } 30 | 31 | // 32 | func packageInit() { 33 | _ = setTestBroker() 34 | setHeartBeatFlags() 35 | } 36 | -------------------------------------------------------------------------------- /transmit.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | Common transmit data for many stomp API calls. 21 | */ 22 | func (c *Connection) transmitCommon(v string, h Headers) error { 23 | ch := h.Clone() 24 | f := Frame{v, ch, NULLBUFF} 25 | r := make(chan error) 26 | if e := c.writeWireData(wiredata{f, r}); e != nil { 27 | return e 28 | } 29 | e := <-r 30 | return e 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2011 Guy M. Allard 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | *.6 17 | 6.* 18 | # 19 | *.8 20 | 8.* 21 | # 22 | _* 23 | # 24 | *~ 25 | *.bak 26 | temp* 27 | # Ignore local vscode artifacts 28 | .vscode/ 29 | # Ignore web page artifacts. To facilaiate local testing of 30 | # markdown artifacts in local development. 31 | *.html 32 | *.htm 33 | # Coverage artifiact 34 | coverage.out 35 | *.out 36 | *.test 37 | *.cvd 38 | # Logging output 39 | *.log 40 | .idea 41 | -------------------------------------------------------------------------------- /cmd/stompngo/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2018 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | /* 20 | Provide package version information. A nod to the concept of semver. 21 | 22 | Example: 23 | fmt.Println("current stompngo version", stompngo.Version()) 24 | 25 | */ 26 | 27 | import ( 28 | "fmt" 29 | // 30 | "github.com/gmallard/stompngo" 31 | ) 32 | 33 | func main() { 34 | fmt.Println(stompngo.Version()) 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2016-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | Provide package version information. A nod to the concept of semver. 21 | 22 | Example: 23 | fmt.Println("current stompngo version", stompngo.Version()) 24 | 25 | */ 26 | 27 | import ( 28 | "fmt" 29 | ) 30 | 31 | var ( 32 | pref = "v" // Prefix 33 | 34 | major = "1" // Major 35 | 36 | minor = "0" // Minor 37 | 38 | patch = "13" // Patch 39 | 40 | mod = "-m.2" // Modification level 41 | 42 | // mod = "" 43 | ) 44 | 45 | func Version() string { 46 | return fmt.Sprintf("%s%s.%s.%s%s", pref, major, minor, patch, mod) 47 | } 48 | -------------------------------------------------------------------------------- /message_methods.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | BodyString returns a Message body as a string. 21 | */ 22 | func (m *Message) BodyString() string { 23 | return string(m.Body) 24 | } 25 | 26 | /* 27 | String makes Message a Stringer. 28 | */ 29 | func (m *Message) String() string { 30 | return "\nCommand:" + m.Command + 31 | "\nHeaders:" + m.Headers.String() + 32 | HexData(m.Body) 33 | } 34 | 35 | /* 36 | Size returns the size of Message on the wire, in bytes. 37 | */ 38 | func (m *Message) Size(e bool) int64 { 39 | var r int64 = 0 40 | r += int64(len(m.Command)) + 1 + m.Headers.Size(e) + 1 + int64(len(m.Body)) + 1 41 | return r 42 | } 43 | -------------------------------------------------------------------------------- /deadline_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2017-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Veridon 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permisidons and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | ) 23 | 24 | var _ = fmt.Println 25 | 26 | /* 27 | Test Deadline Enablement. 28 | */ 29 | func TestDeadlineEnablement(t *testing.T) { 30 | n, _ = openConn(t) 31 | ch := login_headers 32 | conn, e = Connect(n, ch) 33 | if e != nil { 34 | t.Fatalf("TestDeadlineEnablement CONNECT expected nil, got %v\n", e) 35 | } 36 | // 37 | dle := conn.IsWriteDeadlineEnabled() 38 | if dle != wdleInit { 39 | t.Errorf("TestDeadlineEnablement expected false, got true\n") 40 | } 41 | checkReceived(t, conn, false) 42 | e = conn.Disconnect(empty_headers) 43 | checkDisconnectError(t, e) 44 | _ = closeConn(t, n) 45 | } 46 | -------------------------------------------------------------------------------- /nack_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | ) 23 | 24 | var _ = fmt.Println 25 | 26 | /* 27 | Test Nack error cases. 28 | */ 29 | func TestNackErrors(t *testing.T) { 30 | n, _ = openConn(t) 31 | ch := login_headers 32 | ch = headersProtocol(ch, sp) 33 | conn, e = Connect(n, ch) 34 | if e != nil { 35 | t.Fatalf("TestNackErrors CONNECT expected no error, got [%v]\n", e) 36 | } 37 | for ti, tv := range nackList { 38 | conn.protocol = tv.proto // Fake it 39 | e = conn.Nack(tv.headers) 40 | if e != tv.errval { 41 | t.Fatalf("TestNackErrors[%d] NACK -%s- expected error [%v], got [%v]\n", 42 | ti, tv.proto, tv.errval, e) 43 | } 44 | } 45 | // 46 | checkReceived(t, conn, false) 47 | e = conn.Disconnect(empty_headers) 48 | checkDisconnectError(t, e) 49 | _ = closeConn(t, n) 50 | } 51 | -------------------------------------------------------------------------------- /abort.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | Abort a STOMP transaction. 21 | 22 | Headers MUST contain a "transaction" header key 23 | with a value that is not an empty string. 24 | 25 | Example: 26 | h := stompngo.Headers{stompngo.HK_TRANSACTION, "transaction-id1"} 27 | e := c.Abort(h) 28 | if e != nil { 29 | // Do something sane ... 30 | } 31 | */ 32 | func (c *Connection) Abort(h Headers) error { 33 | c.log(ABORT, "start", h) 34 | if !c.isConnected() { 35 | return ECONBAD 36 | } 37 | // We must have a transaction header here 38 | if _, ok := h.Contains(HK_TRANSACTION); !ok { 39 | return EREQTIDABT 40 | } 41 | // And it must not be empty 42 | if h.Value(HK_TRANSACTION) == "" { 43 | return ETIDABTEMT 44 | } 45 | e := c.transmitCommon(ABORT, h) // transmitCommon Clones() the headers 46 | c.log(ABORT, "end", h) 47 | return e 48 | } 49 | -------------------------------------------------------------------------------- /begin.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | Begin a STOMP transaction. 21 | 22 | Headers MUST contain a "transaction" header key 23 | with a value that is not an empty string. 24 | 25 | Example: 26 | h := stompngo.Headers{stompngo.HK_TRANSACTION, "transaction-id1"} 27 | e := c.Begin(h) 28 | if e != nil { 29 | // Do something sane ... 30 | } 31 | */ 32 | func (c *Connection) Begin(h Headers) error { 33 | c.log(BEGIN, "start", h) 34 | if !c.isConnected() { 35 | return ECONBAD 36 | } 37 | // We must have a transaction header here 38 | if _, ok := h.Contains(HK_TRANSACTION); !ok { 39 | return EREQTIDBEG 40 | } 41 | // And it must not be empty 42 | if h.Value(HK_TRANSACTION) == "" { 43 | return ETIDBEGEMT 44 | } 45 | e := c.transmitCommon(BEGIN, h) // transmitCommon Clones() the headers 46 | c.log(BEGIN, "end", h) 47 | return e 48 | } 49 | -------------------------------------------------------------------------------- /commit.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | Commit a STOMP transaction. 21 | 22 | Headers MUST contain a "transaction" header key 23 | with a value that is not an empty string. 24 | 25 | Example: 26 | h := stompngo.Headers{stompngo.HK_TRANSACTION, "transaction-id1"} 27 | e := c.Begin(h) 28 | if e != nil { 29 | // Do something sane ... 30 | } 31 | 32 | */ 33 | func (c *Connection) Commit(h Headers) error { 34 | c.log(COMMIT, "start", h) 35 | if !c.isConnected() { 36 | return ECONBAD 37 | } 38 | // We must have a transaction header here 39 | if _, ok := h.Contains(HK_TRANSACTION); !ok { 40 | return EREQTIDCOM 41 | } 42 | // And it must not be empty 43 | if h.Value(HK_TRANSACTION) == "" { 44 | return ETIDCOMEMT 45 | } 46 | e := c.transmitCommon(COMMIT, h) // transmitCommon Clones() the headers 47 | c.log(COMMIT, "end", h) 48 | return e 49 | } 50 | -------------------------------------------------------------------------------- /send.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | Send a STOMP MESSAGE. 21 | 22 | Headers MUST contain a "destination" header key. 23 | 24 | The message body (payload) is a string, which may be empty. 25 | 26 | Example: 27 | h := stompngo.Headers{stompngo.HK_DESTINATION, "/queue/mymessages"} 28 | m := "My message" 29 | e := c.Send(h, m) 30 | if e != nil { 31 | // Do something sane ... 32 | } 33 | 34 | */ 35 | func (c *Connection) Send(h Headers, b string) error { 36 | c.log(SEND, "start", h) 37 | if !c.isConnected() { 38 | return ECONBAD 39 | } 40 | e := checkHeaders(h, c.Protocol()) 41 | if e != nil { 42 | return e 43 | } 44 | if _, ok := h.Contains(HK_DESTINATION); !ok { 45 | return EREQDSTSND 46 | } 47 | ch := h.Clone() 48 | f := Frame{SEND, ch, []uint8(b)} 49 | r := make(chan error) 50 | if e = c.writeWireData(wiredata{f, r}); e != nil { 51 | return e 52 | } 53 | e = <-r 54 | c.log(SEND, "end", ch) 55 | return e // nil or not 56 | } 57 | -------------------------------------------------------------------------------- /sendbytes.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2014-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | Send a STOMP MESSAGE. 21 | 22 | Headers MUST contain a "destination" header key. 23 | 24 | The message body (payload) is a slice of bytes, which may be empty. 25 | 26 | Example: 27 | h := stompngo.Headers{stompngo.HK_DESTINATION, "/queue/mymessages"} 28 | m := []byte("My message") 29 | e := c.Send(h, m) 30 | if e != nil { 31 | // Do something sane ... 32 | } 33 | 34 | */ 35 | func (c *Connection) SendBytes(h Headers, b []byte) error { 36 | c.log(SEND, "start", h) 37 | if !c.isConnected() { 38 | return ECONBAD 39 | } 40 | e := checkHeaders(h, c.Protocol()) 41 | if e != nil { 42 | return e 43 | } 44 | if _, ok := h.Contains(HK_DESTINATION); !ok { 45 | return EREQDSTSND 46 | } 47 | ch := h.Clone() 48 | f := Frame{SEND, ch, b} 49 | r := make(chan error) 50 | if e = c.writeWireData(wiredata{f, r}); e != nil { 51 | return e 52 | } 53 | e = <-r 54 | c.log(SEND, "end", ch) 55 | return e // nil or not 56 | } 57 | -------------------------------------------------------------------------------- /frame_methods.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "bytes" 21 | ) 22 | 23 | /* 24 | Size returns the size of Frame on the wire, in bytes. 25 | */ 26 | func (f *Frame) Size(e bool) int64 { 27 | var r int64 = 0 28 | r += int64(len(f.Command)) + 1 + f.Headers.Size(e) + 1 + int64(len(f.Body)) + 1 29 | return r 30 | } 31 | 32 | /* 33 | Bytes returns a byte slice of all frame data, ready for the wire 34 | */ 35 | func (f *Frame) Bytes(sclok bool) []byte { 36 | b := make([]byte, 0, 8*1024) 37 | b = append(b, f.Command+"\n"...) 38 | hb := f.Headers.Bytes() 39 | if len(hb) > 0 { 40 | b = append(b, hb...) 41 | } 42 | b = append(b, "\n"...) 43 | if len(f.Body) > 0 { 44 | if sclok { 45 | nz := bytes.IndexByte(f.Body, 0) 46 | // fmt.Printf("WDBG41 ok:%v\n", nz) 47 | if nz == 0 { 48 | f.Body = []byte{} 49 | // fmt.Printf("WDBG42 body:%v bodystring: %v\n", f.Body, string(f.Body)) 50 | } else if nz > 0 { 51 | f.Body = f.Body[0:nz] 52 | // fmt.Printf("WDBG43 body:%v bodystring: %v\n", f.Body, string(f.Body)) 53 | } 54 | } 55 | if len(f.Body) > 0 { 56 | b = append(b, f.Body...) 57 | } 58 | } 59 | b = append(b, ZRB...) 60 | // 61 | return b 62 | } 63 | -------------------------------------------------------------------------------- /utils_exp.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "crypto/rand" 21 | "crypto/sha1" 22 | "encoding/hex" 23 | "fmt" 24 | "io" 25 | // 26 | "github.com/gmallard/stompngo/senv" 27 | ) 28 | 29 | /* 30 | HexData returns a dump formatted value of a byte slice. 31 | */ 32 | func HexData(b []uint8) string { 33 | td := b[:] 34 | m := senv.MaxBodyLength() 35 | if m > 0 { 36 | if m < len(td) { 37 | td = td[:m] 38 | } 39 | } 40 | return "\n" + hex.Dump(td) 41 | } 42 | 43 | /* 44 | Sha1 returns a SHA1 hash for a specified string. 45 | */ 46 | func Sha1(q string) string { 47 | g := sha1.New() 48 | g.Write([]byte(q)) 49 | return fmt.Sprintf("%x", g.Sum(nil)) 50 | } 51 | 52 | /* 53 | Uuid returns a type 4 UUID. 54 | */ 55 | func Uuid() string { 56 | b := make([]byte, 16) 57 | _, _ = io.ReadFull(rand.Reader, b) 58 | b[6] = (b[6] & 0x0F) | 0x40 59 | b[8] = (b[8] &^ 0x40) | 0x80 60 | return fmt.Sprintf("%x-%x-%x-%x-%x", b[:4], b[4:6], b[6:8], b[8:10], b[10:]) 61 | } 62 | 63 | /* 64 | Supported checks if a particular STOMP version is supported in the current 65 | implementation. 66 | */ 67 | func Supported(v string) bool { 68 | return hasValue(supported, v) 69 | } 70 | 71 | /* 72 | Protocols returns a slice of client supported protocol levels. 73 | */ 74 | func Protocols() []string { 75 | return supported 76 | } 77 | -------------------------------------------------------------------------------- /ack.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | /* 20 | Ack a STOMP MESSAGE. 21 | 22 | For Stomp 1.0 Headers MUST contain a "message-id" header key. 23 | 24 | 25 | For Stomp 1.1 Headers must contain a "message-id" key and a "subscription" 26 | header key. 27 | 28 | 29 | For Stomp 1.2 Headers must contain a unique "id" header key. 30 | 31 | See the specifications at http://stomp.github.com/ for details. 32 | 33 | Example: 34 | h := stompngo.Headers{stompngo.HK_MESSAGE_ID, "message-id1", 35 | "subscription", "d2cbe608b70a54c8e69d951b246999fbc20df694"} 36 | e := c.Ack(h) 37 | if e != nil { 38 | // Do something sane ... 39 | } 40 | 41 | */ 42 | func (c *Connection) Ack(h Headers) error { 43 | c.log(ACK, "start", h, c.Protocol()) 44 | if !c.isConnected() { 45 | return ECONBAD 46 | } 47 | e := checkHeaders(h, c.Protocol()) 48 | if e != nil { 49 | return e 50 | } 51 | switch c.Protocol() { 52 | case SPL_12: 53 | if _, ok := h.Contains(HK_ID); !ok { 54 | return EREQIDACK 55 | } 56 | case SPL_11: 57 | if _, ok := h.Contains(HK_SUBSCRIPTION); !ok { 58 | return EREQSUBACK 59 | } 60 | if _, ok := h.Contains(HK_MESSAGE_ID); !ok { 61 | return EREQMIDACK 62 | } 63 | default: // SPL_10 64 | if _, ok := h.Contains(HK_MESSAGE_ID); !ok { 65 | return EREQMIDACK 66 | } 67 | } 68 | 69 | e = c.transmitCommon(ACK, h) // transmitCommon Clones() the headers 70 | c.log(ACK, "end", h, c.Protocol()) 71 | return e 72 | } 73 | -------------------------------------------------------------------------------- /connbv_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2012-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import "testing" 20 | 21 | /* 22 | ConnBadValVer Test: Bad Version value. 23 | */ 24 | func TestConnBadValVer(t *testing.T) { 25 | for _, p := range Protocols() { 26 | n, _ = openConn(t) 27 | ch := login_headers 28 | ch = ch.Add(HK_ACCEPT_VERSION, "3.14159").Add(HK_HOST, "localhost") 29 | conn, e = Connect(n, ch) 30 | if e == nil { 31 | t.Errorf("TestConnBadValVer Expected error, got nil, proto: %s\n", p) 32 | } 33 | if e != EBADVERCLI { 34 | t.Errorf("TestConnBadValVer Expected <%v>, got <%v>, proto: %s\n", 35 | EBADVERCLI, e, p) 36 | } 37 | checkReceived(t, conn, false) 38 | // We are not connected by test design, check nothing around 39 | // DISCONNECT. 40 | _ = closeConn(t, n) 41 | } 42 | } 43 | 44 | /* 45 | ConnBadValHost Test: Bad Version, no host (vhost) value. 46 | */ 47 | func TestConnBadValHost(t *testing.T) { 48 | for _, p := range Protocols() { 49 | n, _ = openConn(t) 50 | ch := login_headers 51 | ch = ch.Add(HK_ACCEPT_VERSION, p) 52 | conn, e = Connect(n, ch) 53 | if e == nil { 54 | t.Errorf("TestConnBadValHost Expected error, got nil, proto: %s\n", p) 55 | } 56 | if e != EREQHOST { 57 | t.Errorf("TestConnBadValHost Expected <%v>, got <%v>, proto: %s\n", 58 | EREQHOST, e, p) 59 | } 60 | checkReceived(t, conn, false) 61 | // We are not connected by test design, check nothing around 62 | // DISCONNECT. 63 | _ = closeConn(t, n) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /nack.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | ) 22 | 23 | var _ = fmt.Println 24 | 25 | /* 26 | Nack a STOMP 1.1+ message. 27 | 28 | For Stomp 1.1 Headers must contain a "message-id" key and a "subscription" 29 | header key. 30 | 31 | 32 | For Stomp 1.2 Headers must contain a unique "id" header key. 33 | 34 | 35 | See the specifications at http://stomp.github.com/ for details. 36 | 37 | 38 | Disallowed for an established STOMP 1.0 connection, and EBADVERNAK is returned. 39 | 40 | Example: 41 | h := stompngo.Headers{stompngo.HK_MESSAGE_ID, "message-id1", 42 | stompngo.HK_SUBSCRIPTION, "d2cbe608b70a54c8e69d951b246999fbc20df694"} 43 | e := c.Nack(h) 44 | if e != nil { 45 | // Do something sane ... 46 | } 47 | 48 | */ 49 | func (c *Connection) Nack(h Headers) error { 50 | c.log(NACK, "start", h, c.Protocol()) 51 | if !c.isConnected() { 52 | return ECONBAD 53 | } 54 | if c.Protocol() == SPL_10 { 55 | return EBADVERNAK 56 | } 57 | e := checkHeaders(h, c.Protocol()) 58 | if e != nil { 59 | return e 60 | } 61 | 62 | switch c.Protocol() { 63 | case SPL_12: 64 | if _, ok := h.Contains(HK_ID); !ok { 65 | return EREQIDNAK 66 | } 67 | default: // SPL_11 68 | if _, ok := h.Contains(HK_SUBSCRIPTION); !ok { 69 | return EREQSUBNAK 70 | } 71 | if _, ok := h.Contains(HK_MESSAGE_ID); !ok { 72 | return EREQMIDNAK 73 | } 74 | } 75 | 76 | e = c.transmitCommon(NACK, h) // transmitCommon Clones() the headers 77 | c.log(NACK, "end", h, c.Protocol()) 78 | return e 79 | } 80 | -------------------------------------------------------------------------------- /senv/senv_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2014-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package senv 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | /* 24 | Test senv defaults. Must run with no environment variables set. 25 | */ 26 | func TestSenvDefaults(t *testing.T) { 27 | h := Host() 28 | if h != "localhost" { 29 | t.Errorf("Senv Host, expected [%s], got [%s]\n", "localhost", h) 30 | } 31 | // 32 | p := Port() 33 | if p != "61613" { 34 | t.Errorf("Senv Post, expected [%s], got [%s]\n", "61613", p) 35 | } 36 | // 37 | p = Protocol() 38 | if p != "1.2" { 39 | t.Errorf("Senv Protocol, expected [%s], got [%s]\n", "1.2", p) 40 | } 41 | // 42 | l := Login() 43 | if l != "guest" { 44 | t.Errorf("Senv Login, expected [%s], got [%s]\n", "guest", l) 45 | } 46 | // 47 | p = Passcode() 48 | if p != "guest" { 49 | t.Errorf("Senv Passcode, expected [%s], got [%s]\n", "guest", p) 50 | } 51 | // 52 | v := Vhost() 53 | if v != "localhost" { 54 | t.Errorf("Senv Vhost, expected [%s], got [%s]\n", "localhost", v) 55 | } 56 | // 57 | d := Dest() 58 | if d != "/queue/sng.sample.stomp.destination" { 59 | t.Errorf("Senv Dest, expected [%s], got [%s]\n", 60 | "/queue/sng.sample.stomp.destination", d) 61 | } 62 | // 63 | n := Nmsgs() 64 | if n != 1 { 65 | t.Errorf("Senv Nmsgs, expected [%d], got [%d]\n", 66 | 1, n) 67 | } 68 | // 69 | nsc := SubChanCap() 70 | if nsc != 1 { 71 | t.Errorf("Senv SunChanCap, expected [%d], got [%d]\n", 72 | 1, nsc) 73 | } 74 | // 75 | b := Persistent() 76 | if b { 77 | t.Errorf("Senv Persistent, expected [%t], got [%t]\n", 78 | false, b) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /send_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | /* 24 | Test Send Basic, one message. 25 | */ 26 | func TestSendBasic(t *testing.T) { 27 | for _, sp := range Protocols() { 28 | n, _ = openConn(t) 29 | ch := login_headers 30 | ch = headersProtocol(ch, sp) 31 | conn, e = Connect(n, ch) 32 | if e != nil { 33 | t.Fatalf("TestSendBasic CONNECT expected no error, got [%v]\n", e) 34 | } 35 | // 36 | ms := "A message" 37 | d := tdest("/queue/send.basiconn.01." + sp) 38 | sh := Headers{HK_DESTINATION, d} 39 | e = conn.Send(sh, ms) 40 | if e != nil { 41 | t.Fatalf("TestSendBasic Expected nil error, got [%v]\n", e) 42 | } 43 | // 44 | e = conn.Send(empty_headers, ms) 45 | if e == nil { 46 | t.Fatalf("TestSendBasic Expected error, got [nil]\n") 47 | } 48 | if e != EREQDSTSND { 49 | t.Fatalf("TestSendBasic Expected [%v], got [%v]\n", EREQDSTSND, e) 50 | } 51 | checkReceived(t, conn, false) 52 | e = conn.Disconnect(empty_headers) 53 | checkDisconnectError(t, e) 54 | _ = closeConn(t, n) 55 | } 56 | } 57 | 58 | /* 59 | Test Send Multiple, multiple messages, 5 to be exact. 60 | */ 61 | func TestSendMultiple(t *testing.T) { 62 | for _, sp := range Protocols() { 63 | n, _ = openConn(t) 64 | ch := login_headers 65 | ch = headersProtocol(ch, sp) 66 | conn, e = Connect(n, ch) 67 | if e != nil { 68 | t.Fatalf("TestSendMultiple CONNECT expected no error, got [%v]\n", e) 69 | } 70 | // 71 | smd := multi_send_data{conn: conn, 72 | dest: tdest("/queue/sendmultiple.01." + sp + "."), 73 | mpref: "sendmultiple.01.message.prefix ", 74 | count: 5} 75 | e = sendMultiple(smd) 76 | if e != nil { 77 | t.Fatalf("TestSendMultiple Expected nil error, got [%v]\n", e) 78 | } 79 | // 80 | checkReceived(t, conn, false) 81 | e = conn.Disconnect(empty_headers) 82 | checkDisconnectError(t, e) 83 | _ = closeConn(t, n) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stompngo - A STOMP 1.0, 1.1 and 1.2 Client Package # 2 | 3 | ## Features ## 4 | 5 | * Full support of STOMP protocols: 6 | 7 | 1. [Protocol Level 1.0](http://stomp.github.com/stomp-specification-1.0.html) 8 | 2. [Protocol Level 1.1](http://stomp.github.com/stomp-specification-1.1.html) 9 | 3. [Protocol Level 1.2](http://stomp.github.com/stomp-specification-1.2.html) 10 | 11 | ## References ## 12 | 13 | * [STOMP 1.0 Protocol](http://stomp.github.com/stomp-specification-1.0.html) 14 | * [STOMP 1.1 Protocol](http://stomp.github.com/stomp-specification-1.1.html) 15 | * [STOMP 1.2 Protocol](http://stomp.github.com/stomp-specification-1.2.html) 16 | 17 | ## Installation ## 18 | 19 | Installation requires a working go environment. For current versions of go 20 | issue: 21 | 22 | * go get github.com/gmallard/stompngo 23 | 24 | The GOPATH environment variable must be set properly. 25 | 26 | It is also possible to just clone the stompngo repository to any location of 27 | choice, and use go modules to locate it. 28 | 29 | ## Examples ## 30 | 31 | The examples in the included unit tests can be used as a good starting point. 32 | 33 | Also see the examples project: 34 | 35 | * [stompngo_examples at github](https://github.com/gmallard/stompngo_examples) 36 | 37 | ## QA ## 38 | 39 | The tests for this STOMP client package run against recent releases of: 40 | 41 | * [ActiveMQ](http://activemq.apache.org/) 42 | * [stompserver_ng](https://github.com/gmallard/stompserver_ng) 43 | * [Apache Apollo](http://activemq.apache.org/apollo/) 44 | * [RabbitMQ](http://www.rabbitmq.com/) 45 | * [Artemis](https://activemq.apache.org/artemis/) 46 | 47 | See the tests for **relevant environment variables**. 48 | 49 | **NOTE:** For testing with rabbitmq, you also need `export STOMP_RMQ="/"` due to the default vhost of rabbitmq is "/" instead of "localhost". 50 | 51 | ## Contributions ## 52 | 53 | Any and all are welcome by pull request or e-mail patch. 54 | 55 | ## Wiki ## 56 | 57 | News and notes will be posted from time to time at the stompngo wiki: 58 | 59 | * [stompngo wiki](https://github.com/gmallard/stompngo/wiki) 60 | 61 | Please review and update that on occaision. 62 | 63 | ## Canonical Repository ## 64 | 65 | For the record, the canonical repository for this project is at: 66 | 67 | * [stompngo at github](https://github.com/gmallard/stompngo) 68 | 69 | ## Issues ## 70 | 71 | Please review issues at the canonical repository. File any new issues there as 72 | well: 73 | 74 | * [issues](https://github.com/gmallard/stompngo/issues?sort=comments&state=open) 75 | 76 | ## Contributors List ## 77 | 78 | See CONTRIBUTORS.md. 79 | -------------------------------------------------------------------------------- /sendbytes_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2014-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | /* 24 | Test Send Basiconn, one message. 25 | */ 26 | func TestSendBytesBasic(t *testing.T) { 27 | for _, sp := range Protocols() { 28 | n, _ = openConn(t) 29 | ch := login_headers 30 | ch = headersProtocol(ch, sp) 31 | conn, e = Connect(n, ch) 32 | if e != nil { 33 | t.Fatalf("TestSendBytesBasic CONNECT expected no error, got [%v]\n", e) 34 | } 35 | 36 | // 37 | mb := []byte("A message-" + sp) 38 | d := tdest("/queue/send.basiconn.01.bytes." + sp) 39 | sh := Headers{HK_DESTINATION, d} 40 | e = conn.SendBytes(sh, mb) 41 | if e != nil { 42 | t.Fatalf("TestSendBytesBasic Expected nil error, got [%v]\n", e) 43 | } 44 | // 45 | e = conn.SendBytes(empty_headers, mb) 46 | if e == nil { 47 | t.Fatalf("TestSendBytesBasic Expected error, got [nil]\n") 48 | } 49 | if e != EREQDSTSND { 50 | t.Fatalf("TestSendBytesBasic Expected [%v], got [%v]\n", EREQDSTSND, e) 51 | } 52 | checkReceived(t, conn, false) 53 | e = conn.Disconnect(empty_headers) 54 | checkDisconnectError(t, e) 55 | _ = closeConn(t, n) 56 | } 57 | } 58 | 59 | /* 60 | Test Send Multiple, multiple messages, 5 to be exact. 61 | */ 62 | func TestSendBytesMultiple(t *testing.T) { 63 | for _, sp := range Protocols() { 64 | n, _ = openConn(t) 65 | ch := login_headers 66 | ch = headersProtocol(ch, sp) 67 | conn, e = Connect(n, ch) 68 | if e != nil { 69 | t.Fatalf("TestSendBytesMultiple CONNECT expected no error, got [%v]\n", e) 70 | } 71 | // 72 | mdb := multi_send_data{conn: conn, 73 | dest: tdest("/queue/sendmultiple.01.bytes." + sp + "."), 74 | mpref: "sendmultiple.01.message.prefix.bytes ", 75 | count: 5} 76 | e = sendMultipleBytes(mdb) 77 | if e != nil { 78 | t.Fatalf("TestSendBytesMultiple Expected nil error, got [%v]\n", e) 79 | } 80 | // 81 | checkReceived(t, conn, false) 82 | e = conn.Disconnect(empty_headers) 83 | checkDisconnectError(t, e) 84 | _ = closeConn(t, n) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /cmd/sng_showsvr/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2017-2018 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "log" 22 | "net" 23 | "os" 24 | // 25 | sng "github.com/gmallard/stompngo" 26 | "github.com/gmallard/stompngo/senv" 27 | ) 28 | 29 | func main() { 30 | 31 | //========================================================================= 32 | // Use something like this a boilerplate for connect (Yes, a lot of work, 33 | // network connects usually are.) 34 | host, port := senv.HostAndPort() 35 | hap := net.JoinHostPort(host, port) 36 | n, err := net.Dial(sng.NetProtoTCP, hap) 37 | log.Printf("Connect Host and Port: %s\n", hap) 38 | log.Printf("Connect Login: %s\n", senv.Login()) 39 | log.Printf("Connect Passcode: %s\n", senv.Passcode()) 40 | if err != nil { 41 | log.Fatalln("Net Connect error for:", hap, "error:", err) 42 | } 43 | // 44 | connect_headers := sng.Headers{sng.HK_LOGIN, senv.Login(), 45 | sng.HK_PASSCODE, senv.Passcode(), 46 | sng.HK_VHOST, senv.Vhost(), 47 | sng.HK_HEART_BEAT, senv.Heartbeats(), 48 | sng.HK_ACCEPT_VERSION, senv.Protocol(), 49 | } 50 | // 51 | stomp_conn, err := sng.Connect(n, connect_headers) 52 | if err != nil { 53 | log.Printf("STOMP Connect failed, error:%v\n", err) 54 | if stomp_conn != nil { 55 | log.Printf("Connect Response: %v\n", stomp_conn.ConnectResponse) 56 | } 57 | os.Exit(1) 58 | } 59 | 60 | //========================================================================= 61 | // Use something like this as real application logic 62 | fmt.Printf("Client CONNECT Headers:\n%v\n", connect_headers) 63 | fmt.Printf("Broker CONNECTED Data:\n") 64 | fmt.Printf("Server: %s\n", 65 | stomp_conn.ConnectResponse.Headers.Value(sng.HK_SERVER)) 66 | fmt.Printf("Protocol: %s\n", 67 | stomp_conn.ConnectResponse.Headers.Value(sng.HK_VERSION)) 68 | fmt.Printf("Heartbeats: %s\n", 69 | stomp_conn.ConnectResponse.Headers.Value(sng.HK_HEART_BEAT)) 70 | fmt.Printf("Session: %s\n", 71 | stomp_conn.ConnectResponse.Headers.Value(sng.HK_SESSION)) 72 | // 73 | 74 | //========================================================================= 75 | // Use something like this as boilerplate for disconnect (Clean disconnects 76 | // are also a lot of work.) 77 | err = stomp_conn.Disconnect(sng.Headers{}) 78 | if err != nil { 79 | log.Fatalf("DISCONNECT Failed, error:%v\n", err) 80 | } 81 | err = n.Close() 82 | if err != nil { 83 | log.Fatalf("Net Close Error:%v\n", err) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /deadline_data.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2017-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed, an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import "time" 20 | 21 | /* 22 | ExpiredNotification is a callback function, provided by the client 23 | and called when a deadline expires. The err parameter will contain 24 | the actual expired error. The rw parameter will be true if 25 | the notification is for a write, and false otherwise. 26 | */ 27 | type ExpiredNotification func(err error, rw bool) 28 | 29 | /* 30 | DeadlineData controls the use of deadlines in network I/O. 31 | */ 32 | type deadlineData struct { 33 | wde bool // Write deadline data enabled 34 | wdld time.Duration // Write deadline duration 35 | wds bool // True if write duration has been set 36 | // 37 | dlnotify ExpiredNotification 38 | dns bool // True if dlnotify has been set 39 | // 40 | rde bool // Read deadline data enabled 41 | rdld time.Duration // Read deadline duration 42 | rds bool // True if read duration has been set 43 | t0 time.Time // 0 value of Time 44 | // 45 | rfsw bool // Attempt to recover from short writes 46 | } 47 | 48 | /* 49 | WriteDeadline sets the write deadline duration. 50 | */ 51 | func (c *Connection) WriteDeadline(d time.Duration) { 52 | c.log("Write Deadline", d) 53 | c.dld.wdld = d 54 | c.dld.wds = true 55 | } 56 | 57 | /* 58 | EnableWriteDeadline enables/disables the use of write deadlines. 59 | */ 60 | func (c *Connection) EnableWriteDeadline(e bool) { 61 | c.log("Enable Write Deadline", e) 62 | c.dld.wde = e 63 | } 64 | 65 | /* 66 | ExpiredNotification sets the expired notification callback function. 67 | */ 68 | func (c *Connection) ExpiredNotification(enf ExpiredNotification) { 69 | c.log("Set ExpiredNotification") 70 | c.dld.dlnotify = enf 71 | c.dld.dns = true 72 | } 73 | 74 | /* 75 | IsWriteDeadlineEnabled returns the current value of write deadline 76 | enablement. 77 | */ 78 | func (c *Connection) IsWriteDeadlineEnabled() bool { 79 | return c.dld.wde 80 | } 81 | 82 | /* 83 | ReadDeadline sets the write deadline duration. 84 | */ 85 | func (c *Connection) ReadDeadline(d time.Duration) { 86 | c.log("Read Deadline", d) 87 | c.dld.rdld = d 88 | c.dld.rds = true 89 | } 90 | 91 | /* 92 | EnableReadDeadline enables/disables the use of read deadlines. 93 | */ 94 | func (c *Connection) EnableReadDeadline(e bool) { 95 | c.log("Enable Read Deadline", e) 96 | c.dld.rde = e 97 | } 98 | 99 | /* 100 | IsReadDeadlineEnabled returns the current value of write deadline 101 | enablement. 102 | */ 103 | func (c *Connection) IsReadDeadlineEnabled() bool { 104 | return c.dld.rde 105 | } 106 | 107 | /* 108 | ShortWriteRecovery enables / disables short write recovery. 109 | enablement. 110 | */ 111 | func (c *Connection) ShortWriteRecovery(ro bool) { 112 | c.dld.rfsw = ro // Set recovery option 113 | } 114 | -------------------------------------------------------------------------------- /elttime.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2019-2020 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed, an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "log" 22 | ) 23 | 24 | type eltd struct { 25 | ens int64 // elapsed nanoseconds 26 | ec int64 // call count 27 | } 28 | 29 | type eltmets struct { 30 | 31 | // Reader overall 32 | rov eltd 33 | // Reader command 34 | rcmd eltd 35 | // Reader individual headers 36 | rivh eltd 37 | // Reader - until null 38 | run eltd 39 | // Reader - Body 40 | rbdy eltd 41 | 42 | // Writer overall 43 | wov eltd 44 | // Writer command 45 | wcmd eltd 46 | // Writer individual headers 47 | wivh eltd 48 | // Writer - Body 49 | wbdy eltd 50 | } 51 | 52 | func (c *Connection) ShowEltd(ll *log.Logger) { 53 | if c.eltd == nil { 54 | return 55 | } 56 | // 57 | ll.Println("Reader Elapsed Time Information") 58 | // 59 | ll.Printf("Overall - ns %d count %d\n", 60 | c.eltd.rov.ens, c.eltd.rov.ec) 61 | // 62 | ll.Printf("Command - ns %d count %d\n", 63 | c.eltd.rcmd.ens, c.eltd.rcmd.ec) 64 | // 65 | ll.Printf("Individual Headers - ns %d count %d\n", 66 | c.eltd.rivh.ens, c.eltd.rivh.ec) 67 | // 68 | ll.Printf("Until Null - ns %d count %d\n", 69 | c.eltd.run.ens, c.eltd.run.ec) 70 | // 71 | ll.Printf("Body - ns %d count %d\n", 72 | c.eltd.rbdy.ens, c.eltd.rbdy.ec) 73 | 74 | // 75 | ll.Println("Writer Elapsed Time Information") 76 | // 77 | ll.Printf("Overall - ns %d count %d\n", 78 | c.eltd.wov.ens, c.eltd.wov.ec) 79 | // 80 | ll.Printf("Command - ns %d count %d\n", 81 | c.eltd.wcmd.ens, c.eltd.wcmd.ec) 82 | // 83 | ll.Printf("Individual Headers - ns %d count %d\n", 84 | c.eltd.wivh.ens, c.eltd.wivh.ec) 85 | // 86 | ll.Printf("Body - ns %d count %d\n", 87 | c.eltd.wbdy.ens, c.eltd.wbdy.ec) 88 | } 89 | 90 | func (c *Connection) ShowEltdCsv() { 91 | if c.eltd == nil { 92 | return 93 | } 94 | // 95 | fmt.Println("SECTION,ELTNS,COUNT,PCT") 96 | // 97 | fmt.Printf("ROV,%d,%d,%s\n", 98 | c.eltd.rov.ens, c.eltd.rov.ec, "100.00") 99 | // 100 | fmt.Printf("RCMD,%d,%d,%s\n", 101 | c.eltd.rcmd.ens, c.eltd.rcmd.ec, getpct(c.eltd.rcmd.ens, c.eltd.rov.ens)) 102 | // 103 | fmt.Printf("RIVH,%d,%d,%s\n", 104 | c.eltd.rivh.ens, c.eltd.rivh.ec, getpct(c.eltd.rivh.ens, c.eltd.rov.ens)) 105 | // 106 | fmt.Printf("RUN,%d,%d,%s\n", 107 | c.eltd.run.ens, c.eltd.run.ec, getpct(c.eltd.run.ens, c.eltd.rov.ens)) 108 | // 109 | fmt.Printf("RBDY,%d,%d,%s\n", 110 | c.eltd.rbdy.ens, c.eltd.rbdy.ec, getpct(c.eltd.rbdy.ens, c.eltd.rov.ens)) 111 | 112 | // 113 | fmt.Printf("WOV,%d,%d,%s\n", 114 | c.eltd.wov.ens, c.eltd.wov.ec, "100.00") 115 | // 116 | fmt.Printf("WCMD,%d,%d,%s\n", 117 | c.eltd.wcmd.ens, c.eltd.wcmd.ec, getpct(c.eltd.wcmd.ens, c.eltd.wov.ens)) 118 | // 119 | fmt.Printf("WIVH,%d,%d,%s\n", 120 | c.eltd.wivh.ens, c.eltd.wivh.ec, getpct(c.eltd.wivh.ens, c.eltd.wov.ens)) 121 | // 122 | fmt.Printf("WBDY,%d,%d,%s\n", 123 | c.eltd.wbdy.ens, c.eltd.wbdy.ec, getpct(c.eltd.wbdy.ens, c.eltd.wov.ens)) 124 | } 125 | 126 | func getpct(num, den int64) string { 127 | fv := float64(num) / float64(den) 128 | return fmt.Sprintf("%f", 100.0*fv) 129 | } 130 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | /* 18 | Package stompngo implements a STOMP 1.1+ compatible client library. 19 | For more STOMP information, see the specification at: 20 | http://stomp.github.com/. 21 | 22 | 23 | Preparation 24 | 25 | Network Connect: 26 | 27 | You are responsible for first establishing a network connection. 28 | 29 | This network connection will be used when you create a stompngo.Connection to 30 | interact with the STOMP broker. 31 | 32 | h := "localhost" 33 | p := "61613" 34 | n, err := net.Dial(stompngo.NetProtoTCP, net.JoinHostPort(h, p)) 35 | if err != nil { 36 | // Do something sane ... 37 | } 38 | 39 | 40 | Shutdown 41 | 42 | Network Disconnect: 43 | 44 | When processing is complete, you MUST close the network 45 | connection. If you fail to do this, you will leak goroutines! 46 | 47 | err = n.Close() // Could be defered above, think about it! 48 | if err != nil { 49 | // Do something sane ... 50 | } 51 | 52 | 53 | STOMP Frames 54 | 55 | The STOMP specification defines these physical frames that can be sent from a client to a STOMP broker: 56 | CONNECT connect to a STOMP broker, any version. 57 | STOMP connect to a STOMP broker, specification version 1.1+ only. 58 | DISCONNECT disconnect from a STOMP broker. 59 | SEND Send a message to a named queue or topic. 60 | SUBSCRIBE Prepare to read messages from a named queue or topic. 61 | UNSUBSCRIBE Complete reading messages from a named queue or topic. 62 | ACK Acknowledge that a message has been received and processed. 63 | NACK Deny that a message has been received and processed, specification version 1.1+ only. 64 | BEGIN Begin a transaction. 65 | COMMIT Commit a transaction. 66 | ABORT Abort a transaction. 67 | 68 | The STOMP specification defines these physical frames that a client can receive from a STOMP broker: 69 | CONNECTED Broker response upon connection success. 70 | ERROR Broker emitted upon any error at any time during an active STOMP connection. 71 | MESSAGE A STOMP message frame, possibly with headers and a data payload. 72 | RECEIPT A receipt from the broker for a previous frame sent by the client. 73 | 74 | 75 | Subscribe and MessageData Channels 76 | 77 | The Subscribe method returns a channel from which you receive MessageData values. 78 | 79 | The channel returned has different characteristics depending on the Stomp Version, and the Headers you pass to Subscribe. 80 | 81 | For details on Subscribe requirements and behavior, see: https://github.com/gmallard/stompngo/wiki/subscribe-and-messagedata 82 | 83 | 84 | RECEIPTs 85 | 86 | Receipts are never received on a subscription unique MessageData channel. 87 | 88 | They are always queued to the shared connection level 89 | stompgo.Connection.MessageData channel. 90 | 91 | The reason for this behavior is because RECEIPT frames do not contain a subscription Header 92 | (per the STOMP specifications). See the: 93 | 94 | https://github.com/gmallard/stompngo_examples 95 | 96 | package for several examples. 97 | 98 | */ 99 | package stompngo 100 | -------------------------------------------------------------------------------- /unsubscribe.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "strconv" 21 | "time" 22 | ) 23 | 24 | /* 25 | Unsubscribe from a STOMP subscription. 26 | 27 | Headers MUST contain a "destination" header key, and for Stomp 1.1+, 28 | a "id" header key per the specifications. The subscription MUST currently 29 | exist for this session. 30 | 31 | Example: 32 | // Possible additional Header keys: "id". 33 | h := stompngo.Headers{stompngo.HK_DESTINATION, "/queue/myqueue"} 34 | e := c.Unsubscribe(h) 35 | if e != nil { 36 | // Do something sane ... 37 | } 38 | 39 | */ 40 | func (c *Connection) Unsubscribe(h Headers) error { 41 | c.log(UNSUBSCRIBE, "start", h) 42 | // fmt.Printf("Unsub Headers: %v\n", h) 43 | if !c.isConnected() { 44 | return ECONBAD 45 | } 46 | e := checkHeaders(h, c.Protocol()) 47 | if e != nil { 48 | return e 49 | } 50 | 51 | // Specification Requirements: 52 | // 1.0) requires either a destination header or an id header 53 | // 1.1) ... requires ... the id header .... 54 | // 1.2) an id header MUST be included in the frame 55 | // 56 | _, okd := h.Contains(HK_DESTINATION) 57 | shid, oki := h.Contains(HK_ID) 58 | switch c.Protocol() { 59 | case SPL_12: 60 | if !oki { 61 | return EUNOSID 62 | } 63 | case SPL_11: 64 | if !oki { 65 | return EUNOSID 66 | } 67 | case SPL_10: 68 | if !oki && !okd { 69 | return EUNODSID 70 | } 71 | default: 72 | panic("unsubscribe version not supported: " + c.Protocol()) 73 | } 74 | // 75 | shaid := Sha1(h.Value(HK_DESTINATION)) // Special for 1.0 76 | c.subsLock.RLock() 77 | s1x, p := c.subs[shid] 78 | s10, ps := c.subs[shaid] // Special for 1.0 79 | c.subsLock.RUnlock() 80 | var usesp *subscription 81 | usekey := "" 82 | 83 | switch c.Protocol() { 84 | case SPL_12: 85 | fallthrough 86 | case SPL_11: 87 | if !oki { 88 | return EUNOSID // id required 89 | } 90 | if !p { // subscription does not exist 91 | return EBADSID // invalid subscription-id 92 | } 93 | usekey = shid 94 | usesp = s1x 95 | case SPL_10: 96 | if !p && !ps { 97 | return EUNODSID 98 | } 99 | usekey = shaid 100 | usesp = s10 101 | default: 102 | panic("unsubscribe version not supported: " + c.Protocol()) 103 | } 104 | 105 | sdn, ok := h.Contains(StompPlusDrainNow) // STOMP Protocol Extension 106 | 107 | if !ok { 108 | e = c.transmitCommon(UNSUBSCRIBE, h) // transmitCommon Clones() the headers 109 | if e != nil { 110 | return e 111 | } 112 | 113 | c.subsLock.Lock() 114 | delete(c.subs, usekey) 115 | c.subsLock.Unlock() 116 | c.log(UNSUBSCRIBE, "end", h) 117 | return nil 118 | } 119 | // 120 | // STOMP Protocol Extension 121 | // 122 | c.log("sngdrnow extension detected") 123 | idn, err := strconv.ParseInt(sdn, 10, 64) 124 | if err != nil { 125 | idn = 100 // 100 milliseconds if bad parameter 126 | } 127 | //ival := time.Duration(idn * 1000000) 128 | ival := time.Duration(time.Duration(idn) * time.Millisecond) 129 | dmc := 0 130 | forsel: 131 | for { 132 | // ticker := time.NewTicker(ival) 133 | select { 134 | case mi, ok := <-usesp.md: 135 | if !ok { 136 | break forsel 137 | } 138 | dmc++ 139 | c.log("\nsngdrnow DROP", dmc, mi.Message.Command, mi.Message.Headers) 140 | // case _ = <-ticker.C: 141 | case <-time.After(ival): 142 | c.log("sngdrnow extension BREAK") 143 | break forsel 144 | } 145 | } 146 | // 147 | c.log("sngdrnow extension at very end") 148 | c.subsLock.Lock() 149 | delete(c.subs, usekey) 150 | c.subsLock.Unlock() 151 | c.log(UNSUBSCRIBE, "endsngdrnow", h) 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /logger_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "log" 21 | "os" 22 | "testing" 23 | "time" 24 | ) 25 | 26 | /* 27 | Test Logger Basic, confirm by observation. 28 | */ 29 | func TestLoggerBasic(t *testing.T) { 30 | for _, sp = range Protocols() { 31 | n, _ = openConn(t) 32 | ch := login_headers 33 | ch = headersProtocol(ch, sp) 34 | log.Printf("Connect Headers: %v\n", ch) 35 | conn, e = Connect(n, ch) 36 | if e != nil { 37 | t.Errorf("TestLoggerBasic CONNECT expected nil, got %v\n", e) 38 | if conn != nil { 39 | t.Errorf("TestLoggerBasic CONNECT ERROR, got %v\n", 40 | conn.ConnectResponse) 41 | } 42 | } 43 | // 44 | l := log.New(os.Stdout, "TLB ", log.Ldate|log.Lmicroseconds) 45 | // Show broker's CONNECT response (CONNECTED frame). 46 | l.Printf("TestLoggerBasic ConnectResponse:\n%v\n", conn.ConnectResponse) 47 | 48 | // We force a logger for this test. 49 | conn.SetLogger(l) 50 | 51 | // 52 | 53 | checkReceived(t, conn, false) 54 | e = conn.Disconnect(empty_headers) 55 | checkDisconnectError(t, e) 56 | time.Sleep(testlgslt * time.Millisecond) 57 | _ = closeConn(t, n) 58 | } 59 | 60 | } 61 | 62 | /* 63 | Test Logger with a zero Byte Message, a corner case. This is merely 64 | to demonstrate the basics of log output when a logger is set for the 65 | connection. 66 | */ 67 | func TestLoggerMiscBytes0(t *testing.T) { 68 | for _, sp := range Protocols() { 69 | ll := log.New(os.Stdout, "TLM01 ", log.Ldate|log.Lmicroseconds|log.Lshortfile) 70 | // Write phase 71 | n, _ = openConn(t) 72 | ch := login_headers 73 | ch = headersProtocol(ch, sp) 74 | conn, e = Connect(n, ch) 75 | if e != nil { 76 | t.Fatalf("TestLoggerMiscBytes0 CONNECT expected nil, got %v\n", e) 77 | } 78 | conn.SetLogger(ll) // Force a logger here 79 | // 80 | ms := "" // No data 81 | d := tdest("/queue/logger.zero.byte.msg." + sp) 82 | sh := Headers{HK_DESTINATION, d} 83 | e = conn.Send(sh, ms) 84 | if e != nil { 85 | t.Fatalf("Expected nil error, got [%v]\n", e) 86 | } 87 | // 88 | _ = conn.Disconnect(empty_headers) 89 | _ = closeConn(t, n) 90 | 91 | // Read phase 92 | n, _ = openConn(t) 93 | ch = login_headers 94 | ch = headersProtocol(ch, sp) 95 | conn, _ = Connect(n, ch) 96 | ll = log.New(os.Stdout, "TLM02 ", log.Ldate|log.Lmicroseconds|log.Lshortfile) 97 | conn.SetLogger(ll) // Force a logger here 98 | // 99 | sbh := sh.Add(HK_ID, d) 100 | sc, e = conn.Subscribe(sbh) 101 | if e != nil { 102 | t.Fatalf("TestLoggerMiscBytes0 Expected no subscribe error, got [%v]\n", 103 | e) 104 | } 105 | if sc == nil { 106 | t.Fatalf("TestLoggerMiscBytes0 Expected subscribe channel, got [nil]\n") 107 | } 108 | 109 | // Read MessageData 110 | var md MessageData 111 | select { 112 | case md = <-sc: 113 | case md = <-conn.MessageData: 114 | t.Fatalf("TestLoggerMiscBytes0 read channel error: expected [nil], got: [%v]\n", 115 | md.Message.Command) 116 | } 117 | 118 | if md.Error != nil { 119 | t.Fatalf("TestLoggerMiscBytes0 Expected no message data error, got [%v]\n", 120 | md.Error) 121 | } 122 | 123 | // The real tests here 124 | if len(md.Message.Body) != 0 { 125 | t.Fatalf("TestLoggerMiscBytes0 Expected body length 0, got [%v]\n", 126 | len(md.Message.Body)) 127 | } 128 | if string(md.Message.Body) != ms { 129 | t.Fatalf("TestLoggerMiscBytes0 Expected [%v], got [%v]\n", 130 | ms, string(md.Message.Body)) 131 | } 132 | // 133 | checkReceived(t, conn, false) 134 | e = conn.Disconnect(empty_headers) 135 | checkDisconnectError(t, e) 136 | _ = closeConn(t, n) 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "strings" 23 | "time" 24 | ) 25 | 26 | /* 27 | Encode a string per STOMP 1.1+ specifications. 28 | */ 29 | func encode(s string) string { 30 | r := s 31 | for _, tr := range codecValues { 32 | if strings.Index(r, tr.decoded) >= 0 { 33 | r = strings.Replace(r, tr.decoded, tr.encoded, -1) 34 | } 35 | } 36 | return r 37 | } 38 | 39 | /* 40 | Decode a string per STOMP 1.1+ specifications. 41 | */ 42 | func decode(s string) string { 43 | r := s 44 | for _, tr := range codecValues { 45 | if strings.Index(r, tr.encoded) >= 0 { 46 | r = strings.Replace(r, tr.encoded, tr.decoded, -1) 47 | } 48 | } 49 | return r 50 | } 51 | 52 | /* 53 | A network helper. Read from the wire until a 0x00 byte is encountered. 54 | */ 55 | func readUntilNul(c *Connection) ([]uint8, error) { 56 | var b []byte 57 | var e error 58 | c.setReadDeadline() 59 | 60 | if c.eltd != nil { 61 | st := time.Now().UnixNano() 62 | b, e = c.rdr.ReadBytes(0) 63 | c.eltd.run.ens += time.Now().UnixNano() - st 64 | c.eltd.run.ec++ 65 | 66 | } else { 67 | b, e = c.rdr.ReadBytes(0) 68 | } 69 | 70 | if c.checkReadError(e) != nil { 71 | return b, e 72 | } 73 | if len(b) == 1 { 74 | b = NULLBUFF 75 | } else { 76 | b = b[0 : len(b)-1] 77 | } 78 | return b, e 79 | } 80 | 81 | /* 82 | A network helper. Read a full message body with a known length that is 83 | > 0. Then read the trailing 'null' byte expected for STOMP frames. 84 | */ 85 | func readBody(c *Connection, l int) ([]uint8, error) { 86 | b := make([]byte, l) 87 | c.setReadDeadline() 88 | var n int 89 | var e error 90 | if c.eltd != nil { 91 | st := time.Now().UnixNano() 92 | n, e = io.ReadFull(c.rdr, b) 93 | c.eltd.rbdy.ens += time.Now().UnixNano() - st 94 | c.eltd.rbdy.ec++ 95 | } else { 96 | n, e = io.ReadFull(c.rdr, b) 97 | } 98 | 99 | if n < l && n != 0 { // Short read, e is ErrUnexpectedEOF 100 | c.log("SHORT READ", n, l, e) 101 | return b[0 : n-1], e 102 | } 103 | if c.checkReadError(e) != nil { // Other erors 104 | return b, e 105 | } 106 | c.setReadDeadline() 107 | _, _ = c.rdr.ReadByte() // trailing NUL 108 | if c.checkReadError(e) != nil { // Other erors 109 | return b, e 110 | } 111 | return b, e 112 | } 113 | 114 | /* 115 | Common Header Validation. 116 | */ 117 | func checkHeaders(h Headers, p string) error { 118 | if h == nil { 119 | return EHDRNIL 120 | } 121 | // Length check 122 | if e := h.Validate(); e != nil { 123 | return e 124 | } 125 | // Empty key / value check 126 | for i := 0; i < len(h); i += 2 { 127 | if h[i] == "" { 128 | return EHDRMTK 129 | } 130 | if p == SPL_10 && h[i+1] == "" { 131 | return EHDRMTV 132 | } 133 | } 134 | // UTF8 check 135 | if p != SPL_10 { 136 | _, e := h.ValidateUTF8() 137 | if e != nil { 138 | return e 139 | } 140 | } 141 | return nil 142 | } 143 | 144 | /* 145 | Internal function used by heartbeat initialization. 146 | */ 147 | func max(a, b int64) int64 { 148 | if a > b { 149 | return a 150 | } 151 | return b 152 | } 153 | 154 | /* 155 | Debug helper. Get properly formatted destination. 156 | */ 157 | func dumpmd(md MessageData) { 158 | fmt.Printf("Command: %s\n", md.Message.Command) 159 | fmt.Println("Headers:") 160 | for i := 0; i < len(md.Message.Headers); i += 2 { 161 | fmt.Printf("key:%s\t\tvalue:%s\n", 162 | md.Message.Headers[i], md.Message.Headers[i+1]) 163 | } 164 | fmt.Printf("Body: %s\n", string(md.Message.Body)) 165 | if md.Error != nil { 166 | fmt.Printf("Error: %s\n", md.Error.Error()) 167 | } else { 168 | fmt.Println("Error: nil") 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /disconnect.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "time" 23 | ) 24 | 25 | /* 26 | Disconnect from a STOMP broker. 27 | 28 | Shut down heart beats if necessary. 29 | Set connection status to false to disable further actions with this 30 | connection. 31 | 32 | 33 | Obtain a receipt unless the client specifically indicates a receipt request 34 | should be excluded. If the client actually asks for a receipt, use the 35 | supplied receipt id. Otherwise generate a unique receipt id and add that 36 | to the DISCONNECT headers. 37 | 38 | Example: 39 | h := stompngo.Headers{HK_RECEIPT, "receipt-id1"} // Ask for a receipt 40 | e := c.Disconnect(h) 41 | if e != nil { 42 | // Do something sane ... 43 | } 44 | fmt.Printf("%q\n", c.DisconnectReceipt) 45 | // Or: 46 | h := stompngo.Headers{"noreceipt", "true"} // Ask for a receipt 47 | e := c.Disconnect(h) 48 | if e != nil { 49 | // Do something sane ... 50 | } 51 | fmt.Printf("%q\n", c.DisconnectReceipt) 52 | 53 | */ 54 | func (c *Connection) Disconnect(h Headers) error { 55 | c.discLock.Lock() 56 | defer c.discLock.Unlock() 57 | // 58 | if !c.isConnected() { 59 | return ECONBAD 60 | } 61 | c.log(DISCONNECT, "start", h) 62 | e := checkHeaders(h, c.Protocol()) 63 | if e != nil { 64 | return e 65 | } 66 | ch := h.Clone() 67 | // If the caller does not want a receipt do not ask for one. Otherwise, 68 | // add a receipt request if caller did not specifically ask for one. This is 69 | // in the spirit of the specification, and allows reasonable resource cleanup 70 | // in both the client and the message broker. 71 | _, cwr := ch.Contains("noreceipt") 72 | if !cwr { 73 | if _, ok := ch.Contains(HK_RECEIPT); !ok { 74 | ch = append(ch, HK_RECEIPT, Uuid()) 75 | } 76 | } 77 | wrid := "" 78 | wrid, _ = ch.Contains(HK_RECEIPT) 79 | _ = wrid 80 | // 81 | f := Frame{DISCONNECT, ch, NULLBUFF} 82 | // 83 | r := make(chan error) 84 | if e = c.writeWireData(wiredata{f, r}); e != nil { 85 | return e 86 | } 87 | e = <-r 88 | // Drive shutdown logic 89 | // Only set DisconnectReceipt if we sucessfully received one, and it is 90 | // the one we were expecting. 91 | if !cwr && e == nil { 92 | // Can be RECEIPT or ERROR frame 93 | mds, e := c.getMessageData() 94 | // 95 | // fmt.Println(DISCONNECT, "sanchek", mds) 96 | // 97 | switch mds.Message.Command { 98 | case ERROR: 99 | e = fmt.Errorf("DISBRKERR -> %q", mds.Message) 100 | c.log(DISCONNECT, "errf", e) 101 | case RECEIPT: 102 | gr := mds.Message.Headers.Value(HK_RECEIPT_ID) 103 | if wrid != gr { 104 | e = fmt.Errorf("%s wanted:%s got:%s", EBADRID, wrid, gr) 105 | c.log(DISCONNECT, "nadrid", e) 106 | } else { 107 | c.DisconnectReceipt = mds 108 | c.log(DISCONNECT, "OK") 109 | } 110 | default: 111 | e = fmt.Errorf("DISBADFRM -> %q", mds.Message) 112 | c.log(DISCONNECT, "badf", e) 113 | } 114 | } 115 | c.log(DISCONNECT, "ends", ch) 116 | c.shutdown() 117 | c.sysAbort() 118 | c.log(DISCONNECT, "system shutdown cannel closed") 119 | return e 120 | } 121 | 122 | func (c *Connection) getMessageData() (MessageData, error) { 123 | var md MessageData 124 | var me error 125 | me = nil 126 | if os.Getenv("STOMP_MAXDISCTO") != "" { 127 | d, e := time.ParseDuration(os.Getenv("STOMP_MAXDISCTO")) 128 | if e != nil { 129 | c.log("DISCGETMD PDERROR -> ", e) 130 | md = <-c.input 131 | } else { 132 | c.log("DISCGETMD DUR -> ", d) 133 | select { 134 | case <-time.After(d): 135 | me = EDISCTO 136 | case md = <-c.input: 137 | } 138 | } 139 | } else { 140 | c.log("DISNOMAX", me) 141 | md = <-c.input 142 | } 143 | // 144 | return md, me 145 | } 146 | -------------------------------------------------------------------------------- /unsub_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | //"fmt" 21 | "log" 22 | 23 | //"os" 24 | "testing" 25 | //"time" 26 | ) 27 | 28 | func TestUnSubNoHeader(t *testing.T) { 29 | n, _ = openConn(t) 30 | ch := login_headers 31 | ch = headersProtocol(ch, SPL_10) // To start 32 | conn, e = Connect(n, ch) 33 | if e != nil { 34 | t.Fatalf("TestUnSubNoHeader CONNECT Failed: e:<%q> connresponse:<%q>\n", e, 35 | conn.ConnectResponse) 36 | } 37 | // 38 | for ti, tv := range unsubNoHeaderDataList { 39 | conn.protocol = tv.proto // Cheat, fake all protocols 40 | e = conn.Unsubscribe(empty_headers) 41 | if e == nil { 42 | t.Fatalf("TestUnSubNoHeader[%d] proto:%s expected:%q got:nil\n", 43 | ti, sp, tv.exe) 44 | } 45 | if e != tv.exe { 46 | t.Fatalf("TestUnSubNoHeader[%d] proto:%s expected:%q got:%q\n", 47 | ti, sp, tv.exe, e) 48 | } 49 | } 50 | // 51 | checkReceived(t, conn, false) 52 | e = conn.Disconnect(empty_headers) 53 | checkDisconnectError(t, e) 54 | _ = closeConn(t, n) 55 | log.Printf("TestUnSubNoHeader %d tests complete.\n", len(subNoHeaderDataList)) 56 | 57 | } 58 | 59 | func TestUnSubNoID(t *testing.T) { 60 | n, _ = openConn(t) 61 | ch := login_headers 62 | ch = headersProtocol(ch, SPL_10) // To start 63 | conn, e = Connect(n, ch) 64 | if e != nil { 65 | t.Fatalf("TestUnSubNoID CONNECT Failed: e:<%q> connresponse:<%q>\n", e, 66 | conn.ConnectResponse) 67 | } 68 | // 69 | for ti, tv := range unsubNoHeaderDataList { 70 | conn.protocol = tv.proto // Cheat, fake all protocols 71 | e = conn.Unsubscribe(empty_headers) 72 | if e != tv.exe { 73 | t.Fatalf("TestUnSubNoHeader[%d] proto:%s expected:%q got:%q\n", 74 | ti, sp, tv.exe, e) 75 | } 76 | } 77 | // 78 | checkReceived(t, conn, false) 79 | e = conn.Disconnect(empty_headers) 80 | checkDisconnectError(t, e) 81 | _ = closeConn(t, n) 82 | log.Printf("TestUnSubNoID %d tests complete.\n", len(unsubNoHeaderDataList)) 83 | } 84 | 85 | func TestUnSubBool(t *testing.T) { 86 | n, _ = openConn(t) 87 | ch := login_headers 88 | ch = headersProtocol(ch, SPL_10) // To start 89 | conn, e = Connect(n, ch) 90 | if e != nil { 91 | t.Fatalf("CONNECT Failed: e:<%q> connresponse:<%q>\n", e, 92 | conn.ConnectResponse) 93 | } 94 | // 95 | for ti, tv := range unsubBoolDataList { 96 | conn.protoLock.Lock() 97 | conn.protocol = tv.proto // Cheat, fake all protocols 98 | conn.protoLock.Unlock() 99 | 100 | // SUBSCRIBE Phase (depending on test data) 101 | if tv.subfirst { 102 | // Do a real SUBSCRIBE 103 | // sc, e = conn.Subscribe 104 | sh := fixHeaderDest(tv.subh) // destination fixed if needed 105 | sc, e = conn.Subscribe(sh) 106 | if e == nil && sc == nil { 107 | t.Fatalf("TestUnSubBool[%d] SUBSCRIBE proto:%s expected OK, got <%v> <%v>\n", 108 | ti, conn.protocol, e, sc) 109 | } 110 | if sc == nil { 111 | t.Fatalf("TestUnSubBool[%d] SUBSCRIBE, proto:[%s], channel is nil\n", 112 | ti, tv.proto) 113 | } 114 | if e != tv.exe1 { 115 | t.Fatalf("TestUnSubBool[%d] SUBSCRIBE NEQCHECK proto:%s expected:%v got:%q\n", 116 | ti, tv.proto, tv.exe1, e) 117 | } 118 | } 119 | 120 | //fmt.Printf("fs,unsubh: <%v>\n", tv.unsubh) 121 | // UNSCRIBE Phase 122 | sh := fixHeaderDest(tv.unsubh) // destination fixed if needed 123 | e = conn.Unsubscribe(sh) 124 | if e != tv.exe2 { 125 | t.Fatalf("TestUnSubBool[%d] UNSUBSCRIBE NEQCHECK proto:%s expected:%v got:%q\n", 126 | ti, tv.proto, tv.exe2, e) 127 | } 128 | } 129 | // 130 | checkReceived(t, conn, true) // true for this test 131 | _ = conn.Disconnect(empty_headers) 132 | _ = closeConn(t, n) 133 | log.Printf("TestUnSubBool %d tests complete.\n", len(unsubBoolDataList)) 134 | } 135 | -------------------------------------------------------------------------------- /header_methods.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "unicode/utf8" 21 | ) 22 | 23 | /* 24 | Add appends a key and value pair as a header to a set of Headers. 25 | */ 26 | func (h Headers) Add(k, v string) Headers { 27 | r := append(h, k, v) 28 | return r 29 | } 30 | 31 | /* 32 | AddHeaders appends one set of Headers to another. 33 | */ 34 | func (h Headers) AddHeaders(o Headers) Headers { 35 | r := append(h, o...) 36 | return r 37 | } 38 | 39 | /* 40 | Compare compares one set of Headers with another. 41 | */ 42 | func (h Headers) Compare(other Headers) bool { 43 | if len(h) != len(other) { 44 | return false 45 | } 46 | for i, v := range h { 47 | if v != other[i] { 48 | return false 49 | } 50 | } 51 | return true 52 | } 53 | 54 | /* 55 | Contains returns true if a set of Headers contains a key. 56 | */ 57 | func (h Headers) Contains(k string) (string, bool) { 58 | for i := 0; i < len(h); i += 2 { 59 | if h[i] == k { 60 | return h[i+1], true 61 | } 62 | } 63 | return "", false 64 | } 65 | 66 | /* 67 | ContainsKV returns true if a set of Headers contains a key and value pair. 68 | */ 69 | func (h Headers) ContainsKV(k string, v string) bool { 70 | for i := 0; i < len(h); i += 2 { 71 | if h[i] == k && h[i+1] == v { 72 | return true 73 | } 74 | } 75 | return false 76 | } 77 | 78 | /* 79 | Value returns a header value for a specified key. If the key is not present 80 | an empty string is returned. 81 | */ 82 | func (h Headers) Value(k string) string { 83 | for i := 0; i < len(h); i += 2 { 84 | if h[i] == k { 85 | return h[i+1] 86 | } 87 | } 88 | return "" 89 | } 90 | 91 | /* 92 | Index returns the index of a keader key in Headers. Return -1 if the 93 | key is not present. 94 | */ 95 | func (h Headers) Index(k string) int { 96 | r := -1 97 | for i := 0; i < len(h); i += 2 { 98 | if h[i] == k { 99 | r = i 100 | break 101 | } 102 | } 103 | return r 104 | } 105 | 106 | /* 107 | Validate performs bacic validation of a set of Headers. 108 | */ 109 | func (h Headers) Validate() error { 110 | if len(h)%2 != 0 { 111 | return EHDRLEN 112 | } 113 | return nil 114 | } 115 | 116 | /* 117 | ValidateUTF8 validates that header strings are UTF8. 118 | */ 119 | func (h Headers) ValidateUTF8() (string, error) { 120 | for i := range h { 121 | if !utf8.ValidString(h[i]) { 122 | return h[i], EHDRUTF8 123 | } 124 | } 125 | return "", nil 126 | } 127 | 128 | /* 129 | Clone copies a set of Headers. 130 | */ 131 | func (h Headers) Clone() Headers { 132 | r := make(Headers, len(h)) 133 | copy(r, h) 134 | return r 135 | } 136 | 137 | /* 138 | Delete removes a key and value pair from a set of Headers. 139 | */ 140 | func (h Headers) Delete(k string) Headers { 141 | r := h.Clone() 142 | i := r.Index(k) 143 | if i >= 0 { 144 | r = append(r[:i], r[i+2:]...) 145 | } 146 | return r 147 | } 148 | 149 | /* 150 | Size returns the size of Headers on the wire, in bytes. 151 | */ 152 | func (h Headers) Size(e bool) int64 { 153 | l := 0 154 | for i := 0; i < len(h); i += 2 { 155 | if e { 156 | l += len(encode(h[i])) + 1 + len(encode(h[i+1])) + 1 157 | } else { 158 | l += len(h[i]) + 1 + len(h[i+1]) + 1 159 | } 160 | } 161 | return int64(l) 162 | } 163 | 164 | /* 165 | String makes Headers a Stringer. 166 | */ 167 | func (h Headers) String() string { 168 | ec := h.Validate() 169 | if ec != nil { 170 | return ec.Error() 171 | } 172 | b := make([]byte, 0, 1024) 173 | for i := 0; i < len(h); i += 2 { 174 | b = append(b, h[i]+":"+h[i+1]+"\n"...) 175 | } 176 | return string(b) 177 | } 178 | 179 | /* 180 | Bytes returns a byte slice of the headers 181 | */ 182 | func (h Headers) Bytes() []byte { 183 | b := make([]byte, 0, 1024) 184 | for i := 0; i < len(h); i += 2 { 185 | b = append(b, h[i]+":"+h[i+1]+"\n"...) 186 | } 187 | return b 188 | } 189 | -------------------------------------------------------------------------------- /trans_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "log" 21 | "testing" 22 | ) 23 | 24 | /* 25 | Test transaction errors. 26 | */ 27 | func TestTransErrors(t *testing.T) { 28 | for pi, sp := range Protocols() { 29 | n, _ = openConn(t) 30 | ch := login_headers 31 | ch = headersProtocol(ch, sp) 32 | conn, e = Connect(n, ch) 33 | if e != nil { 34 | t.Fatalf("TestTransErrors[%d/%s] CONNECT expected OK, got: %v\n", pi, 35 | sp, e) 36 | } 37 | for ti, tv := range transBasicList { 38 | switch tv.action { 39 | case BEGIN: 40 | e = conn.Begin(tv.th) 41 | case COMMIT: 42 | e = conn.Commit(tv.th) 43 | case ABORT: 44 | e = conn.Abort(tv.th) 45 | default: 46 | t.Fatalf("TestTransErrors[%d/%s] %s BAD DATA[%d]\n", pi, 47 | sp, tv.action, ti) 48 | } 49 | if e == nil { 50 | t.Fatalf("TestTransErrors[%d/%s] %s expected error[%d], got %v\n", pi, 51 | sp, tv.action, ti, e) 52 | } 53 | if e != tv.te { 54 | t.Fatalf("TestTransErrors[%d/%s] %s expected[%d]: %v, got %v\n", pi, 55 | sp, tv.action, ti, tv.te, e) 56 | } 57 | } 58 | checkReceived(t, conn, false) 59 | e = conn.Disconnect(empty_headers) 60 | checkDisconnectError(t, e) 61 | _ = closeConn(t, n) 62 | } 63 | } 64 | 65 | /* 66 | Test transaction send and commit. 67 | */ 68 | func TestTransSendCommit(t *testing.T) { 69 | 70 | for pi, sp := range Protocols() { 71 | n, _ = openConn(t) 72 | ch := login_headers 73 | ch = headersProtocol(ch, sp) 74 | conn, _ = Connect(n, ch) 75 | if e != nil { 76 | t.Fatalf("TestTransSendCommit[%d/%s] CONNECT expected OK, got: %v\n", 77 | pi, 78 | sp, e) 79 | } 80 | 81 | for ti, tv := range transSendCommitList { 82 | // BEGIN 83 | e = conn.Begin(Headers{HK_TRANSACTION, tv.tid}) 84 | if e != nil { 85 | t.Fatalf("TestTransSendCommit BEGIN[%d][%d] expected [%v], got: [%v]\n", 86 | pi, ti, tv.exe, e) 87 | } 88 | // SEND 89 | qn := tdest("/queue/" + tv.tid + ".1") 90 | log.Println("TSCQN:", qn) 91 | sh := Headers{HK_DESTINATION, qn, 92 | HK_TRANSACTION, tv.tid} 93 | e = conn.Send(sh, tm) 94 | if e != nil { 95 | t.Fatalf("TestTransSendCommit SEND[%d][%d] expected [%v], got: [%v]\n", 96 | pi, ti, tv.exe, e) 97 | } 98 | // COMMIT 99 | e = conn.Commit(Headers{HK_TRANSACTION, tv.tid}) 100 | if e != nil { 101 | t.Fatalf("TestTransSendCommit COMMIT[%d][%d] expected [%v], got: [%v]\n", 102 | pi, ti, tv.exe, e) 103 | } 104 | } 105 | // 106 | checkReceived(t, conn, false) 107 | e = conn.Disconnect(empty_headers) 108 | checkDisconnectError(t, e) 109 | _ = closeConn(t, n) 110 | } 111 | } 112 | 113 | /* 114 | Test transaction send then abort. 115 | */ 116 | 117 | func TestTransSendAbort(t *testing.T) { 118 | 119 | for pi, sp := range Protocols() { 120 | n, _ = openConn(t) 121 | ch := login_headers 122 | ch = headersProtocol(ch, sp) 123 | conn, _ = Connect(n, ch) 124 | if e != nil { 125 | t.Fatalf("TestTransSendAbort[%d/%s] CONNECT expected OK, got: %v\n", 126 | pi, 127 | sp, e) 128 | } 129 | for ti, tv := range transSendAbortList { 130 | // BEGIN 131 | e = conn.Begin(Headers{HK_TRANSACTION, tv.tid}) 132 | if e != nil { 133 | t.Fatalf("TestTransSendAbort BEGIN[%d][%d] expected [%v], got: [%v]\n", 134 | pi, ti, tv.exe, e) 135 | } 136 | // SEND 137 | sh := Headers{HK_DESTINATION, tdest("/queue/" + tv.tid + ".1"), 138 | HK_TRANSACTION, tv.tid} 139 | e = conn.Send(sh, tm) 140 | if e != nil { 141 | t.Fatalf("TestTransSendAbort SEND[%d][%d] expected [%v], got: [%v]\n", 142 | pi, ti, tv.exe, e) 143 | } 144 | // ABORT 145 | e = conn.Abort(Headers{HK_TRANSACTION, tv.tid}) 146 | if e != nil { 147 | t.Fatalf("TestTransSendAbort COMMIT[%d][%d] expected [%v], got: [%v]\n", 148 | pi, ti, tv.exe, e) 149 | } 150 | } 151 | // 152 | checkReceived(t, conn, false) 153 | e = conn.Disconnect(empty_headers) 154 | checkDisconnectError(t, e) 155 | _ = closeConn(t, n) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /connect.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "bufio" 21 | "log" 22 | "os" 23 | 24 | // "fmt" 25 | "net" 26 | "time" 27 | 28 | "github.com/gmallard/stompngo/senv" 29 | ) 30 | 31 | /* 32 | Wrapper for primary STOMP Connect function that returns an interface. 33 | */ 34 | func NewConnector(n net.Conn, h Headers) (STOMPConnector, error) { 35 | return Connect(n, h) 36 | } 37 | 38 | /* 39 | Primary STOMP Connect. 40 | 41 | For STOMP 1.1+ the Headers parameter MUST contain the headers required 42 | by the specification. Those headers are not magically inferred. 43 | 44 | Example: 45 | // Obtain a network connection 46 | n, e := net.Dial(NetProtoTCP, "localhost:61613") 47 | if e != nil { 48 | // Do something sane ... 49 | } 50 | h := stompngo.Headers{} // A STOMP 1.0 connection request 51 | c, e := stompngo.Connect(n, h) 52 | if e != nil { 53 | // Do something sane ... 54 | } 55 | // Use c 56 | 57 | Example: 58 | // Obtain a network connection 59 | n, e := net.Dial(NetProtoTCP, "localhost:61613") 60 | if e != nil { 61 | // Do something sane ... 62 | } 63 | h := stompngo.Headers{HK_ACCEPT_VERSION, "1.1", 64 | HK_HOST, "localhost"} // A STOMP 1.1 connection 65 | c, e := stompngo.Connect(n, h) 66 | if e != nil { 67 | // Do something sane ... 68 | } 69 | // Use c 70 | */ 71 | func Connect(n net.Conn, h Headers) (*Connection, error) { 72 | if h == nil { 73 | return nil, EHDRNIL 74 | } 75 | if e := h.Validate(); e != nil { 76 | return nil, e 77 | } 78 | if _, ok := h.Contains(HK_RECEIPT); ok { 79 | return nil, ENORECPT 80 | } 81 | ch := h.Clone() 82 | //fmt.Printf("CONDB01\n") 83 | c := &Connection{netconn: n, 84 | input: make(chan MessageData, 1), 85 | output: make(chan wiredata), 86 | connected: false, 87 | session: "", 88 | protocol: SPL_10, 89 | subs: make(map[string]*subscription), 90 | DisconnectReceipt: MessageData{}, 91 | ssdc: make(chan struct{}), 92 | wtrsdc: make(chan struct{}), 93 | scc: 1, 94 | dld: &deadlineData{}} 95 | 96 | // Basic metric data 97 | c.mets = &metrics{st: time.Now()} 98 | 99 | // Assumed for now 100 | c.MessageData = c.input 101 | 102 | // Check that the client wants a version we support 103 | if e := c.checkClientVersions(h); e != nil { 104 | return c, e 105 | } 106 | // Optional logging from connection start 107 | ln := senv.WantLogger() 108 | if ln != "" { 109 | c.SetLogger(log.New(os.Stdout, ln+" ", 110 | log.Ldate|log.Lmicroseconds|log.Lshortfile)) 111 | } 112 | 113 | // Initialize elapsed time tracking data if needed 114 | c.eltd = nil 115 | if os.Getenv("STOMP_TRACKELT") != "" { 116 | c.eltd = &eltmets{} 117 | } 118 | 119 | // OK, put a CONNECT on the wire 120 | c.wtr = bufio.NewWriterSize(n, senv.WriteBufsz()) // Create the writer 121 | // fmt.Println("TCDBG", c.wtr.Size()) 122 | go c.writer() // Start it 123 | var f Frame 124 | if senv.UseStomp() { 125 | if ch.Value("accept-version") == SPL_11 || ch.Value("accept-version") == SPL_12 { 126 | f = Frame{STOMP, ch, NULLBUFF} // Create actual STOMP frame 127 | } else { 128 | f = Frame{CONNECT, ch, NULLBUFF} // Create actual STOMP frame 129 | } 130 | // fmt.Printf("Frame: %q\n", f) 131 | } else { 132 | f = Frame{CONNECT, ch, NULLBUFF} // Create actual CONNECT frame 133 | // fmt.Printf("Frame: %q\n", f) 134 | } 135 | r := make(chan error) // Make the error channel for a write 136 | if e := c.writeWireData(wiredata{f, r}); e != nil { // Send the CONNECT frame 137 | return c, e 138 | } 139 | e := <-r // Retrieve any error 140 | // 141 | if e != nil { 142 | c.sysAbort() // Shutdown, we are done with errors 143 | return c, e 144 | } 145 | //fmt.Printf("CONDB03\n") 146 | // 147 | e = c.connectHandler(ch) 148 | if e != nil { 149 | c.sysAbort() // Shutdown , we are done with errors 150 | return c, e 151 | } 152 | //fmt.Printf("CONDB04\n") 153 | // We are connected 154 | go c.reader() 155 | // 156 | return c, e 157 | } 158 | -------------------------------------------------------------------------------- /data_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | /* 24 | Data Test: Frame Basic. 25 | */ 26 | func TestDataFrameBasic(t *testing.T) { 27 | cm := CONNECT 28 | wh := Headers{"keya", "valuea"} 29 | ms := "The Message Body" 30 | f := &Frame{Command: cm, Headers: wh, Body: []byte(ms)} 31 | // 32 | if cm != f.Command { 33 | t.Fatalf("TestDataFrameBasic Command, expected: [%v], got [%v]\n", 34 | cm, f.Command) 35 | } 36 | if !wh.Compare(f.Headers) { 37 | t.Fatalf("TestDataFrameBasic Headers, expected: [true], got [false], for [%v] [%v]\n", 38 | wh, f.Headers) 39 | } 40 | if ms != string(f.Body) { 41 | t.Fatalf("TestDataFrameBasic Body string, expected: [%v], got [%v]\n", 42 | ms, string(f.Body)) 43 | } 44 | } 45 | 46 | /* 47 | Data Test: Message Basic. 48 | */ 49 | func TestDataMessageBasic(t *testing.T) { 50 | fc := CONNECT 51 | wh := Headers{"keya", "valuea"} 52 | ms := "The Message Body" 53 | m := &Message{Command: fc, Headers: wh, Body: []byte(ms)} 54 | // 55 | if fc != m.Command { 56 | t.Fatalf("TestDataMessageBasic Command, expected: [%v], got [%v]\n", 57 | fc, m.Command) 58 | } 59 | if !wh.Compare(m.Headers) { 60 | t.Fatalf("TestDataMessageBasic Headers, expected: [true], got [false], for [%v] [%v]\n", 61 | wh, m.Headers) 62 | } 63 | if ms != m.BodyString() { 64 | t.Fatalf("TestDataMessageBasic Body string, expected: [%v], got [%v]\n", 65 | ms, m.BodyString()) 66 | } 67 | } 68 | 69 | /* 70 | Data Test: protocols. 71 | */ 72 | func TestDataprotocols(t *testing.T) { 73 | if !Supported(SPL_10) { 74 | t.Fatalf("TestDataprotocolsExpected: [true], got: [false] for protocol level %v\n", 75 | SPL_10) 76 | } 77 | if !Supported(SPL_11) { 78 | t.Fatalf("TestDataprotocols Expected: [true], got: [false] for protocol level %v\n", 79 | SPL_11) 80 | } 81 | if !Supported(SPL_12) { 82 | t.Fatalf("TestDataprotocols Expected: [true], got: [false] for protocol level %v\n", 83 | SPL_12) 84 | } 85 | if Supported("9.9") { 86 | t.Fatalf("TestDataprotocols Expected: [false], got: [true] for protocol level %v\n", 87 | "9.9") 88 | } 89 | // 90 | for _, v := range suptests { 91 | b := Supported(v.v) 92 | if b != v.s { 93 | t.Fatalf("TestDataprotocols Expected: [%v] for protocol level [%v]\n", 94 | v.s, v.v) 95 | } 96 | } 97 | } 98 | 99 | /* 100 | Data test: Protocols. 101 | */ 102 | func TestDataProtocols(t *testing.T) { 103 | for i, p := range Protocols() { 104 | if supported[i] != p { 105 | t.Fatalf("TestDataProtocols Expected [%v], got [%v]\n", 106 | supported[i], p) 107 | } 108 | } 109 | } 110 | 111 | /* 112 | Data test: Error. 113 | */ 114 | func TestDataError(t *testing.T) { 115 | es := "An error string" 116 | e = Error(es) 117 | if es != e.Error() { 118 | t.Fatalf("TestDataError Expected [%v], got [%v]\n", es, e.Error()) 119 | } 120 | } 121 | 122 | /* 123 | Data Test: Message Size. 124 | */ 125 | func TestDataMessageSize(t *testing.T) { 126 | f := CONNECT 127 | wh := Headers{"keya", "valuea"} 128 | ms := "The Message Body" 129 | m := &Message{Command: f, Headers: wh, Body: []byte(ms)} 130 | b := false 131 | // 132 | var w int64 = int64(len(CONNECT)) + 1 + wh.Size(b) + 1 + int64(len(ms)) + 1 133 | r := m.Size(b) 134 | if w != r { 135 | t.Fatalf("TestDataMessageSize Message size, expected: [%d], got [%d]\n", 136 | w, r) 137 | } 138 | } 139 | 140 | /* 141 | Data Test: Broker Command Validity. 142 | */ 143 | func TestDataBrokerCmdVal(t *testing.T) { 144 | var tData = map[string]bool{MESSAGE: true, ERROR: true, RECEIPT: true, 145 | CONNECT: false, DISCONNECT: false, SUBSCRIBE: false, BEGIN: false, 146 | STOMP: false, COMMIT: false, ABORT: false, UNSUBSCRIBE: false, 147 | SEND: false, ACK: false, NACK: false, CONNECTED: false, 148 | "JUNK": false} 149 | for k, v := range tData { 150 | if v != validCmds[k] { 151 | t.Fatalf("TestDataBrokerCmdVal Command Validity, expected: [%t], got [%t] for key [%s]\n", 152 | v, 153 | validCmds[k], k) 154 | } 155 | } 156 | } 157 | 158 | func BenchmarkHeaderAdd(b *testing.B) { 159 | h := Headers{"k1", "v1"} 160 | for n := 0; n < b.N; n++ { 161 | _ = h.Add("akey", "avalue") 162 | } 163 | } 164 | 165 | func BenchmarkHeaderAppend(b *testing.B) { 166 | h := []string{"k1", "v1"} 167 | for n := 0; n < b.N; n++ { 168 | _ = append(h, "akey", "avalue") 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /shovel_dupe_headers_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "log" 21 | "testing" 22 | ) 23 | 24 | var _ = log.Println 25 | 26 | /* 27 | Test a Stomp 1.1+ duplicate header shovel. 28 | The Stomp 1.0 specification level is silent on the subject of duplicate 29 | headers. For STOMP 1.1+ this client package is strictly compliant: if user 30 | logic passes duplicates, they are sent to the broker. 31 | The STOMP 1.1 and 1.2 specifications: 32 | a) differ slightly in specified broker behavior 33 | b) allow quite a bit of variance in broker behavoir. 34 | As usual: YMMV. 35 | */ 36 | func TestShovelDupeHeaders(t *testing.T) { 37 | for _, sp := range oneOnePlusProtos { 38 | n, _ = openConn(t) 39 | ch := login_headers 40 | ch = headersProtocol(ch, sp) 41 | conn, e = Connect(n, ch) 42 | if e != nil { 43 | t.Fatalf("TestShovelDupeHeaders CONNECT expected no error, got [%v]\n", e) 44 | } 45 | 46 | // 47 | ms := "A message" 48 | d := tdest("/queue/subunsub.shovel.01") 49 | sh := Headers{HK_DESTINATION, d} 50 | sh = sh.AddHeaders(tsdhHeaders) 51 | _ = conn.Send(sh, ms) 52 | // 53 | sbh := Headers{HK_DESTINATION, d, HK_ID, d} 54 | sc, e = conn.Subscribe(sbh) 55 | if e != nil { 56 | t.Fatalf("TestShovelDupeHeaders Expected no subscribe error, got [%v]\n", e) 57 | } 58 | if sc == nil { 59 | t.Fatalf("TestShovelDupeHeaders Expected subscribe channel, got [nil]\n") 60 | } 61 | 62 | // Read MessageData 63 | var md MessageData 64 | select { 65 | case md = <-sc: 66 | case md = <-conn.MessageData: 67 | t.Fatalf("TestShovelDupeHeaders read channel error: expected [nil], got: [%v]\n", 68 | md.Message.Command) 69 | } 70 | 71 | // 72 | if md.Error != nil { 73 | t.Fatalf("TestShovelDupeHeaders Expected no message data error, got [%v]\n", 74 | md.Error) 75 | } 76 | rm := md.Message 77 | //log.Printf("TestShovelDupeHeaders SDHT01: %s <%v>\n", conn.Protocol(), 78 | // rm.Headers) 79 | rd := rm.Headers.Value(HK_DESTINATION) 80 | if rd != d { 81 | t.Fatalf("TestShovelDupeHeaders Expected destination [%v], got [%v]\n", 82 | d, rd) 83 | } 84 | rs := rm.Headers.Value(HK_SUBSCRIPTION) 85 | if rs != d { 86 | t.Fatalf("TestShovelDupeHeaders Expected subscription [%v], got [%v]\n", 87 | d, rs) 88 | } 89 | 90 | // Broker behavior can differ WRT repeated header entries 91 | // they receive. Here we try to adjust to observed broker behavior 92 | // with the brokers used in local testing. 93 | // Also note that the wording of the 1.1 and 1.2 specs is slightly 94 | // different WRT repeated header entries. 95 | // In any case: YMMV. 96 | 97 | // 98 | switch brokerid { 99 | case TEST_AMQ: 100 | if !rm.Headers.ContainsKV("dupkey1", "value0") { 101 | t.Fatalf("TestShovelDupeHeaders AMQ Expected true for [%v], [%v]\n", "dupkey1", "value0") 102 | } 103 | case TEST_RMQ: 104 | if !rm.Headers.ContainsKV("dupkey1", "value0") { 105 | t.Fatalf("TestShovelDupeHeaders RMQ Expected true for [%v], [%v]\n", "dupkey1", "value0") 106 | } 107 | case TEST_APOLLO: 108 | if !rm.Headers.ContainsKV("dupkey1", "value0") { 109 | t.Fatalf("TestShovelDupeHeaders APOLLO Expected true for [%v], [%v]\n", "dupkey1", "value0") 110 | } 111 | e = checkDupeHeaders(rm.Headers, wantedDupeVAll) 112 | if e != nil { 113 | t.Fatalf("TestShovelDupeHeaders APOLLO Expected dupe headers, but something is missing: %v %v\n", 114 | rm.Headers, wantedDupeVAll) 115 | } 116 | case TEST_ARTEMIS: 117 | // 118 | if sp == SPL_11 { 119 | if !rm.Headers.ContainsKV("dupkey1", "value2") { 120 | t.Fatalf("TestShovelDupeHeaders MAIN Expected true for [%v], [%v]\n", "dupkey1", "value2") 121 | } 122 | } else { // This is SPL_12 123 | if !rm.Headers.ContainsKV("dupkey1", "value0") { 124 | t.Fatalf("TestShovelDupeHeaders MAIN Expected true for [%v], [%v]\n", "dupkey1", "value0") 125 | } 126 | } 127 | break 128 | //} 129 | default: 130 | } 131 | // 132 | uh := Headers{HK_ID, rs, HK_DESTINATION, d} 133 | e = conn.Unsubscribe(uh) 134 | if e != nil { 135 | t.Fatalf("TestShovelDupeHeaders Expected no unsubscribe error, got [%v]\n", 136 | e) 137 | } 138 | // 139 | checkReceived(t, conn, false) 140 | e = conn.Disconnect(empty_headers) 141 | checkDisconnectError(t, e) 142 | _ = closeConn(t, n) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | ## Contributors (by first author date) 4 | 5 | 6 | Contribution Information: 7 | 8 | 9 | 10 | 11 | 14 | 17 | 20 | 21 | 22 | 25 | 28 | 34 | 35 | 36 | 39 | 42 | 48 | 49 | 50 | 53 | 56 | 62 | 63 | 64 | 67 | 70 | 76 | 77 | 78 | 81 | 84 | 90 | 91 | 92 | 95 | 98 | 104 | 105 | 106 | 109 | 112 | 118 | 119 | 120 | 123 | 126 | 132 | 133 | 134 | 137 | 140 | 146 | 147 | 148 | 151 | 154 | 160 | 161 | 162 | 165 | 168 | 174 | 175 | 176 | 179 | 182 | 188 | 189 |
12 | First Author Date 13 | 15 | (Commit Count) 16 | 18 | Name / E-mail 19 |
23 | 2011-10-10 24 | 26 | (0240) 27 | 29 | 30 | gmallard 31 | 32 | / <allard.guy.m@gmail.com> 33 |
37 | 2014-02-02 38 | 40 | (0001) 41 | 43 | 44 | Kelsey Hightower 45 | 46 | / <kelsey.hightower@gmail.com> 47 |
51 | 2014-12-12 52 | 54 | (0001) 55 | 57 | 58 | Logan Attwood 59 | 60 | / <logan@therounds.ca> 61 |
65 | 2015-04-16 66 | 68 | (0002) 69 | 71 | 72 | Max Garvey 73 | 74 | / <mgarvey@monsooncommerce.com> 75 |
79 | 2016-04-11 80 | 82 | (0001) 83 | 85 | 86 | Joakim Sernbrant 87 | 88 | / <joakim.sernbrant@trioptima.com> 89 |
93 | 2016-07-30 94 | 96 | (0152) 97 | 99 | 100 | Guy M. Allard 101 | 102 | / <allard.guy.m@gmail.com> 103 |
107 | 2017-03-01 108 | 110 | (0001) 111 | 113 | 114 | Dan Corin 115 | 116 | / <danielcorin@users.noreply.github.com> 117 |
121 | 2017-05-05 122 | 124 | (0001) 125 | 127 | 128 | Jason Libbey 129 | 130 | / <jlibbey@uber.com> 131 |
135 | 2017-06-05 136 | 138 | (0001) 139 | 141 | 142 | Dan Corin 143 | 144 | / <dancorin@uber.com> 145 |
149 | 2017-11-06 150 | 152 | (0006) 153 | 155 | 156 | tomsawyer 157 | 158 | / <tomsawyer126@gmail.com> 159 |
163 | 2017-11-20 164 | 166 | (0001) 167 | 169 | 170 | itomsawyer 171 | 172 | / <tomsawyer126@gmail.com> 173 |
177 | 2018-03-12 178 | 180 | (0001) 181 | 183 | 184 | GAOXIANG4 185 | 186 | / <gaoxiang4@kingsoft.com> 187 |
190 | -------------------------------------------------------------------------------- /suppress_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import "testing" 20 | 21 | var () 22 | 23 | /* 24 | Test suppress_content_length header. 25 | */ 26 | func TestSuppressContentLength(t *testing.T) { 27 | for _, sp := range Protocols() { 28 | n, _ = openConn(t) 29 | ch := login_headers 30 | ch = headersProtocol(ch, sp) 31 | conn, e = Connect(n, ch) 32 | if e != nil { 33 | t.Fatalf("TestSuppressContentLength CONNECT Failed: e:<%q> connresponse:<%q>\n", 34 | e, 35 | conn.ConnectResponse) 36 | } 37 | // 38 | d := tdest("/queue/suppress.content.length") 39 | id := Uuid() 40 | sbh := Headers{HK_DESTINATION, d, HK_ID, id} 41 | sc, e = conn.Subscribe(sbh) 42 | if e != nil { 43 | t.Fatalf("TestSuppressContentLength Expected no subscribe error, got [%v]\n", 44 | e) 45 | } 46 | if sc == nil { 47 | t.Fatalf("TestSuppressContentLength Expected subscribe channel, got [nil]\n") 48 | } 49 | 50 | // Do the work here 51 | var v MessageData 52 | sh := Headers{HK_DESTINATION, d, HK_SUPPRESS_CL, "yes"} 53 | for tn, tv := range tsclData { 54 | // 55 | e = conn.SendBytes(sh, tv.ba) 56 | if e != nil { 57 | t.Fatalf("TestSuppressContentLength Expected no send error, got [%v]\n", 58 | e) 59 | } 60 | select { 61 | case v = <-sc: 62 | case v = <-conn.MessageData: 63 | t.Fatalf("TestSuppressContentLength Expected no RECEIPT/ERROR error, got [%v]\n", 64 | v) 65 | } 66 | if tv.wanted != string(v.Message.Body) { 67 | t.Fatalf("TestSuppressContentLength Expected same data, tn:%d wanted[%v], got [%v]\n", 68 | tn, tv.wanted, string(v.Message.Body)) 69 | } 70 | } 71 | 72 | // Finally Unsubscribe 73 | uh := Headers{HK_DESTINATION, d, HK_ID, id} 74 | e = conn.Unsubscribe(uh) 75 | if e != nil { 76 | t.Fatalf("TestSuppressContentLength Expected no unsubscribe error, got [%v]\n", 77 | e) 78 | } 79 | 80 | checkReceived(t, conn, false) 81 | e = conn.Disconnect(empty_headers) 82 | checkDisconnectError(t, e) 83 | _ = closeConn(t, n) 84 | } 85 | } 86 | 87 | /* 88 | Test suppress_content_type header. 89 | */ 90 | func TestSuppressContentType(t *testing.T) { 91 | for _, sp := range Protocols() { 92 | n, _ = openConn(t) 93 | ch := login_headers 94 | ch = headersProtocol(ch, sp) 95 | conn, e = Connect(n, ch) 96 | if e != nil { 97 | t.Fatalf("TestSuppressContentType CONNECT Failed: e:<%q> connresponse:<%q>\n", 98 | e, 99 | conn.ConnectResponse) 100 | } 101 | // l := log.New(os.Stdout, "TSCT", log.Ldate|log.Lmicroseconds) 102 | // conn.SetLogger(l) 103 | 104 | // 105 | d := tdest("/queue/suppress.content.type") 106 | id := Uuid() 107 | sbh := Headers{HK_DESTINATION, d, HK_ID, id} 108 | sc, e = conn.Subscribe(sbh) 109 | if e != nil { 110 | t.Fatalf("TestSuppressContentType Expected no subscribe error, got [%v]\n", 111 | e) 112 | } 113 | if sc == nil { 114 | t.Fatalf("TestSuppressContentType Expected subscribe channel, got [nil]\n") 115 | } 116 | 117 | // Do the work here 118 | var v MessageData 119 | var sh Headers 120 | for tn, tv := range tsctData { 121 | if tv.doSuppress { 122 | sh = Headers{HK_DESTINATION, d, HK_SUPPRESS_CT, "yes"} 123 | } else { 124 | // sh = Headers{HK_DESTINATION, d, HK_SUPPRESS_CT} 125 | sh = Headers{HK_DESTINATION, d} 126 | } 127 | // 128 | e = conn.Send(sh, tv.body) 129 | if e != nil { 130 | t.Fatalf("TestSuppressContentType Expected no send error, got [%v]\n", 131 | e) 132 | } 133 | // fmt.Printf("SCT01 tn:%d sent:%s\n", tn, tv.body) 134 | select { 135 | case v = <-sc: 136 | case v = <-conn.MessageData: 137 | t.Fatalf("TestSuppressContentType Expected no RECEIPT/ERROR error, got [%v]\n", 138 | v) 139 | } 140 | _, try := v.Message.Headers.Contains(HK_CONTENT_TYPE) 141 | // fmt.Printf("DUMP: md:%#v\n", v) 142 | if tv.doSuppress { 143 | if try != tv.wanted { 144 | t.Fatalf("TestSuppressContentType tn:%d wanted:%t got:%t\n", 145 | tn, tv.wanted, try) 146 | } 147 | } 148 | } 149 | // Finally Unsubscribe 150 | uh := Headers{HK_DESTINATION, d, HK_ID, id} 151 | e = conn.Unsubscribe(uh) 152 | if e != nil { 153 | t.Fatalf("TestSuppressContentType Expected no unsubscribe error, got [%v]\n", 154 | e) 155 | } 156 | 157 | checkReceived(t, conn, false) 158 | e = conn.Disconnect(empty_headers) 159 | checkDisconnectError(t, e) 160 | _ = closeConn(t, n) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /codec_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "log" 21 | "os" 22 | "testing" 23 | "time" 24 | ) 25 | 26 | var _ = log.Println 27 | 28 | // Test STOMP 1.1 Header Codec - Basic Encode. 29 | func TestCodecEncodeBasic(t *testing.T) { 30 | for _, _ = range Protocols() { 31 | for _, ede := range tdList { 32 | ev := encode(ede.decoded) 33 | if ede.encoded != ev { 34 | t.Fatalf("TestCodecEncodeBasic ENCODE ERROR: expected: [%v] got: [%v]", 35 | ede.encoded, ev) 36 | } 37 | } 38 | } 39 | } 40 | 41 | /* 42 | Test STOMP 1.1 Header Codec - Basic Decode. 43 | */ 44 | func TestCodecDecodeBasic(t *testing.T) { 45 | for _, _ = range Protocols() { 46 | for _, ede := range tdList { 47 | dv := decode(ede.encoded) 48 | if ede.decoded != dv { 49 | t.Fatalf("TestCodecDecodeBasic DECODE ERROR: expected: [%v] got: [%v]", 50 | ede.decoded, dv) 51 | } 52 | } 53 | } 54 | } 55 | 56 | func BenchmarkCodecEncode(b *testing.B) { 57 | for _, _ = range Protocols() { 58 | for i := 0; i < len(tdList); i++ { 59 | for n := 0; n < b.N; n++ { 60 | _ = encode(tdList[i].decoded) 61 | } 62 | } 63 | } 64 | } 65 | 66 | func BenchmarkCodecDecode(b *testing.B) { 67 | for _, _ = range Protocols() { 68 | for i := 0; i < len(tdList); i++ { 69 | for n := 0; n < b.N; n++ { 70 | _ = decode(tdList[i].encoded) 71 | } 72 | } 73 | } 74 | } 75 | 76 | /* 77 | Test STOMP 1.1 Send / Receive - no codec error. 78 | */ 79 | func TestCodecSendRecvCodec(t *testing.T) { 80 | if os.Getenv("STOMP_ARTEMIS") != "" { 81 | return 82 | } 83 | // 84 | for _, p := range Protocols() { 85 | usemap := srcdmap[p] 86 | //log.Printf("Protocol: %s\n", p) 87 | //log.Printf("MapLen: %d\n", len(usemap)) 88 | for _, v := range usemap { 89 | 90 | // 91 | // RMQ and STOMP Level 1.0 : 92 | // Headers are encoded (as if the STOMP protocol were 1.1 93 | // or 1.2). 94 | // MAYBEDO: Report issue. (Is this a bug or a feature?) 95 | // 96 | if p == SPL_10 && os.Getenv("STOMP_RMQ") != "" { 97 | continue 98 | } 99 | 100 | n, _ = openConn(t) 101 | ch := login_headers 102 | ch = headersProtocol(ch, p) 103 | conn, e = Connect(n, ch) 104 | if e != nil { 105 | t.Fatalf("TestCodecSendRecvCodec CONNECT expected nil, got %v\n", e) 106 | } 107 | // 108 | d := tdest("/queue/gostomp.codec.sendrecv.1.protocol." + p) 109 | ms := "msg.codec.sendrecv.1.protocol." + p + " - a message" 110 | wh := Headers{HK_DESTINATION, d} 111 | 112 | //log.Printf("TestData: %+v\n", v) 113 | sh := wh.Clone() 114 | for i := range v.sk { 115 | sh = sh.Add(v.sk[i], v.sv[i]) 116 | } 117 | // Send 118 | //log.Printf("Send Headers: %v\n", sh) 119 | e = conn.Send(sh, ms) 120 | if e != nil { 121 | t.Fatalf("TestCodecSendRecvCodec Send failed: %v protocol:%s\n", 122 | e, p) 123 | } 124 | // Check for ERROR frame 125 | time.Sleep(1e9 / 8) // Wait one eigth 126 | // Poll for adhoc ERROR from server 127 | select { 128 | case vx := <-conn.MessageData: 129 | t.Fatalf("TestCodecSendRecvCodec Send Error: [%v] protocol:%s\n", 130 | vx, p) 131 | default: 132 | // 133 | } 134 | // Subscribe 135 | sbh := wh.Add(HK_ID, v.sid) 136 | //log.Printf("Subscribe Headers: %v\n", sbh) 137 | sc, e = conn.Subscribe(sbh) 138 | if e != nil { 139 | t.Fatalf("TestCodecSendRecvCodec Subscribe failed: %v protocol:%s\n", 140 | e, p) 141 | } 142 | if sc == nil { 143 | t.Fatalf("TestCodecSendRecvCodec Subscribe sub chan is nil protocol:%s\n", 144 | p) 145 | } 146 | // 147 | checkReceivedMD(t, conn, sc, "codec_test_"+p) // Receive 148 | // Check body data 149 | b := md.Message.BodyString() 150 | if b != ms { 151 | t.Fatalf("TestCodecSendRecvCodec Receive expected: [%v] got: [%v] protocol:%s\n", 152 | ms, b, p) 153 | } 154 | // Unsubscribe 155 | //log.Printf("Unsubscribe Headers: %v\n", sbh) 156 | e = conn.Unsubscribe(sbh) 157 | if e != nil { 158 | t.Fatalf("TestCodecSendRecvCodec Unsubscribe failed: %v protocol:%s\n", 159 | e, p) 160 | } 161 | // Check headers 162 | log.Printf("Receive Headers: %v\n", md.Message.Headers) 163 | log.Printf("Check map: %v\n", v.rv) 164 | for key, value := range v.rv { 165 | log.Printf("Want Key: [%v] Value: [%v] \n", key, value) 166 | hv, ok = md.Message.Headers.Contains(key) 167 | if !ok { 168 | t.Fatalf("TestCodecSendRecvCodec Header key expected: [%v] got: [%v] protocol:%s\n", 169 | key, hv, p) 170 | } 171 | if value != hv { 172 | t.Fatalf("TestCodecSendRecvCodec Header value expected: [%v] got: [%v] protocol:%s\n", 173 | value, hv, p) 174 | } 175 | } 176 | // 177 | checkReceived(t, conn, false) 178 | e = conn.Disconnect(empty_headers) 179 | checkDisconnectError(t, e) 180 | _ = closeConn(t, n) 181 | } 182 | // 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /connect_helpers.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "bufio" 21 | "bytes" 22 | 23 | // "fmt" 24 | "github.com/gmallard/stompngo/senv" 25 | "strings" 26 | ) 27 | 28 | type CONNERROR struct { 29 | err error 30 | desc string 31 | } 32 | 33 | func (e *CONNERROR) Error() string { 34 | return e.err.Error() + ":" + e.desc 35 | } 36 | 37 | /* 38 | Connection handler, one time use during initial connect. 39 | 40 | Handle broker response, react to version incompatabilities, set up session, 41 | and if necessary initialize heart beats. 42 | */ 43 | func (c *Connection) connectHandler(h Headers) (e error) { 44 | //fmt.Printf("CHDB01\n") 45 | c.rdr = bufio.NewReaderSize(c.netconn, senv.ReadBufsz()) 46 | b, e := c.rdr.ReadBytes(0) 47 | if e != nil { 48 | return e 49 | } 50 | //fmt.Printf("CHDB02\n") 51 | f, e := connectResponse(string(b)) 52 | if e != nil { 53 | return e 54 | } 55 | //fmt.Printf("CHDB03\n") 56 | // 57 | c.ConnectResponse = &Message{f.Command, f.Headers, f.Body} 58 | if c.ConnectResponse.Command == ERROR { 59 | return &CONNERROR{ECONERR, string(f.Body)} 60 | } 61 | //fmt.Printf("CHDB04\n") 62 | // 63 | e = c.setProtocolLevel(h, c.ConnectResponse.Headers) 64 | if e != nil { 65 | return e 66 | } 67 | //fmt.Printf("CHDB05\n") 68 | // 69 | if s, ok := c.ConnectResponse.Headers.Contains(HK_SESSION); ok { 70 | c.sessLock.Lock() 71 | c.session = s 72 | c.sessLock.Unlock() 73 | } 74 | 75 | if c.Protocol() >= SPL_11 { 76 | e = c.initializeHeartBeats(h) 77 | if e != nil { 78 | return e 79 | } 80 | } 81 | //fmt.Printf("CHDB06\n") 82 | 83 | c.setConnected(true) 84 | c.mets.tfr += 1 85 | c.mets.tbr += c.ConnectResponse.Size(false) 86 | return nil 87 | } 88 | 89 | /* 90 | Handle data from the wire after CONNECT is sent. Attempt to create a Frame 91 | from the wire data. 92 | 93 | Called one time per connection at connection start. 94 | */ 95 | func connectResponse(s string) (*Frame, error) { 96 | // 97 | f := new(Frame) 98 | f.Headers = Headers{} 99 | f.Body = make([]uint8, 0) 100 | 101 | // Get f.Command 102 | c := strings.SplitN(s, "\n", 2) 103 | if len(c) < 2 { 104 | if len(c) == 1 { 105 | // fmt.Printf("lenc is: %d, data:%#v\n", len(c), c[0]) 106 | if bytes.Compare(HandShake, []byte(c[0])) == 0 { 107 | return nil, EBADSSLP 108 | } 109 | } 110 | return nil, EBADFRM 111 | } 112 | f.Command = c[0] 113 | if f.Command != CONNECTED && f.Command != ERROR { 114 | return f, EUNKFRM 115 | } 116 | 117 | switch c[1] { 118 | case "\x00", "\n": // No headers, malformed bodies 119 | f.Body = []uint8(c[1]) 120 | return f, EBADFRM 121 | case "\n\x00": // No headers, no body is OK 122 | return f, nil 123 | default: // Otherwise continue 124 | } 125 | 126 | b := strings.SplitN(c[1], "\n\n", 2) 127 | if len(b) == 1 { // No Headers, b[0] == body 128 | w := []uint8(b[0]) 129 | f.Body = w[0 : len(w)-1] 130 | if f.Command == CONNECTED && len(f.Body) > 0 { 131 | return f, EBDYDATA 132 | } 133 | return f, nil 134 | } 135 | 136 | // Here: 137 | // b[0] - the headers 138 | // b[1] - the body 139 | 140 | // Get f.Headers 141 | for _, l := range strings.Split(b[0], "\n") { 142 | p := strings.SplitN(l, ":", 2) 143 | if len(p) < 2 { 144 | f.Body = []uint8(p[0]) // Bad feedback 145 | return f, EUNKHDR 146 | } 147 | f.Headers = append(f.Headers, p[0], p[1]) 148 | } 149 | // get f.Body 150 | w := []uint8(b[1]) 151 | f.Body = w[0 : len(w)-1] 152 | if f.Command == CONNECTED && len(f.Body) > 0 { 153 | return f, EBDYDATA 154 | } 155 | 156 | return f, nil 157 | } 158 | 159 | /* 160 | Check client version, one time use during initial connect. 161 | */ 162 | func (c *Connection) checkClientVersions(h Headers) (e error) { 163 | w := h.Value(HK_ACCEPT_VERSION) 164 | if w == "" { // Not present, client wants 1.0 165 | return nil 166 | } 167 | v := strings.SplitN(w, ",", -1) // 168 | ok := false 169 | for _, sv := range v { 170 | if hasValue(supported, sv) { 171 | ok = true // At least we support one the client wants 172 | } 173 | } 174 | if !ok { 175 | return EBADVERCLI 176 | } 177 | if _, ok = h.Contains(HK_HOST); !ok { 178 | return EREQHOST 179 | } 180 | return nil 181 | } 182 | 183 | /* 184 | Set the protocol level for this new connection. 185 | */ 186 | func (c *Connection) setProtocolLevel(ch, sh Headers) (e error) { 187 | chw := ch.Value(HK_ACCEPT_VERSION) 188 | shr := sh.Value(HK_VERSION) 189 | 190 | if chw == shr && Supported(shr) { 191 | c.protocol = shr 192 | return nil 193 | } 194 | if chw == "" && shr == "" { // Straight up 1.0 195 | return nil // protocol level defaults to SPL_10 196 | } 197 | cv := strings.SplitN(chw, ",", -1) // Client requested versions 198 | 199 | if chw != "" && shr != "" { 200 | if hasValue(cv, shr) { 201 | if !Supported(shr) { 202 | return EBADVERSVR // Client and server agree, but we do not support it 203 | } 204 | c.protocol = shr 205 | return nil 206 | } else { 207 | return EBADVERCLI 208 | } 209 | } 210 | if chw != "" && shr == "" { // Client asked for something, server is pure 1.0 211 | if hasValue(cv, SPL_10) { 212 | return nil // protocol level defaults to SPL_10 213 | } 214 | } 215 | 216 | c.protocol = shr // Could be anything we support 217 | return nil 218 | } 219 | 220 | /* 221 | Internal function, used only during CONNECT processing. 222 | */ 223 | func hasValue(a []string, w string) bool { 224 | for _, v := range a { 225 | if v == w { 226 | return true 227 | } 228 | } 229 | return false 230 | } 231 | -------------------------------------------------------------------------------- /heartbeats.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "strconv" 22 | "strings" 23 | "time" 24 | ) 25 | 26 | /* 27 | Initialize heart beats if necessary and possible. 28 | 29 | Return an error, possibly nil, to mainline if initialization can not 30 | complete. Establish heartbeat send and receive goroutines as necessary. 31 | */ 32 | func (c *Connection) initializeHeartBeats(ch Headers) (e error) { 33 | // Client wants Heartbeats ? 34 | vc, ok := ch.Contains(HK_HEART_BEAT) 35 | if !ok || vc == "0,0" { 36 | return nil 37 | } 38 | // Server wants Heartbeats ? 39 | vs, ok := c.ConnectResponse.Headers.Contains(HK_HEART_BEAT) 40 | if !ok || vs == "0,0" { 41 | return nil 42 | } 43 | // Work area, may or may not become connection heartbeat data 44 | w := &heartBeatData{cx: 0, cy: 0, sx: 0, sy: 0, 45 | hbs: true, hbr: true, // possible reset later 46 | sti: 0, rti: 0, 47 | ls: 0, lr: 0} 48 | 49 | // Client specified values 50 | cp := strings.Split(vc, ",") 51 | if len(cp) != 2 { // S/B caught by the server first 52 | return Error("invalid client heart-beat header: " + vc) 53 | } 54 | w.cx, e = strconv.ParseInt(cp[0], 10, 64) 55 | if e != nil { 56 | return Error("non-numeric cx heartbeat value: " + cp[0]) 57 | } 58 | w.cy, e = strconv.ParseInt(cp[1], 10, 64) 59 | if e != nil { 60 | return Error("non-numeric cy heartbeat value: " + cp[1]) 61 | } 62 | 63 | // Server specified values 64 | sp := strings.Split(vs, ",") 65 | if len(sp) != 2 { 66 | return Error("invalid server heart-beat header: " + vs) 67 | } 68 | w.sx, e = strconv.ParseInt(sp[0], 10, 64) 69 | if e != nil { 70 | return Error("non-numeric sx heartbeat value: " + sp[0]) 71 | } 72 | w.sy, e = strconv.ParseInt(sp[1], 10, 64) 73 | if e != nil { 74 | return Error("non-numeric sy heartbeat value: " + sp[1]) 75 | } 76 | 77 | // Check for sending needed 78 | if w.cx == 0 || w.sy == 0 { 79 | w.hbs = false // 80 | } 81 | 82 | // Check for receiving needed 83 | if w.sx == 0 || w.cy == 0 { 84 | w.hbr = false // 85 | } 86 | 87 | // ======================================================================== 88 | 89 | if !w.hbs && !w.hbr { 90 | return nil // none required 91 | } 92 | 93 | // ======================================================================== 94 | 95 | c.hbd = w // OK, we are doing some kind of heartbeating 96 | ct := time.Now().UnixNano() // Prime current time 97 | 98 | if w.hbs { // Finish sender parameters if required 99 | sm := max(w.cx, w.sy) // ticker interval, ms 100 | w.sti = 1000000 * sm // ticker interval, ns 101 | w.ssd = make(chan struct{}) // add shutdown channel 102 | w.ls = ct // Best guess at start 103 | // fmt.Println("start send ticker") 104 | go c.sendTicker() 105 | } 106 | 107 | if w.hbr { // Finish receiver parameters if required 108 | rm := max(w.sx, w.cy) // ticker interval, ms 109 | w.rti = 1000000 * rm // ticker interval, ns 110 | w.rsd = make(chan struct{}) // add shutdown channel 111 | w.lr = ct // Best guess at start 112 | // fmt.Println("start receive ticker") 113 | go c.receiveTicker() 114 | } 115 | return nil 116 | } 117 | 118 | /* 119 | The heart beat send ticker. 120 | */ 121 | func (c *Connection) sendTicker() { 122 | c.hbd.sc = 0 123 | ticker := time.NewTicker(time.Duration(c.hbd.sti)) 124 | defer ticker.Stop() 125 | hbSend: 126 | for { 127 | select { 128 | case <-ticker.C: 129 | c.log("HeartBeat Send data") 130 | // Send a heartbeat 131 | f := Frame{"\n", Headers{}, NULLBUFF} // Heartbeat frame 132 | r := make(chan error) 133 | if e := c.writeWireData(wiredata{f, r}); e != nil { 134 | c.Hbsf = true 135 | break hbSend 136 | } 137 | e := <-r 138 | // 139 | c.hbd.sdl.Lock() 140 | if e != nil { 141 | fmt.Printf("Heartbeat Send Failure: %v\n", e) 142 | c.Hbsf = true 143 | } else { 144 | c.Hbsf = false 145 | c.hbd.sc++ 146 | } 147 | c.hbd.sdl.Unlock() 148 | // 149 | case _ = <-c.hbd.ssd: 150 | break hbSend 151 | case _ = <-c.ssdc: 152 | break hbSend 153 | } // End of select 154 | } // End of for 155 | c.log("Heartbeat Send Ends", time.Now()) 156 | return 157 | } 158 | 159 | /* 160 | The heart beat receive ticker. 161 | */ 162 | func (c *Connection) receiveTicker() { 163 | c.hbd.rc = 0 164 | var first, last, nd int64 165 | hbGet: 166 | for { 167 | nd = c.hbd.rti - (last - first) 168 | // Check if receives are supposed to be "fast" *and* we spent a 169 | // lot of time in the previous loop. 170 | if nd <= 0 { 171 | nd = c.hbd.rti 172 | } 173 | ticker := time.NewTicker(time.Duration(nd)) 174 | select { 175 | case ct := <-ticker.C: 176 | first = time.Now().UnixNano() 177 | ticker.Stop() 178 | c.hbd.rdl.Lock() 179 | flr := c.hbd.lr 180 | ld := ct.UnixNano() - flr 181 | c.log("HeartBeat Receive TIC", "TickerVal", ct.UnixNano(), 182 | "LastReceive", flr, "Diff", ld) 183 | if ld > (c.hbd.rti + (c.hbd.rti / 5)) { // swag plus to be tolerant 184 | c.log("HeartBeat Receive Read is dirty") 185 | c.Hbrf = true // Flag possible dirty connection 186 | } else { 187 | c.Hbrf = false // Reset 188 | c.hbd.rc++ 189 | } 190 | c.hbd.rdl.Unlock() 191 | last = time.Now().UnixNano() 192 | case _ = <-c.hbd.rsd: 193 | ticker.Stop() 194 | break hbGet 195 | case _ = <-c.ssdc: 196 | ticker.Stop() 197 | break hbGet 198 | } // End of select 199 | } // End of for 200 | c.log("Heartbeat Receive Ends", time.Now()) 201 | return 202 | } 203 | -------------------------------------------------------------------------------- /senv/senv.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2014-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | /* 18 | Helper package for stompngo users. 19 | 20 | Extract commonly used data elements from the environment, and expose 21 | this data to users. 22 | 23 | */ 24 | package senv 25 | 26 | import ( 27 | "log" 28 | "os" 29 | "strconv" 30 | "sync" 31 | ) 32 | 33 | var ( 34 | host = "localhost" // default host 35 | port = "61613" // default port 36 | protocol = "1.2" // Default protocol level 37 | login = "guest" // default login 38 | passcode = "guest" // default passcode 39 | vhost = "localhost" // default vhost 40 | heartbeats = "0,0" // default (no) heartbeats 41 | dest = "/queue/sng.sample.stomp.destination" // default destination 42 | scc = 1 // Subchannel capacity 43 | nmsgs = 1 // default number of messages (useful at times) 44 | maxbl = -1 // Max body length to dump (-1 => no limit) 45 | vhLock sync.Mutex // vhsost set lock 46 | nmLock sync.Mutex // nmsgs set lock 47 | useStomp = false // use STOMP, not CONNECT 48 | wbs = 64 * 1024 // TCP Write buffer size 49 | rbs = 64 * 1024 // TCP Read buffer size 50 | ) 51 | 52 | // Destination 53 | func Dest() string { 54 | // Destination 55 | de := os.Getenv("STOMP_DEST") 56 | if de != "" { 57 | dest = de 58 | } 59 | return dest 60 | } 61 | 62 | // Heartbeats returns client requested heart beat values. 63 | func Heartbeats() string { 64 | // Heartbeats 65 | hb := os.Getenv("STOMP_HEARTBEATS") 66 | if hb != "" { 67 | heartbeats = hb 68 | } 69 | return heartbeats 70 | } 71 | 72 | // Host returns a default connection hostname. 73 | func Host() string { 74 | // Host 75 | he := os.Getenv("STOMP_HOST") 76 | if he != "" { 77 | host = he 78 | } 79 | return host 80 | } 81 | 82 | // HostAndPort returns a default host and port (useful for Dial). 83 | func HostAndPort() (string, string) { 84 | return Host(), Port() 85 | } 86 | 87 | // Login returns a default login ID. 88 | func Login() string { 89 | // Login 90 | l := os.Getenv("STOMP_LOGIN") 91 | if l != "" { 92 | login = l 93 | } 94 | if l == "NONE" { 95 | login = "" 96 | } 97 | return login 98 | } 99 | 100 | // Number of messages 101 | func Nmsgs() int { 102 | // Number of messages 103 | nmLock.Lock() 104 | defer nmLock.Unlock() 105 | ns := os.Getenv("STOMP_NMSGS") 106 | if ns == "" { 107 | return nmsgs 108 | } 109 | n, e := strconv.ParseInt(ns, 10, 0) 110 | if e != nil { 111 | log.Printf("NMSGS Conversion error: %v\n", e) 112 | return nmsgs 113 | } 114 | nmsgs = int(n) 115 | return nmsgs 116 | } 117 | 118 | // Passcode returns a default passcode. 119 | func Passcode() string { 120 | // Passcode 121 | pc := os.Getenv("STOMP_PASSCODE") 122 | if pc != "" { 123 | passcode = pc 124 | } 125 | if pc == "NONE" { 126 | passcode = "" 127 | } 128 | return passcode 129 | } 130 | 131 | // True if persistent messages are desired. 132 | func Persistent() bool { 133 | f := os.Getenv("STOMP_PERSISTENT") 134 | if f == "" { 135 | return false 136 | } 137 | return true 138 | } 139 | 140 | // Port returns a default connection port. 141 | func Port() string { 142 | // Port 143 | pt := os.Getenv("STOMP_PORT") 144 | if pt != "" { 145 | port = pt 146 | } 147 | return port 148 | } 149 | 150 | // Protocol returns a default level. 151 | func Protocol() string { 152 | // Protocol 153 | pr := os.Getenv("STOMP_PROTOCOL") 154 | if pr != "" { 155 | protocol = pr 156 | } 157 | return protocol 158 | } 159 | 160 | func SubChanCap() int { 161 | if s := os.Getenv("STOMP_SUBCHANCAP"); s != "" { 162 | i, e := strconv.ParseInt(s, 10, 32) 163 | if nil != e { 164 | log.Println("SUBCHANCAP conversion error", e) 165 | } else { 166 | scc = int(i) 167 | } 168 | } 169 | return scc 170 | } 171 | 172 | func WriteBufsz() int { 173 | if s := os.Getenv("STOMP_WRITEBUFSZ"); s != "" { 174 | i, e := strconv.ParseInt(s, 10, 32) 175 | if nil != e { 176 | log.Println("WRITEBUFSZ conversion error", e) 177 | } else { 178 | wbs = int(i) 179 | } 180 | } 181 | return wbs 182 | } 183 | 184 | func ReadBufsz() int { 185 | if s := os.Getenv("STOMP_READBUFSZ"); s != "" { 186 | i, e := strconv.ParseInt(s, 10, 32) 187 | if nil != e { 188 | log.Println("READBUFSZ conversion error", e) 189 | } else { 190 | rbs = int(i) 191 | } 192 | } 193 | return rbs 194 | } 195 | 196 | // Vhost returns a default vhost name. 197 | func Vhost() string { 198 | // Vhost 199 | vhLock.Lock() 200 | defer vhLock.Unlock() 201 | vh := os.Getenv("STOMP_VHOST") 202 | if vh != "" { 203 | vhost = vh 204 | } else { 205 | vhost = Host() 206 | } 207 | return vhost 208 | } 209 | 210 | func MaxBodyLength() int { 211 | if s := os.Getenv("STOMP_MAXBODYLENGTH"); s != "" { 212 | i, e := strconv.ParseInt(s, 10, 32) 213 | if nil != e { 214 | log.Println("MAXBODYLENGTH conversion error", e) 215 | } else { 216 | maxbl = int(i) 217 | } 218 | } 219 | return maxbl 220 | } 221 | 222 | // Optional set logger during connection start 223 | func WantLogger() string { 224 | return os.Getenv("STOMP_LOGGER") 225 | } 226 | 227 | // UseStomp returns true is user wants STOMP frames instead of CONNECT 228 | func UseStomp() bool { 229 | // Protocol 230 | t := os.Getenv("STOMP_USESTOMP") 231 | if t != "" { 232 | useStomp = true 233 | } 234 | return useStomp 235 | } 236 | -------------------------------------------------------------------------------- /subscribe.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "log" 22 | "strconv" 23 | ) 24 | 25 | var _ = fmt.Println 26 | 27 | /* 28 | Subscribe to a STOMP subscription. 29 | 30 | Headers MUST contain a "destination" header key. 31 | 32 | All clients are recommended to supply a unique HK_ID header on Subscribe. 33 | 34 | For STOMP 1.0 clients: if an "id" header is supplied, attempt to use it. 35 | If the "id" header is not unique in the session, return an error. If no 36 | "id" header is supplied, attempt to generate a unique subscription id based 37 | on the destination name. If a unique subscription id cannot be generated, 38 | return an error. 39 | 40 | For STOMP 1.1+ clients: If any client does not supply an HK_ID header, 41 | attempt to generate a unique "id". In all cases, do not allow duplicate 42 | subscription "id"s in this session. 43 | 44 | In summary, multiple subscriptions to the same destination are not allowed 45 | unless a unique "id" is supplied. 46 | 47 | For details about the returned MessageData channel, see: https://github.com/gmallard/stompngo/wiki/subscribe-and-messagedata 48 | 49 | Example: 50 | // Possible additional Header keys: "ack", "id". 51 | h := stompngo.Headers{stompngo.HK_DESTINATION, "/queue/myqueue"} 52 | s, e := c.Subscribe(h) 53 | if e != nil { 54 | // Do something sane ... 55 | } 56 | 57 | */ 58 | func (c *Connection) Subscribe(h Headers) (<-chan MessageData, error) { 59 | c.log(SUBSCRIBE, "start", h, c.Protocol()) 60 | if !c.isConnected() { 61 | return nil, ECONBAD 62 | } 63 | e := checkHeaders(h, c.Protocol()) 64 | if e != nil { 65 | return nil, e 66 | } 67 | e = c.checkSubscribeHeaders(h) 68 | if e != nil { 69 | return nil, e 70 | } 71 | ch := h.Clone() 72 | if _, ok := ch.Contains(HK_ACK); !ok { 73 | ch = append(ch, HK_ACK, AckModeAuto) 74 | } 75 | sub, e, ch := c.establishSubscription(ch) 76 | if e != nil { 77 | return nil, e 78 | } 79 | // 80 | f := Frame{SUBSCRIBE, ch, NULLBUFF} 81 | // 82 | r := make(chan error) 83 | if e = c.writeWireData(wiredata{f, r}); e != nil { 84 | return nil, e 85 | } 86 | e = <-r 87 | c.log(SUBSCRIBE, "end", ch, c.Protocol()) 88 | return sub.md, e 89 | } 90 | 91 | /* 92 | Check SUBSCRIBE specific requirements. 93 | */ 94 | func (c *Connection) checkSubscribeHeaders(h Headers) error { 95 | if _, ok := h.Contains(HK_DESTINATION); !ok { 96 | return EREQDSTSUB 97 | } 98 | // 99 | am, ok := h.Contains(HK_ACK) 100 | // 101 | switch c.Protocol() { 102 | case SPL_10: 103 | if ok { // Client supplied ack header 104 | if !validAckModes10[am] { 105 | return ESBADAM 106 | } 107 | } 108 | case SPL_11: 109 | fallthrough 110 | case SPL_12: 111 | if ok { // Client supplied ack header 112 | if !(validAckModes10[am] || validAckModes1x[am]) { 113 | return ESBADAM 114 | } 115 | } 116 | default: 117 | log.Fatalf("Internal protocol level error:<%s>\n", c.Protocol()) 118 | } 119 | return nil 120 | } 121 | 122 | /* 123 | Handle subscribe id. 124 | */ 125 | func (c *Connection) establishSubscription(h Headers) (*subscription, error, Headers) { 126 | c.log(SUBSCRIBE, "start establishSubscription") 127 | defer c.log(SUBSCRIBE, "end establishSubscription") 128 | // 129 | id, hid := h.Contains(HK_ID) 130 | uuid1 := Uuid() 131 | sha11 := Sha1(h.Value(HK_DESTINATION)) 132 | // 133 | c.subsLock.RLock() // Acquire Read lock 134 | // No duplicates 135 | if hid { 136 | if _, q := c.subs[id]; q { 137 | c.subsLock.RUnlock() // Release Read lock 138 | return nil, EDUPSID, h // Duplicate subscriptions not allowed 139 | } 140 | if _, q := c.subs[sha11]; q { 141 | c.subsLock.RUnlock() // Release Read lock 142 | return nil, EDUPSID, h // Duplicate subscriptions not allowed 143 | } 144 | } else { 145 | if _, q := c.subs[uuid1]; q { 146 | c.subsLock.RUnlock() // Release Read lock 147 | return nil, EDUPSID, h // Duplicate subscriptions not allowed 148 | } 149 | } 150 | c.subsLock.RUnlock() // Release Read lock 151 | // 152 | sd := new(subscription) // New subscription data 153 | if hid { 154 | sd.id = id // Note user supplied id 155 | } 156 | sd.cs = false // No shutdown yet 157 | sd.drav = false // Drain after value validity 158 | sd.dra = 0 // Never drain MESSAGE frames 159 | sd.drmc = 0 // Current drain count 160 | sd.md = make(chan MessageData, c.scc) // Make subscription MD channel 161 | sd.am = h.Value(HK_ACK) // Set subscription ack mode 162 | // 163 | if !hid { 164 | // No caller supplied ID. This STOMP client package supplies one. It is the 165 | // caller's responsibility for discover the value from subsequent message 166 | // traffic. 167 | switch c.Protocol() { 168 | case SPL_10: 169 | nsid := sha11 // This will be unique for a given destination 170 | sd.id = nsid 171 | h = h.Add(HK_ID, nsid) 172 | case SPL_11: 173 | fallthrough 174 | case SPL_12: 175 | sd.id = uuid1 176 | h = h.Add(HK_ID, uuid1) 177 | default: 178 | log.Fatalf("Internal protocol level error:<%s>\n", c.Protocol()) 179 | } 180 | } 181 | 182 | // STOMP Protocol Enhancement 183 | if dc, okda := h.Contains(StompPlusDrainAfter); okda { 184 | n, e := strconv.ParseInt(dc, 10, 0) 185 | if e != nil { 186 | log.Printf("sng_drafter conversion error: %v\n", e) 187 | } else { 188 | sd.drav = true // Drain after value is OK 189 | sd.dra = uint(n) // Drain after count 190 | } 191 | } 192 | 193 | // This is a write lock 194 | c.subsLock.Lock() 195 | c.subs[sd.id] = sd // Add subscription to the connection subscription map 196 | c.subsLock.Unlock() 197 | // 198 | return sd, nil, h // Return the subscription pointer 199 | } 200 | -------------------------------------------------------------------------------- /connection.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "log" 21 | "runtime" 22 | "time" 23 | ) 24 | 25 | // Exported Connection methods 26 | 27 | /* 28 | Connected returns the current connection status. 29 | */ 30 | func (c *Connection) Connected() bool { 31 | return c.isConnected() 32 | } 33 | 34 | /* 35 | Session returns the broker assigned session id. 36 | */ 37 | func (c *Connection) Session() string { 38 | return c.session 39 | } 40 | 41 | /* 42 | Protocol returns the current connection protocol level. 43 | */ 44 | func (c *Connection) Protocol() string { 45 | c.protoLock.Lock() 46 | defer c.protoLock.Unlock() 47 | return c.protocol 48 | } 49 | 50 | /* 51 | SetLogger enables a client defined logger for this connection. 52 | 53 | Set to "nil" to disable logging. 54 | 55 | Example: 56 | // Start logging 57 | l := log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds) 58 | c.SetLogger(l) 59 | */ 60 | func (c *Connection) SetLogger(l *log.Logger) { 61 | logLock.Lock() 62 | c.logger = l 63 | logLock.Unlock() 64 | } 65 | 66 | /* 67 | GetLogger - returns the current connection logger. 68 | */ 69 | func (c *Connection) GetLogger() *log.Logger { 70 | return c.logger 71 | } 72 | 73 | /* 74 | SendTickerInterval returns any heartbeat send ticker interval in ms. A return 75 | value of zero means no heartbeats are being sent. 76 | */ 77 | func (c *Connection) SendTickerInterval() int64 { 78 | if c.hbd == nil { 79 | return 0 80 | } 81 | return c.hbd.sti / 1000000 82 | } 83 | 84 | /* 85 | ReceiveTickerInterval returns any heartbeat receive ticker interval in ms. 86 | A return value of zero means no heartbeats are being received. 87 | */ 88 | func (c *Connection) ReceiveTickerInterval() int64 { 89 | if c.hbd == nil { 90 | return 0 91 | } 92 | return c.hbd.rti / 1000000 93 | } 94 | 95 | /* 96 | SendTickerCount returns any heartbeat send ticker count. A return value of 97 | zero usually indicates no send heartbeats are enabled. 98 | */ 99 | func (c *Connection) SendTickerCount() int64 { 100 | if c.hbd == nil { 101 | return 0 102 | } 103 | return c.hbd.sc 104 | } 105 | 106 | /* 107 | ReceiveTickerCount returns any heartbeat receive ticker count. A return 108 | value of zero usually indicates no read heartbeats are enabled. 109 | */ 110 | func (c *Connection) ReceiveTickerCount() int64 { 111 | if c.hbd == nil { 112 | return 0 113 | } 114 | return c.hbd.rc 115 | } 116 | 117 | /* 118 | FramesRead returns a count of the number of frames read on the connection. 119 | */ 120 | func (c *Connection) FramesRead() int64 { 121 | return c.mets.tfr 122 | } 123 | 124 | /* 125 | BytesRead returns a count of the number of bytes read on the connection. 126 | */ 127 | func (c *Connection) BytesRead() int64 { 128 | return c.mets.tbr 129 | } 130 | 131 | /* 132 | FramesWritten returns a count of the number of frames written on the connection. 133 | */ 134 | func (c *Connection) FramesWritten() int64 { 135 | return c.mets.tfw 136 | } 137 | 138 | /* 139 | BytesWritten returns a count of the number of bytes written on the connection. 140 | */ 141 | func (c *Connection) BytesWritten() int64 { 142 | return c.mets.tbw 143 | } 144 | 145 | /* 146 | Running returns a time duration since connection start. 147 | */ 148 | func (c *Connection) Running() time.Duration { 149 | return time.Since(c.mets.st) 150 | } 151 | 152 | /* 153 | SubChanCap returns the current subscribe channel capacity. 154 | */ 155 | func (c *Connection) SubChanCap() int { 156 | return c.scc 157 | } 158 | 159 | /* 160 | SetSubChanCap sets a new subscribe channel capacity, to be used during future 161 | SUBSCRIBE operations. 162 | */ 163 | func (c *Connection) SetSubChanCap(nc int) { 164 | c.scc = nc 165 | return 166 | } 167 | 168 | // Unexported Connection methods 169 | 170 | /* 171 | Log data if possible. 172 | */ 173 | func (c *Connection) log(v ...interface{}) { 174 | logLock.Lock() 175 | defer logLock.Unlock() 176 | if c.logger == nil { 177 | return 178 | } 179 | _, fn, ld, ok := runtime.Caller(1) 180 | 181 | if ok { 182 | c.logger.Printf("%s %s %d %v\n", c.session, fn, ld, v) 183 | } else { 184 | c.logger.Printf("%s %v\n", c.session, v) 185 | } 186 | return 187 | } 188 | 189 | /* 190 | Log data if possible (extended / abbreviated) logic). 191 | */ 192 | func (c *Connection) logx(v ...interface{}) { 193 | _, fn, ld, ok := runtime.Caller(1) 194 | 195 | c.sessLock.Lock() 196 | if ok { 197 | c.logger.Printf("%s %s %d %v\n", c.session, fn, ld, v) 198 | } else { 199 | c.logger.Printf("%s %v\n", c.session, v) 200 | } 201 | c.sessLock.Unlock() 202 | return 203 | } 204 | 205 | /* 206 | Shutdown heartbeats 207 | */ 208 | func (c *Connection) shutdownHeartBeats() { 209 | // Shutdown heartbeats if necessary 210 | if c.hbd != nil { 211 | c.hbd.clk.Lock() 212 | if !c.hbd.ssdn { 213 | if c.hbd.hbs { 214 | close(c.hbd.ssd) 215 | } 216 | if c.hbd.hbr { 217 | close(c.hbd.rsd) 218 | } 219 | c.hbd.ssdn = true 220 | } 221 | c.hbd.clk.Unlock() 222 | } 223 | } 224 | 225 | /* 226 | Shutdown logic. 227 | */ 228 | func (c *Connection) shutdown() { 229 | c.log("SHUTDOWN", "starts") 230 | c.shutdownHeartBeats() 231 | // Close all individual subscribe channels 232 | // This is a write lock 233 | c.subsLock.Lock() 234 | for key := range c.subs { 235 | close(c.subs[key].md) 236 | c.subs[key].cs = true 237 | } 238 | c.setConnected(false) 239 | c.subsLock.Unlock() 240 | c.log("SHUTDOWN", "ends") 241 | return 242 | } 243 | 244 | /* 245 | Connection Abort logic. Shutdown connection system on problem happens 246 | */ 247 | func (c *Connection) sysAbort() { 248 | c.abortOnce.Do(func() { close(c.ssdc) }) 249 | return 250 | } 251 | 252 | /* 253 | Read error handler. 254 | */ 255 | func (c *Connection) handleReadError(md MessageData) { 256 | c.log("HDRERR", "starts", md) 257 | c.shutdownHeartBeats() // We are done here 258 | // Notify any general subscriber of error 259 | select { 260 | case c.input <- md: 261 | default: 262 | } 263 | // Notify all individual subscribers of error 264 | // This is a read lock 265 | c.subsLock.RLock() 266 | if c.isConnected() { 267 | for key := range c.subs { 268 | c.subs[key].md <- md 269 | } 270 | } 271 | c.subsLock.RUnlock() 272 | // Try to catch the writer 273 | close(c.wtrsdc) 274 | c.log("HDRERR", "ends") 275 | // Let further shutdown logic proceed normally. 276 | return 277 | } 278 | 279 | /* 280 | Connected check 281 | */ 282 | func (c *Connection) isConnected() bool { 283 | c.connLock.Lock() 284 | defer c.connLock.Unlock() 285 | return c.connected 286 | } 287 | 288 | /* 289 | Connected set 290 | */ 291 | func (c *Connection) setConnected(to bool) { 292 | c.connLock.Lock() 293 | defer c.connLock.Unlock() 294 | c.connected = to 295 | } 296 | -------------------------------------------------------------------------------- /headers_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2012-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | /* 24 | Data Test: Headers Basic. 25 | */ 26 | func TestHeadersBasic(t *testing.T) { 27 | for _, _ = range Protocols() { 28 | k := "keya" 29 | v := "valuea" 30 | h := Headers{k, v} 31 | if nil != h.Validate() { 32 | t.Fatalf("TestHeadersBasic Header validate error: [%v]\n", h.Validate()) 33 | } 34 | if len(h) != 2 { 35 | t.Fatalf("TestHeadersBasic Header Unexpected length error 1, length: [%v]\n", 36 | len(h)) 37 | } 38 | h = h.Add("keyb", "valueb").Add("keya", "valuea2") 39 | if len(h) != 6 { 40 | t.Fatalf("TestHeadersBasic Header Unexpected length error 2, length after add: [%v]\n", 41 | len(h)) 42 | } 43 | if _, ok := h.Contains(k); !ok { 44 | t.Fatalf("TestHeadersBasic Header Unexpected false for key: [%v]\n", k) 45 | } 46 | k = "xyz" 47 | if _, ok := h.Contains(k); ok { 48 | t.Fatalf("TestHeadersBasic Header Unexpected true for key: [%v]\n", k) 49 | } 50 | // 51 | h = Headers{k} 52 | if e = h.Validate(); e != EHDRLEN { 53 | t.Fatalf("TestHeadersBasic Header Validate, got [%v], expected [%v]\n", 54 | e, EHDRLEN) 55 | } 56 | } 57 | } 58 | 59 | /* 60 | Data Test: Headers UTF8. 61 | */ 62 | func TestHeadersUTF8(t *testing.T) { 63 | for _, _ = range Protocols() { 64 | k := "keya" 65 | v := "valuea" 66 | wh := Headers{k, v} 67 | var e error // An error 68 | var rs string // Result string 69 | if rs, e = wh.ValidateUTF8(); e != nil { 70 | t.Fatalf("TestHeadersUTF8 Unexpected UTF8 error 1: [%v]\n", e) 71 | } 72 | if rs != "" { 73 | t.Fatalf("TestHeadersUTF8 Unexpected UTF8 error 1B, got [%v], expected [%v]\n", rs, "") 74 | } 75 | // 76 | wh = Headers{k, v, `“Iñtërnâtiônàlizætiøn”`, "valueb", "keyc", `“Iñtërnâtiônàlizætiøn”`} 77 | if _, e = wh.ValidateUTF8(); e != nil { 78 | t.Fatalf("TestHeadersUTF8 Unexpected UTF8 error 2: [%v]\n", e) 79 | } 80 | // 81 | wh = Headers{k, v, `“Iñtërnâtiônàlizætiøn”`, "\x80", "keyc", `“Iñtërnâtiônàlizætiøn”`} 82 | if rs, e = wh.ValidateUTF8(); e == nil { 83 | t.Fatalf("TestHeadersUTF8 Unexpected UTF8 error 3, got nil, expected an error") 84 | } 85 | if e != EHDRUTF8 { 86 | t.Fatalf("TestHeadersUTF8 Unexpected UTF8 error 4, got [%v], expected [%v]\n", 87 | e, EHDRUTF8) 88 | } 89 | if rs != "\x80" { 90 | t.Fatalf("TestHeadersUTF8 Unexpected UTF8 error 5, got [%v], expected [%v]\n", 91 | rs, "\x80") 92 | } 93 | } 94 | } 95 | 96 | /*. 97 | Data Test: Headers Clone 98 | */ 99 | func TestHeadersClone(t *testing.T) { 100 | for _, _ = range Protocols() { 101 | wh := Headers{"ka", "va"}.Add("kb", "vb").Add("kc", "vc") 102 | hc := wh.Clone() 103 | if !wh.Compare(hc) { 104 | t.Fatalf("TestHeadersClone Unexpected false for clone: [%v], [%v]\n", 105 | wh, hc) 106 | } 107 | } 108 | } 109 | 110 | /* 111 | Data Test: Headers Add / Delete. 112 | */ 113 | func TestHeadersAddDelete(t *testing.T) { 114 | for _, _ = range Protocols() { 115 | ha := Headers{"ka", "va", "kb", "vb", "kc", "vc"} 116 | hb := Headers{"kaa", "va", "kbb", "vb", "kcc", "vc"} 117 | hn := ha.AddHeaders(hb) 118 | if len(ha)+len(hb) != len(hn) { 119 | t.Fatalf("TestHeadersAddDelete Unexpected length AddHeaders, got: [%v], expected: [%v]\n", 120 | len(hn), len(ha)+len(hb)) 121 | } 122 | ol := len(hn) 123 | hn = hn.Delete("ka") 124 | if len(hn) != ol-2 { 125 | t.Fatalf("TestHeadersAddDelete Unexpected length Delete 1, got: [%v], expected: [%v]\n", 126 | len(hn), ol-2) 127 | } 128 | hn = hn.Delete("kcc") 129 | if len(hn) != ol-4 { 130 | t.Fatalf("TestHeadersAddDelete Unexpected length Delete 2, got: [%v], expected: [%v]\n", 131 | len(hn), ol-4) 132 | } 133 | } 134 | } 135 | 136 | /* 137 | Data Test: Headers ContainsKV 138 | */ 139 | func TestHeadersContainsKV(t *testing.T) { 140 | for _, _ = range Protocols() { 141 | ha := Headers{"ka", "va", "kb", "vb", "kc", "vc"} 142 | b := ha.ContainsKV("kb", "vb") 143 | if !b { 144 | t.Fatalf("TestHeadersContainsKV KV01 got false, expected true") 145 | } 146 | b = ha.ContainsKV("kb", "zz") 147 | if b { 148 | t.Fatalf("TestHeadersContainsKV KV02 got true, expected false") 149 | } 150 | } 151 | } 152 | 153 | /* 154 | Data Test: Headers Compare 155 | */ 156 | func TestHeadersCompare(t *testing.T) { 157 | for _, _ = range Protocols() { 158 | ha := Headers{"ka", "va", "kb", "vb", "kc", "vc"} 159 | hb := Headers{"ka", "va", "kb", "vb", "kc", "vc"} 160 | hc := Headers{"ka", "va"} 161 | hd := Headers{"k1", "v1", "k2", "v2", "k3", "v3"} 162 | b := ha.Compare(hb) 163 | if !b { 164 | t.Fatalf("TestHeadersCompare CMP01 Expected true, got false") 165 | } 166 | b = ha.Compare(hc) 167 | if b { 168 | t.Fatalf("TestHeadersCompare CMP02 Expected false, got true") 169 | } 170 | b = ha.Compare(hd) 171 | if b { 172 | t.Fatalf("TestHeadersCompare CMP03 Expected false, got true") 173 | } 174 | b = hd.Compare(ha) 175 | if b { 176 | t.Fatalf("TestHeadersCompare CMP04 Expected false, got true") 177 | } 178 | } 179 | } 180 | 181 | /* 182 | Data Test: Headers Size 183 | */ 184 | func TestHeadersSize(t *testing.T) { 185 | for _, _ = range Protocols() { 186 | ha := Headers{"k", "v"} 187 | s := ha.Size(false) 188 | var w int64 = 4 189 | if s != w { 190 | t.Fatalf("TestHeadersSize SIZ01 size, got [%d], expected [%v]\n", 191 | s, w) 192 | } 193 | // 194 | ha = Headers{"kaa", "vaa2", "kba", "vba2", "kca", "vca2"} 195 | s = ha.Size(true) 196 | w = 3 + 1 + 4 + 1 + 3 + 1 + 4 + 1 + 3 + 1 + 4 + 1 197 | if s != w { 198 | t.Fatalf("TestHeadersSize SIZ02 size, got [%d] expected [%v]\n", 199 | s, w) 200 | } 201 | } 202 | } 203 | 204 | /* 205 | Data Test: Empty Header Key / Value 206 | */ 207 | func TestHeadersEmtKV(t *testing.T) { 208 | for _, _ = range Protocols() { 209 | wh := Headers{"a", "b", "c", "d"} // work headers 210 | ek := Headers{"a", "b", "", "d"} // empty key 211 | ev := Headers{"a", "", "c", "d"} // empty value 212 | // 213 | e = checkHeaders(wh, SPL_10) 214 | if e != nil { 215 | t.Fatalf("TestHeadersEmtKV CHD01 Expected [nil], got [%v]\n", e) 216 | } 217 | e = checkHeaders(wh, SPL_11) 218 | if e != nil { 219 | t.Fatalf("TestHeadersEmtKV CHD02 Expected [nil], got [%v]\n", e) 220 | } 221 | // 222 | e = checkHeaders(ek, SPL_10) 223 | if e != EHDRMTK { 224 | t.Fatalf("TestHeadersEmtKV CHD03 Expected [%v], got [%v]\n", EHDRMTK, 225 | e) 226 | } 227 | e = checkHeaders(ek, SPL_11) 228 | if e != EHDRMTK { 229 | t.Fatalf("TestHeadersEmtKV CHD04 Expected [%v], got [%v]\n", EHDRMTK, 230 | e) 231 | } 232 | // 233 | e = checkHeaders(ev, SPL_10) 234 | if e != EHDRMTV { 235 | t.Fatalf("TestHeadersEmtKV CHD05 Expected [%v], got [%v]\n", EHDRMTV, 236 | e) 237 | } 238 | e = checkHeaders(ev, SPL_11) 239 | if e != nil { 240 | t.Fatalf("TestHeadersEmtKV CHD06 Expected [nil], got [%v]\n", e) 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "encoding/hex" 21 | "fmt" 22 | "log" 23 | "net" 24 | "os" 25 | "runtime/debug" 26 | "strings" 27 | "testing" 28 | // 29 | "github.com/gmallard/stompngo/senv" 30 | ) 31 | 32 | /* 33 | Host and port for Dial. 34 | */ 35 | func badVerHostAndPort() (string, string) { 36 | h := os.Getenv("STOMP_HOSTBV") // export only if you understand these tests 37 | if h == "" { 38 | h = "localhost" 39 | } 40 | p := os.Getenv("STOMP_PORTBV") // export only if you understand these tests 41 | if p == "" { 42 | p = "61613" 43 | } 44 | return h, p 45 | } 46 | 47 | /* 48 | Check if 1.1+ style Headers are needed, and return appropriate Headers. 49 | */ 50 | func check11(h Headers) Headers { 51 | v := os.Getenv("STOMP_TEST11p") 52 | if v == "" { 53 | return h 54 | } 55 | if !Supported(v) { 56 | v = SPL_11 // Just use 1.1 57 | } 58 | h = h.Add(HK_ACCEPT_VERSION, v) 59 | s := "localhost" // STOMP 1.1 vhost (configure for Apollo) 60 | if os.Getenv("STOMP_RMQ") != "" { // Rabbitmq default vhost 61 | s = "/" 62 | } 63 | h = h.Add(HK_HOST, s) 64 | return h 65 | } 66 | 67 | /* 68 | Return headers appropriate for the protocol level. 69 | */ 70 | func headersProtocol(h Headers, protocol string) Headers { 71 | if protocol == SPL_10 { 72 | return h 73 | } 74 | h = h.Add(HK_ACCEPT_VERSION, protocol) 75 | vh := "localhost" // STOMP 1.{1,2} vhost 76 | if os.Getenv("STOMP_RMQ") != "" { // Rabbitmq default vhost 77 | vh = "/" 78 | } 79 | h = h.Add(HK_HOST, vh).Add(HK_HEART_BEAT, senv.Heartbeats()) 80 | return h 81 | } 82 | 83 | /* 84 | Test helper. 85 | */ 86 | func checkReceived(t *testing.T, conn *Connection, eofok bool) { 87 | var md MessageData 88 | select { 89 | case md = <-conn.MessageData: 90 | log.Printf("md is [%q]\n", md) 91 | if eofok && md.Error == nil { 92 | return 93 | } 94 | if eofok && md.Error.Error() == "EOF" { 95 | return 96 | } 97 | debug.PrintStack() 98 | t.Fatalf("Unexpected frame received, got [%#v]\n", md) 99 | default: 100 | } 101 | } 102 | 103 | /* 104 | Test helper. 105 | */ 106 | func checkReceivedMD(t *testing.T, conn *Connection, 107 | sc <-chan MessageData, id string) { 108 | select { 109 | case md = <-sc: 110 | case md = <-conn.MessageData: 111 | debug.PrintStack() 112 | t.Fatalf("id: read channel error: expected [nil], got: [%#v] [%#v]\n", 113 | id, md.Message.Command) 114 | } 115 | if md.Error != nil { 116 | debug.PrintStack() 117 | t.Fatalf("id: receive error: [%#v] [%#v]\n", 118 | id, md.Error) 119 | } 120 | return 121 | } 122 | 123 | /* 124 | Close a network connection. 125 | */ 126 | func closeConn(t *testing.T, n net.Conn) error { 127 | err := n.Close() 128 | if err != nil { 129 | debug.PrintStack() 130 | t.Fatalf("Unexpected n.Close() error: %#v\n", err) 131 | } 132 | return err 133 | } 134 | 135 | /* 136 | Test helper. 137 | */ 138 | func getMessageData(sc <-chan MessageData, conn *Connection, t *testing.T) (md MessageData) { 139 | // When this is called, there should not be any MessageData instance 140 | // available on the connection level MessageData channel. 141 | select { 142 | case md = <-sc: 143 | case md = <-conn.MessageData: 144 | debug.PrintStack() 145 | t.Fatalf("read channel error: expected [nil], got: [%#v]\n", 146 | md.Message.Command) 147 | } 148 | return md 149 | } 150 | 151 | /* 152 | Open a network connection. 153 | */ 154 | func openConn(t *testing.T) (net.Conn, error) { 155 | h, p := senv.HostAndPort() 156 | hap := net.JoinHostPort(h, p) 157 | n, err := net.Dial(NetProtoTCP, hap) 158 | if err != nil { 159 | debug.PrintStack() 160 | t.Fatalf("Unexpected net.Dial error: %#v\n", err) 161 | } 162 | return n, err 163 | } 164 | 165 | /* 166 | Test helper. Send multiple messages. 167 | */ 168 | func sendMultiple(md multi_send_data) error { 169 | h := Headers{HK_DESTINATION, md.dest} 170 | for i := 0; i < md.count; i++ { 171 | cstr := fmt.Sprintf("%d", i) 172 | mts := md.mpref + cstr 173 | e = md.conn.Send(h, mts) 174 | if e != nil { 175 | return e // now 176 | } 177 | } 178 | return nil 179 | } 180 | 181 | /* 182 | Test helper. Send multiple []byte messages. 183 | */ 184 | func sendMultipleBytes(md multi_send_data) error { 185 | h := Headers{HK_DESTINATION, md.dest} 186 | for i := 0; i < md.count; i++ { 187 | cstr := fmt.Sprintf("%d", i) 188 | mts := md.mpref + cstr 189 | e = md.conn.SendBytes(h, []byte(mts)) 190 | if e != nil { 191 | return e // now 192 | } 193 | } 194 | return nil 195 | } 196 | 197 | /* 198 | Test helper. Get properly formatted destination. 199 | */ 200 | func tdest(d string) string { 201 | if brokerid != TEST_ARTEMIS { 202 | return d 203 | } 204 | pref := "jms.queue" 205 | if strings.Index(d, "topic") >= 0 { 206 | pref = "jms.topic" 207 | } 208 | return pref + strings.Replace(d, "/", ".", -1) 209 | } 210 | 211 | /* 212 | Test debug helper. Get properly formatted destination. 213 | */ 214 | func tdumpmd(md MessageData) { 215 | fmt.Printf("Command: %s\n", md.Message.Command) 216 | fmt.Println("Headers:") 217 | for i := 0; i < len(md.Message.Headers); i += 2 { 218 | fmt.Printf("key:%s\t\tvalue:%s\n", 219 | md.Message.Headers[i], md.Message.Headers[i+1]) 220 | } 221 | hdb := hex.Dump(md.Message.Body) 222 | fmt.Printf("Body: %s\n", hdb) 223 | if md.Error != nil { 224 | fmt.Printf("Error: %s\n", md.Error.Error()) 225 | } else { 226 | fmt.Println("Error: nil") 227 | } 228 | } 229 | 230 | /* 231 | Test helper. Check disconnect error. 232 | */ 233 | func checkDisconnectError(t *testing.T, e error) { 234 | if e == nil { 235 | return 236 | } 237 | debug.PrintStack() 238 | t.Fatalf("DISCONNECT Error: expected nil, got:<%#v>\n", e) 239 | } 240 | 241 | /* 242 | Test helper. Fix up destination 243 | */ 244 | func fixHeaderDest(h Headers) Headers { 245 | r := h.Clone() 246 | for i := 0; i < len(h); i += 2 { 247 | if r[i] == HK_DESTINATION { 248 | r[i+1] = tdest(r[i+1]) 249 | } 250 | } 251 | return r 252 | } 253 | 254 | /* 255 | Test helper. Set which broker is being tested. 256 | */ 257 | func setTestBroker() int { 258 | brokerid = TEST_ANYBROKER 259 | if os.Getenv("STOMP_AMQ") != "" { 260 | brokerid = TEST_AMQ 261 | } else if os.Getenv("STOMP_RMQ") != "" { 262 | brokerid = TEST_RMQ 263 | } else if os.Getenv("STOMP_ARTEMIS") != "" { 264 | brokerid = TEST_ARTEMIS 265 | } else if os.Getenv("STOMP_APOLLO") != "" { 266 | brokerid = TEST_APOLLO 267 | } 268 | return brokerid 269 | } 270 | 271 | /* 272 | Test helper. Set long heartbeat test flag. 273 | */ 274 | func setHeartBeatFlags() { 275 | if os.Getenv("STOMP_HBLONG") == "Y" { // Note: a single value to run long hb tests 276 | testhbrd.testhbl = true 277 | } 278 | if os.Getenv("STOMP_HBVERBOSE") != "" { // Any value will do 279 | testhbrd.testhbvb = true 280 | } 281 | return 282 | } 283 | 284 | /* 285 | Test helper. Check for missing headers 286 | */ 287 | func checkDupeHeaders(ms, wh Headers) error { 288 | for i := 0; i < len(wh); i += 2 { 289 | if !ms.ContainsKV(wh[i], wh[i+1]) { 290 | return Error("missing header values") 291 | } 292 | } 293 | return nil 294 | } 295 | -------------------------------------------------------------------------------- /sub_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "log" 22 | //"os" 23 | "testing" 24 | //"time" 25 | ) 26 | 27 | func TestSubNoHeader(t *testing.T) { 28 | n, _ = openConn(t) 29 | ch := login_headers 30 | ch = headersProtocol(ch, SPL_10) // Start with 1.0 31 | conn, e = Connect(n, ch) 32 | if e != nil { 33 | t.Fatalf("TestSubNoHeader CONNECT Failed: e:<%q> connresponse:<%q>\n", e, 34 | conn.ConnectResponse) 35 | } 36 | // 37 | for ti, tv := range subNoHeaderDataList { 38 | conn.protocol = tv.proto // Cheat, fake all protocols 39 | _, e = conn.Subscribe(empty_headers) 40 | if e == nil { 41 | t.Fatalf("TestSubNoHeader[%d] proto:%s expected:%v got:nil\n", 42 | ti, tv.proto, tv.exe) 43 | } 44 | if e != tv.exe { 45 | t.Fatalf("TestSubNoHeader[%d] proto:%s expected:%v got:%v\n", 46 | ti, tv.proto, tv.exe, e) 47 | } 48 | } 49 | // 50 | e = conn.Disconnect(empty_headers) 51 | checkDisconnectError(t, e) 52 | _ = closeConn(t, n) 53 | log.Printf("TestSubNoHeader %d tests complete.\n", len(subNoHeaderDataList)) 54 | } 55 | 56 | func TestSubNoID(t *testing.T) { 57 | n, _ = openConn(t) 58 | ch := login_headers 59 | ch = headersProtocol(ch, SPL_10) // Start with 1.0 60 | conn, e = Connect(n, ch) 61 | if e != nil { 62 | t.Fatalf("TestSubNoID CONNECT Failed: e:<%q> connresponse:<%q>\n", e, 63 | conn.ConnectResponse) 64 | } 65 | // 66 | for ti, tv := range subNoIDDataList { 67 | conn.protocol = tv.proto // Cheat, fake all protocols 68 | ud := tdest(tv.subh.Value(HK_DESTINATION)) 69 | _, e = conn.Subscribe(Headers{HK_DESTINATION, ud}) 70 | if e != tv.exe { 71 | t.Fatalf("TestSubNoID[%d] proto:%s expected:%v got:%v\n", 72 | ti, tv.proto, tv.exe, e) 73 | } 74 | } 75 | // 76 | e = conn.Disconnect(empty_headers) 77 | checkDisconnectError(t, e) 78 | _ = closeConn(t, n) 79 | log.Printf("TestSubNoID %d tests complete.\n", len(subNoIDDataList)) 80 | } 81 | 82 | func TestSubPlain(t *testing.T) { 83 | for ti, tv := range subPlainDataList { 84 | n, _ = openConn(t) 85 | ch := login_headers 86 | ch = headersProtocol(ch, tv.proto) 87 | conn, e = Connect(n, ch) 88 | if e != nil { 89 | t.Fatalf("TestSubPlain CONNECT Failed: e:<%q> connresponse:<%q>\n", e, 90 | conn.ConnectResponse) 91 | } 92 | 93 | // SUBSCRIBE Phase 94 | sh := fixHeaderDest(tv.subh) // destination fixed if needed 95 | sc, e = conn.Subscribe(sh) 96 | if sc == nil { 97 | t.Fatalf("TestSubPlain[%d] SUBSCRIBE, proto:[%s], channel is nil\n", 98 | ti, tv.proto) 99 | } 100 | if e != tv.exe1 { 101 | t.Fatalf("TestSubPlain[%d] SUBSCRIBE, proto:%s expected:%v got:%v\n", 102 | ti, tv.proto, tv.exe1, e) 103 | } 104 | 105 | // UNSUBSCRIBE Phase 106 | sh = fixHeaderDest(tv.unsubh) // destination fixed if needed 107 | e = conn.Unsubscribe(sh) 108 | if e != tv.exe2 { 109 | t.Fatalf("TestSubPlain[%d] UNSUBSCRIBE, proto:%s expected:%v got:%v\n", 110 | ti, tv.proto, tv.exe2, e) 111 | } 112 | 113 | e = conn.Disconnect(empty_headers) 114 | checkDisconnectError(t, e) 115 | _ = closeConn(t, n) 116 | } 117 | log.Printf("TestSubPlain %d tests complete.\n", len(subPlainDataList)) 118 | } 119 | 120 | func TestSubNoTwice(t *testing.T) { 121 | for ti, tv := range subTwiceDataList { 122 | n, _ = openConn(t) 123 | ch := login_headers 124 | ch = headersProtocol(ch, tv.proto) 125 | conn, e = Connect(n, ch) 126 | if e != nil { 127 | t.Fatalf("TestSubNoTwice CONNECT Failed: e:<%q> connresponse:<%q>\n", 128 | e, 129 | conn.ConnectResponse) 130 | } 131 | 132 | // SUBSCRIBE Phase 1 133 | sh := fixHeaderDest(tv.subh) // destination fixed if needed 134 | sc, e = conn.Subscribe(sh) 135 | if sc == nil { 136 | t.Fatalf("TestSubNoTwice[%d] SUBSCRIBE1, proto:[%s], channel is nil\n", 137 | ti, tv.proto) 138 | } 139 | if e != tv.exe1 { 140 | t.Fatalf("TestSubNoTwice[%d] SUBSCRIBE1, proto:%s expected:%v got:%v\n", 141 | ti, tv.proto, tv.exe1, e) 142 | } 143 | 144 | // SUBSCRIBE Phase 2 145 | sc, e = conn.Subscribe(sh) 146 | if e != tv.exe2 { 147 | t.Fatalf("TestSubNoTwice[%d] SUBSCRIBE2, proto:%s expected:%v got:%v\n", 148 | ti, tv.proto, tv.exe2, e) 149 | } 150 | 151 | e = conn.Disconnect(empty_headers) 152 | checkDisconnectError(t, e) 153 | _ = closeConn(t, n) 154 | } 155 | log.Printf("TestSubNoTwice %d tests complete.\n", len(subTwiceDataList)) 156 | } 157 | 158 | func TestSubRoundTrip(t *testing.T) { 159 | for ti, tv := range subPlainDataList { // *NOTE* Use the PlainData table 160 | n, _ = openConn(t) 161 | ch := login_headers 162 | ch = headersProtocol(ch, tv.proto) 163 | conn, e = Connect(n, ch) 164 | if e != nil { 165 | t.Fatalf("TestSubRoundTrip CONNECT Failed: e:<%q> connresponse:<%q>\n", 166 | e, 167 | conn.ConnectResponse) 168 | } 169 | sh := fixHeaderDest(tv.subh) // destination fixed if needed 170 | 171 | // SEND Phase 172 | msg := "SUBROUNDTRIP: " + tv.proto 173 | nh := Headers{HK_DESTINATION, sh.Value(HK_DESTINATION)} 174 | e = conn.Send(nh, msg) 175 | if e != nil { 176 | t.Fatalf("TestSubRoundTrip[%d] SEND, proto:%s expected:%v got:%v\n", 177 | ti, tv.proto, nil, e) 178 | } 179 | 180 | // SUBSCRIBE Phase 181 | sc, e = conn.Subscribe(sh) 182 | if sc == nil { 183 | t.Fatalf("TestSubRoundTrip[%d] SUBSCRIBE, proto:[%s], channel is nil\n", 184 | ti, tv.proto) 185 | } 186 | if e != tv.exe1 { 187 | t.Fatalf("TestSubRoundTrip[%d] SUBSCRIBE, proto:%s expected:%v got:%v\n", 188 | ti, tv.proto, tv.exe1, e) 189 | } 190 | 191 | // RECEIVE Phase 192 | id := fmt.Sprintf("TestSubRoundTrip[%d] RECEIVE, proto:%s", ti, tv.proto) 193 | checkReceivedMD(t, conn, sc, id) 194 | if msg != md.Message.BodyString() { 195 | t.Fatalf("TestSubRoundTrip[%d] RECEIVE, proto:%s expected:%v got:%v\n", 196 | ti, tv.proto, msg, md.Message.BodyString()) 197 | } 198 | 199 | // UNSUBSCRIBE Phase 200 | e = conn.Unsubscribe(sh) 201 | if e != tv.exe2 { 202 | t.Fatalf("TestSubRoundTrip[%d] UNSUBSCRIBE, proto:%s expected:%v got:%v\n", 203 | ti, tv.proto, tv.exe2, e) 204 | } 205 | 206 | e = conn.Disconnect(empty_headers) 207 | checkDisconnectError(t, e) 208 | _ = closeConn(t, n) 209 | } 210 | log.Printf("TestSubRoundTrip %d tests complete.\n", len(subPlainDataList)) 211 | } 212 | 213 | func TestSubAckModes(t *testing.T) { 214 | for ti, tv := range subAckDataList { 215 | n, _ = openConn(t) 216 | ch := login_headers 217 | ch = headersProtocol(ch, tv.proto) 218 | conn, e = Connect(n, ch) 219 | if e != nil { 220 | t.Fatalf("TestSubAckModes CONNECT Failed: e:<%q> connresponse:<%q>\n", 221 | e, 222 | conn.ConnectResponse) 223 | } 224 | 225 | // SUBSCRIBE Phase 1 226 | sh := fixHeaderDest(tv.subh) // destination fixed if needed 227 | sc, e = conn.Subscribe(sh) 228 | if e == nil { 229 | if sc == nil { 230 | t.Fatalf("TestSubAckModes[%d] SUBSCRIBE, proto:[%s], channel is nil\n", 231 | ti, tv.proto) 232 | } 233 | } 234 | if e != tv.exe { 235 | t.Fatalf("TestSubAckModes[%d] SUBSCRIBE, proto:%s expected:%v got:%v\n", 236 | ti, tv.proto, tv.exe, e) 237 | } 238 | 239 | e = conn.Disconnect(empty_headers) 240 | checkDisconnectError(t, e) 241 | _ = closeConn(t, n) 242 | } 243 | log.Printf("TestSubAckModes %d tests complete.\n", len(subAckDataList)) 244 | } 245 | -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "net" 23 | "strconv" 24 | "strings" 25 | "time" 26 | ) 27 | 28 | /* 29 | Logical network reader. 30 | 31 | Read STOMP frames from the connection, create MessageData 32 | structures from the received data, and push the MessageData to the client. 33 | */ 34 | func (c *Connection) reader() { 35 | var f Frame 36 | var e error 37 | // 38 | readLoop: 39 | for { 40 | if c.eltd != nil { 41 | // fmt.Println("DEROV", c.eltd.rov.ens) 42 | st := time.Now().UnixNano() 43 | f, e = c.readFrame() 44 | c.eltd.rov.ens += time.Now().UnixNano() - st 45 | c.eltd.rov.ec++ 46 | } else { 47 | f, e = c.readFrame() 48 | } 49 | // 50 | logLock.Lock() 51 | if c.logger != nil { 52 | c.logx("RDR_RECEIVE_FRAME", f.Command, f.Headers, HexData(f.Body), 53 | "RDR_RECEIVE_ERR", e) 54 | } 55 | logLock.Unlock() 56 | if e != nil { 57 | //debug.PrintStack() 58 | f.Headers = append(f.Headers, "connection_read_error", e.Error()) 59 | md := MessageData{Message(f), e} 60 | c.handleReadError(md) 61 | if e == io.EOF && !c.isConnected() { 62 | c.log("RDR_SHUTDOWN_EOF", e) 63 | } else { 64 | c.log("RDR_CONN_GENL_ERR", e) 65 | } 66 | break readLoop 67 | } 68 | 69 | if f.Command == "" { 70 | continue readLoop 71 | } 72 | 73 | m := Message(f) 74 | c.mets.tfr += 1 // Total frames read 75 | // Headers already decoded 76 | c.mets.tbr += m.Size(false) // Total bytes read 77 | 78 | //************************************************************************* 79 | // Replacement START 80 | md := MessageData{m, e} 81 | switch f.Command { 82 | // 83 | case MESSAGE: 84 | sid, ok := f.Headers.Contains(HK_SUBSCRIPTION) 85 | if !ok { // This should *NEVER* happen 86 | panic(fmt.Sprintf("stompngo INTERNAL ERROR: command:<%s> headers:<%v>", 87 | f.Command, f.Headers)) 88 | } 89 | c.subsLock.RLock() 90 | ps, sok := c.subs[sid] // This is a map of pointers ..... 91 | // 92 | if !sok { 93 | // The sub can be gone under some timing conditions. In that case 94 | // we log it of possible, and continue (hope for the best). 95 | c.log("RDR_NOSUB", sid, m.Command, m.Headers) 96 | goto csRUnlock 97 | } 98 | if ps.cs { 99 | // The sub can also already be closed under some conditions. 100 | // Again, we log that if possible, and continue 101 | c.log("RDR_CLSUB", sid, m.Command, m.Headers) 102 | goto csRUnlock 103 | } 104 | // Handle subscription draining 105 | switch ps.drav { 106 | case false: 107 | ps.md <- md 108 | default: 109 | ps.drmc++ 110 | if ps.drmc > ps.dra { 111 | logLock.Lock() 112 | if c.logger != nil { 113 | c.logx("RDR_DROPM", ps.drmc, sid, m.Command, 114 | m.Headers, HexData(m.Body)) 115 | } 116 | logLock.Unlock() 117 | } else { 118 | ps.md <- md 119 | } 120 | } 121 | csRUnlock: 122 | c.subsLock.RUnlock() 123 | // 124 | case ERROR: 125 | fallthrough 126 | // 127 | case RECEIPT: 128 | c.input <- md 129 | // 130 | default: 131 | panic(fmt.Sprintf("Broker SEVERE ERROR, not STOMP? command:<%s> headers:<%v>", 132 | f.Command, f.Headers)) 133 | } 134 | // Replacement END 135 | //************************************************************************* 136 | 137 | select { 138 | case _ = <-c.ssdc: 139 | c.log("RDR_SHUTDOWN detected") 140 | break readLoop 141 | default: 142 | } 143 | c.log("RDR_RELOOP") 144 | } 145 | close(c.input) 146 | c.setConnected(false) 147 | c.sysAbort() 148 | c.log("RDR_SHUTDOWN", time.Now()) 149 | } 150 | 151 | /* 152 | Physical frame reader. 153 | 154 | This parses a single STOMP frame from data off of the wire, and 155 | returns a Frame, with a possible error. 156 | 157 | Note: this functionality could hang or exhibit other erroneous behavior 158 | if running against a non-compliant STOMP server. 159 | */ 160 | func (c *Connection) readFrame() (f Frame, e error) { 161 | var s string 162 | var bx []byte 163 | f = Frame{"", Headers{}, NULLBUFF} 164 | 165 | // Read f.Command or line ends (maybe heartbeats) 166 | c.setReadDeadline() 167 | 168 | if c.eltd != nil { 169 | st := time.Now().UnixNano() 170 | // s, e = c.rdr.ReadString('\n') 171 | bx, e = c.rdr.ReadBytes('\n') 172 | s = string(bx) 173 | c.eltd.rcmd.ens += time.Now().UnixNano() - st 174 | c.eltd.rcmd.ec++ 175 | // fmt.Println("DERCMD", s) 176 | } else { 177 | // s, e = c.rdr.ReadString('\n') 178 | bx, e = c.rdr.ReadBytes('\n') 179 | s = string(bx) 180 | } 181 | 182 | if c.checkReadError(e) != nil { 183 | return f, e 184 | } 185 | if s == "" { 186 | return f, e 187 | } 188 | if c.hbd != nil { 189 | c.updateHBReads() 190 | } 191 | f.Command = s[0 : len(s)-1] 192 | if s == "\n" { 193 | return f, e 194 | } 195 | // fmt.Println("DERCMD2", f.Command) 196 | // Validate the command 197 | if _, ok := validCmds[f.Command]; !ok { 198 | ev := fmt.Errorf("%s\n%s", EINVBCMD, HexData([]byte(f.Command))) 199 | return f, ev 200 | } 201 | // Read f.Headers 202 | for { 203 | c.setReadDeadline() 204 | 205 | if c.eltd != nil { 206 | st := time.Now().UnixNano() 207 | s, e = c.rdr.ReadString('\n') 208 | c.eltd.rivh.ens += time.Now().UnixNano() - st 209 | c.eltd.rivh.ec++ 210 | 211 | } else { 212 | s, e = c.rdr.ReadString('\n') 213 | } 214 | 215 | if c.checkReadError(e) != nil { 216 | return f, e 217 | } 218 | if c.hbd != nil { 219 | c.updateHBReads() 220 | } 221 | if s == "\n" { 222 | break 223 | } 224 | s = s[0 : len(s)-1] 225 | p := strings.SplitN(s, ":", 2) 226 | if len(p) != 2 { 227 | return f, EUNKHDR 228 | } 229 | // Always decode regardless of protocol level. See issue #47. 230 | p[0] = decode(p[0]) 231 | p[1] = decode(p[1]) 232 | f.Headers = append(f.Headers, p[0], p[1]) 233 | } 234 | // 235 | e = checkHeaders(f.Headers, c.Protocol()) 236 | if e != nil { 237 | return f, e 238 | } 239 | // Read f.Body 240 | if v, ok := f.Headers.Contains(HK_CONTENT_LENGTH); ok { 241 | l, e := strconv.Atoi(strings.TrimSpace(v)) 242 | if e != nil { 243 | return f, e 244 | } 245 | if l == 0 { 246 | f.Body, e = readUntilNul(c) 247 | } else { 248 | f.Body, e = readBody(c, l) 249 | } 250 | } else { 251 | // content-length not present 252 | f.Body, e = readUntilNul(c) 253 | } 254 | if c.checkReadError(e) != nil { 255 | return f, e 256 | } 257 | if c.hbd != nil { 258 | c.updateHBReads() 259 | } 260 | // End of read loop - set no deadline 261 | if c.dld.rde { 262 | _ = c.netconn.SetReadDeadline(c.dld.t0) 263 | } 264 | return f, e 265 | } 266 | 267 | func (c *Connection) updateHBReads() { 268 | c.hbd.rdl.Lock() 269 | c.hbd.lr = time.Now().UnixNano() // Latest good read 270 | c.hbd.rdl.Unlock() 271 | } 272 | 273 | func (c *Connection) setReadDeadline() { 274 | if c.dld.rde && c.dld.rds { 275 | _ = c.netconn.SetReadDeadline(time.Now().Add(c.dld.rdld)) 276 | } 277 | } 278 | 279 | func (c *Connection) checkReadError(e error) error { 280 | //c.log("checkReadError", e) 281 | if e == nil { 282 | return e 283 | } 284 | ne, ok := e.(net.Error) 285 | if !ok { 286 | return e 287 | } 288 | if ne.Timeout() { 289 | //c.log("is a timeout") 290 | if c.dld.dns { 291 | c.log("invoking read deadline callback") 292 | c.dld.dlnotify(e, false) 293 | } 294 | } 295 | return e 296 | } 297 | -------------------------------------------------------------------------------- /ack_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Veridon 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permisidons and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "fmt" 21 | "log" 22 | "os" 23 | "testing" 24 | "time" 25 | ) 26 | 27 | var _ = fmt.Println 28 | 29 | /* 30 | Test Ack errors. 31 | */ 32 | func TestAckErrors(t *testing.T) { 33 | n, _ = openConn(t) 34 | ch := login_headers 35 | conn, e = Connect(n, ch) 36 | if e != nil { 37 | t.Fatalf("TestAckErrors CONNECT expected nil, got %v\n", e) 38 | } 39 | // 40 | for _, tv := range terrList { 41 | conn.protocol = tv.proto // Fake it 42 | e = conn.Ack(tv.headers) 43 | if e != tv.errval { 44 | t.Fatalf("ACK -%s- expected error [%v], got [%v]\n", 45 | tv.proto, tv.errval, e) 46 | } 47 | } 48 | checkReceived(t, conn, false) 49 | e = conn.Disconnect(empty_headers) 50 | checkDisconnectError(t, e) 51 | _ = closeConn(t, n) 52 | } 53 | 54 | /* 55 | Test Ack Same Connection. 56 | */ 57 | func TestAckSameConn(t *testing.T) { 58 | for _, sp := range Protocols() { 59 | n, _ = openConn(t) 60 | ch := login_headers 61 | ch = headersProtocol(ch, sp) 62 | conn, e = Connect(n, ch) 63 | if e != nil { 64 | t.Fatalf("TestAckSameConn CONNECT expected nil, got %v\n", e) 65 | } 66 | // 67 | // Basic headers 68 | wh := Headers{HK_DESTINATION, 69 | tdest(TEST_TDESTPREF + "acksc1-" + conn.Protocol())} 70 | // Subscribe Headers 71 | sbh := wh.Add(HK_ACK, AckModeClient) 72 | id := TEST_TDESTPREF + "acksc1.chkprotocol-" + conn.Protocol() 73 | sbh = sbh.Add(HK_ID, id) // Always use an 'id' 74 | ms := "acksc1 message 1" 75 | // 76 | // Subscribe 77 | sc, e = conn.Subscribe(sbh) 78 | if e != nil { 79 | t.Fatalf("TestAckSameConn SUBSCRIBE expected [nil], got: [%v]\n", e) 80 | } 81 | 82 | // 83 | // Send 84 | sh := wh.Clone() 85 | // For RabbitMQ and STOMP 1.0, do not add current-time header, where the 86 | // value contains ':' characters. 87 | switch conn.Protocol() { 88 | case SPL_10: 89 | if os.Getenv("STOMP_RMQ") == "" { 90 | sh = sh.Add("current-time", time.Now().String()) // The added header value has ':' characters 91 | } 92 | default: 93 | sh = sh.Add("current-time", time.Now().String()) // The added header value has ':' characters 94 | } 95 | e = conn.Send(sh, ms) 96 | if e != nil { 97 | t.Fatalf("TestAckSameConn SEND expected [nil], got: [%v]\n", e) 98 | } 99 | // 100 | // Read MessageData 101 | select { 102 | case md = <-sc: 103 | case md = <-conn.MessageData: 104 | t.Fatalf("TestAckSameConn read channel error: expected [nil], got: [%v] msg: [%v] err: [%v]\n", 105 | md.Message.Command, md.Message, md.Error) 106 | } 107 | if md.Error != nil { 108 | t.Fatalf("TestAckSameConn read error: expected [nil], got: [%v]\n", 109 | md.Error) 110 | } 111 | if ms != md.Message.BodyString() { 112 | t.Fatalf("TestAckSameConn message error: expected: [%v], got: [%v] Message: [%q]\n", 113 | ms, md.Message.BodyString(), md.Message) 114 | } 115 | // Ack headers 116 | ah := Headers{} 117 | if conn.Protocol() == SPL_12 { 118 | ah = ah.Add(HK_ID, md.Message.Headers.Value(HK_ACK)) 119 | } else { 120 | ah = ah.Add(HK_MESSAGE_ID, md.Message.Headers.Value(HK_MESSAGE_ID)) 121 | } 122 | // 123 | if conn.Protocol() == SPL_11 { 124 | ah = ah.Add(HK_SUBSCRIPTION, id) // Always use subscription for 1.1 125 | } 126 | // Ack 127 | e = conn.Ack(ah) 128 | if e != nil { 129 | t.Fatalf("ACK expected [nil], got: [%v]\n", e) 130 | } 131 | // Make sure Apollo Jira issue APLO-88 stays fixed. 132 | select { 133 | case md = <-sc: 134 | t.Fatalf("TestAckSameConn RECEIVE not expected, got: [%v]\n", md) 135 | default: 136 | } 137 | 138 | // Unsubscribe 139 | uh := wh.Add(HK_ID, id) 140 | e = conn.Unsubscribe(uh) 141 | if e != nil { 142 | t.Fatalf("TestAckSameConn UNSUBSCRIBE expected [nil], got: [%v]\n", e) 143 | } 144 | 145 | // 146 | checkReceived(t, conn, false) 147 | e = conn.Disconnect(empty_headers) 148 | checkDisconnectError(t, e) 149 | _ = closeConn(t, n) 150 | } 151 | } 152 | 153 | /* 154 | Test Ack Different Connection. 155 | */ 156 | func TestAckDiffConn(t *testing.T) { 157 | 158 | for _, sp := range Protocols() { 159 | n, _ = openConn(t) 160 | ch := login_headers 161 | ch = headersProtocol(ch, sp) 162 | conn, e = Connect(n, ch) 163 | if e != nil { 164 | t.Fatalf("TestAckDiffConn CONNECT expected nil, got %v\n", e) 165 | } 166 | // 167 | // Basic headers 168 | wh := Headers{HK_DESTINATION, 169 | tdest(TEST_TDESTPREF + "ackdc1-" + conn.Protocol())} 170 | ms := "ackdc1 message 1" 171 | // Send 172 | sh := wh.Clone() 173 | // For RabbitMQ and STOMP 1.0, do not add current-time header, where the 174 | // value contains ':' characters. 175 | switch conn.Protocol() { 176 | case SPL_10: 177 | if os.Getenv("STOMP_RMQ") == "" { 178 | sh = sh.Add("current-time", time.Now().String()) // The added header value has ':' characters 179 | } 180 | default: 181 | sh = sh.Add("current-time", time.Now().String()) // The added header value has ':' characters 182 | } 183 | e = conn.Send(sh, ms) 184 | if e != nil { 185 | t.Fatalf("TestAckDiffConn SEND expected [nil], got: [%v]\n", e) 186 | } 187 | // 188 | checkReceived(t, conn, false) 189 | e = conn.Disconnect(empty_headers) 190 | checkDisconnectError(t, e) 191 | _ = closeConn(t, n) 192 | // 193 | n, _ = openConn(t) 194 | ch = login_headers 195 | ch = headersProtocol(ch, sp) 196 | conn, e = Connect(n, ch) // Reconnect 197 | if e != nil { 198 | t.Fatalf("TestAckDiffConn Second Connect, expected no error, got:<%v>\n", e) 199 | } 200 | // 201 | // Subscribe Headers 202 | sbh := wh.Add(HK_ACK, AckModeClient) 203 | id := "ackdc1.chkprotocol-" + conn.Protocol() 204 | sbh = sbh.Add(HK_ID, id) // Always use an 'id' 205 | // Subscribe 206 | log.Printf("SUB Headers: [%q]\n", sbh) 207 | sc, e = conn.Subscribe(sbh) 208 | if e != nil { 209 | t.Fatalf("TestAckDiffConn SUBSCRIBE expected [nil], got: [%v]\n", e) 210 | } 211 | // Read MessageData 212 | select { 213 | case md = <-sc: 214 | case md = <-conn.MessageData: 215 | t.Fatalf("TestAckDiffConn read channel error: expected [nil], got: [%v], msg: [%v], err: [%v]\n", 216 | md.Message.Command, md.Message, md.Error) 217 | } 218 | if md.Error != nil { 219 | t.Fatalf("read error: expected [nil], got: [%v]\n", md.Error) 220 | } 221 | if ms != md.Message.BodyString() { 222 | t.Fatalf("TestAckDiffConn message error: expected: [%v], got: [%v] Message: [%q]\n", 223 | ms, md.Message.BodyString(), md.Message) 224 | } 225 | // Ack headers 226 | ah := Headers{} 227 | if conn.Protocol() == SPL_12 { 228 | ah = ah.Add(HK_ID, md.Message.Headers.Value(HK_ACK)) 229 | } else { 230 | ah = ah.Add(HK_MESSAGE_ID, md.Message.Headers.Value(HK_MESSAGE_ID)) 231 | } 232 | // 233 | if conn.Protocol() == SPL_11 { 234 | ah = ah.Add(HK_SUBSCRIPTION, id) // Always use subscription for 1.1 235 | } 236 | // Ack 237 | e = conn.Ack(ah) 238 | if e != nil { 239 | t.Fatalf("TestAckDiffConn ACK expected [nil], got: [%v]\n", e) 240 | } 241 | // Make sure Apollo Jira issue APLO-88 stays fixed. 242 | select { 243 | case md = <-sc: 244 | t.Fatalf("TestAckDiffConn RECEIVE not expected, got: [%v]\n", md) 245 | default: 246 | } 247 | // Unsubscribe 248 | uh := wh.Add(HK_ID, id) 249 | e = conn.Unsubscribe(uh) 250 | if e != nil { 251 | t.Fatalf("TestAckDiffConn UNSUBSCRIBE expected [nil], got: [%v]\n", e) 252 | } 253 | // 254 | checkReceived(t, conn, false) 255 | e = conn.Disconnect(empty_headers) 256 | checkDisconnectError(t, e) 257 | _ = closeConn(t, n) 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /SENV.md: -------------------------------------------------------------------------------- 1 | # senv - A stompngo helper (sub) package 2 | 3 | The intent of this package is to assist the clients of the 4 | _stompngo_ package when developing and using _stompngo_ client application 5 | code. 6 | 7 | This assistance is _primarily_ implemented in the form of environment variables: 8 | 9 | * export=STOMP_var=value (Unix systems) 10 | * set STOMP_var=value (Windows systems) 11 | 12 | Using these environment variables can help avoid or eliminate 'hard coding' 13 | values in the client code. 14 | 15 | Environment variables and related subjects are discussed in the following sections: 16 | 17 | * [Supported Environment Variables](#senv) 18 | * [Supported Helper Function](#shf) 19 | * [Example Code Fragments](#ecf) 20 | * [Complete Connect Header Fragment](#cchf) 21 |
22 | 23 | --- 24 | 25 | ## Supported Environment Variables 26 | 27 | The following table shows currently supported environment variables. 28 | 29 | 30 | 31 | 34 | 37 | 38 | 39 | 40 | 43 | 47 | 48 | 49 | 50 | 53 | 57 | 58 | 59 | 60 | 63 | 67 | 68 | 69 | 70 | 73 | 77 | 78 | 79 | 80 | 83 | 87 | 88 | 89 | 90 | 93 | 97 | 98 | 99 | 100 | 103 | 109 | 110 | 111 | 112 | 115 | 119 | 120 | 121 | 122 | 125 | 131 | 132 | 133 | 134 | 137 | 141 | 142 | 143 | 144 | 147 | 151 | 152 |
32 | Environment Variable Name 33 | 35 | Usage 36 |
41 | STOMP_DEST 42 | 44 | A destination to be used by the client.
45 | Default: /queue/sng.sample.stomp.destination 46 |
51 | STOMP_HEARTBEATS 52 | 54 | For protocol 1.1+, the heart-beat value to be used by the client in the CONNECT frame.
55 | Default: 0,0 56 |
61 | STOMP_HOST 62 | 64 | The broker host to connect to.
65 | Default: localhost 66 |
71 | STOMP_LOGIN 72 | 74 | The login to be used by the client in the CONNECT frame.
75 | Default: guest 76 |
81 | STOMP_NMSGS 82 | 84 | A default nummber of messages to receive or send. Useful for some clients.
85 | Default: 1 86 |
91 | STOMP_PASSCODE 92 | 94 | The passcode to be used by the client in the CONNECT frame.
95 | Default: guest 96 |
101 | STOMP_PERSISTENT 102 | 104 | May control use of the persistent header in SEND frames.
105 | Default: no persistent header to be used.
106 | Example:
107 | STOMP_PERSISTENT=anyvalue 108 |
113 | STOMP_PORT 114 | 116 | The broker port to connect to.
117 | Default: 61613 118 |
123 | STOMP_PROTOCOL 124 | 126 | For protocol 1.1+, the accept-version value to be used by the client in the CONNECT frame.
127 | Default: 1.2
128 | Multiple versions may be used per the STOMP specifications, e.g.:
129 | STOMP_PROTOCOL="1.0,1.1,1.2" 130 |
135 | STOMP_SUBCHANCAP 136 | 138 | Used to possibly override the default capacity of _stompngo_ subscription channels.
139 | Default: 1 (the same as the _stompngo_ default.) 140 |
145 | STOMP_VHOST 146 | 148 | For protocol 1.1+, the host value to be used by the client in the CONNECT frame.
149 | Default: If not specified the default is STOMP_HOST (i.e. localhost). 150 |
153 | 154 |
155 | 156 | --- 157 | 158 | ## Supported Helper Function 159 | 160 | There is currently one helper function also provided by the _senv_ package. 161 | 162 | 163 | 164 | 167 | 170 | 171 | 172 | 173 | 176 | 181 | 182 | 183 |
165 | Function Name 166 | 168 | Usage 169 |
174 | HostAndPort() 175 | 177 | This function returns two values, the STOMP_HOST and STOMP_PORT values.
178 | Example:
179 | h, p := senv.HostAndPort() 180 |
184 | 185 |
186 | 187 | --- 188 | 189 | ## Example Code Fragments 190 | 191 | Example code fragments follow. 192 | 193 | ### STOMP_DEST Code Fragment 194 | 195 | sbh := ..... // Subscribe header, type stompngo.Headers 196 | if senv.Dest() != "" { 197 | sbh = sbh.Add("destination", senv.Dest()) 198 | } 199 | 200 | ### STOMP_HEARTBEATS Code Fragment 201 | 202 | ch := ..... // Connect headers, type stompngo.Headers 203 | if senv.Heartbeats() != "" { 204 | ch = ch.Add("heart-beat", senv.Heartbeats()) 205 | } 206 | 207 | ### STOMP_HOST Code Fragment 208 | 209 | ch := ..... // Connect headers, type stompngo.Headers 210 | if senv.Host() != "" { 211 | ch = ch.Add("host", senv.Host()) 212 | } 213 | 214 | ### STOMP_LOGIN Code Fragment 215 | 216 | ch := ..... // Connect headers, type stompngo.Headers 217 | if senv.Login() != "" { 218 | ch = ch.Add("login", senv.Login()) 219 | } 220 | 221 | ### STOMP_NMSGS Code Fragment 222 | 223 | msg_count := senv.Nmsgs() // Default is 1 224 | 225 | ### STOMP_PASSCODE Code Fragment 226 | 227 | ch := ..... // Connect headers, type stompngo.Headers 228 | if senv.Passcode() != "" { 229 | ch = ch.Add("passcode", senv.Passcode()) 230 | } 231 | 232 | ### STOMP_PERSISTENT Code Fragment 233 | 234 | sh := ..... // Send headers, type stompngo.Headers 235 | if senv.Persistent() != "" { 236 | ch = ch.Add("persistent", "true") // Brokers might need 'true' here 237 | } 238 | 239 | ### STOMP_PORT Code Fragment 240 | 241 | ch := ..... // Connect headers, type stompngo.Headers 242 | if senv.Port() != "" { 243 | ch = ch.Add("port", senv.Port()) 244 | } 245 | 246 | ### STOMP_PROTOCOL Code Fragment 247 | 248 | ch := ..... // Connect headers, type stompngo.Headers 249 | if senv.Protocol() != "" { 250 | ch = ch.Add("accept-version", senv.Protocol()) 251 | } 252 | 253 | ### STOMP_VHOST Code Fragment 254 | 255 | ch := ..... // Connect headers, type stompngo.Headers 256 | if senv.Vhost() != "" { 257 | ch = ch.Add("host", senv.Vhost()) 258 | } 259 |
260 | 261 | --- 262 | 263 | ## Complete Connect Header Fragment 264 | 265 | Obtaining a full set of headers to use for a _stompngo.Connect_ might 266 | look like this: 267 | 268 | func ConnectHeaders() stompngo.Headers { 269 | h := stompngo.Headers{} 270 | l := senv.Login() 271 | if l != "" { 272 | h = h.Add("login", l) 273 | } 274 | pc := senv.Passcode() 275 | if pc != "" { 276 | h = h.Add("passcode", pc) 277 | } 278 | // 279 | p := senv.Protocol() 280 | if p != stompngo.SPL_10 { // 1.1 and 1.2 281 | h = h.Add("accept-version", p).Add("host", senv.Vhost()) 282 | } 283 | // 284 | hb := senv.Heartbeats() 285 | if hb != "" { 286 | h = h.Add("heart-beat", hb) 287 | } 288 | return h 289 | } 290 | 291 | -------------------------------------------------------------------------------- /writer.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "bufio" 21 | "bytes" 22 | "net" 23 | 24 | // "bytes" 25 | "strconv" 26 | "time" 27 | ) 28 | 29 | /* 30 | Write data to logical network writer. Writer will take care of the output wire data. 31 | If the underlying connection goes bad and writer give up working, the closed ssdc chan 32 | will make sure write action aware that happens. 33 | */ 34 | func (c *Connection) writeWireData(wd wiredata) error { 35 | select { 36 | case c.output <- wd: 37 | case <-c.ssdc: 38 | return ECONBAD 39 | } 40 | return nil 41 | } 42 | 43 | /* 44 | Logical network writer. Read wiredata structures from the communication 45 | channel, and put the frame on the wire. 46 | */ 47 | func (c *Connection) writer() { 48 | writerLoop: 49 | for { 50 | select { 51 | case d := <-c.output: 52 | c.log("WTR_WIREWRITE start") 53 | if c.eltd != nil { 54 | st := time.Now().UnixNano() 55 | c.wireWrite(d) 56 | c.eltd.wov.ens += time.Now().UnixNano() - st 57 | c.eltd.wov.ec++ 58 | } else { 59 | c.wireWrite(d) 60 | } 61 | logLock.Lock() 62 | if c.logger != nil { 63 | c.logx("WTR_WIREWRITE COMPLETE", d.frame.Command, d.frame.Headers, 64 | HexData(d.frame.Body)) 65 | } 66 | logLock.Unlock() 67 | if d.frame.Command == DISCONNECT { 68 | break writerLoop // we are done with this connection 69 | } 70 | case _ = <-c.ssdc: 71 | c.log("WTR_WIREWRITE shutdown S received") 72 | break writerLoop 73 | case _ = <-c.wtrsdc: 74 | c.log("WTR_WIREWRITE shutdown W received") 75 | break writerLoop 76 | } 77 | } // of for 78 | // 79 | c.setConnected(false) 80 | c.sysAbort() 81 | c.log("WTR_SHUTDOWN", time.Now()) 82 | } 83 | 84 | /* 85 | Connection logical write. 86 | */ 87 | func (c *Connection) wireWrite(d wiredata) { 88 | f := &d.frame 89 | // fmt.Printf("WWD01 f:[%v]\n", f) 90 | switch f.Command { 91 | case "\n": // HeartBeat frame 92 | if c.dld.wde && c.dld.wds { 93 | _ = c.netconn.SetWriteDeadline(time.Now().Add(c.dld.wdld)) 94 | } 95 | var e error 96 | if c.eltd != nil { 97 | st := time.Now().UnixNano() 98 | _, e = c.wtr.WriteString(f.Command) 99 | c.eltd.wcmd.ens += time.Now().UnixNano() - st 100 | c.eltd.wcmd.ec++ 101 | } else { 102 | _, e = c.wtr.WriteString(f.Command) 103 | } 104 | 105 | if e != nil { 106 | if e.(net.Error).Timeout() { 107 | if c.dld.dns { 108 | c.dld.dlnotify(e, true) 109 | } 110 | } 111 | d.errchan <- e 112 | return 113 | } 114 | default: // Other frames 115 | if e := f.writeFrame(c.wtr, c); e != nil { 116 | d.errchan <- e 117 | return 118 | } 119 | if e := c.wtr.Flush(); e != nil { 120 | d.errchan <- e 121 | return 122 | } 123 | } 124 | if e := c.wtr.Flush(); e != nil { 125 | d.errchan <- e 126 | return 127 | } 128 | // 129 | if c.hbd != nil { 130 | c.hbd.sdl.Lock() 131 | c.hbd.ls = time.Now().UnixNano() // Latest good send 132 | c.hbd.sdl.Unlock() 133 | } 134 | c.mets.tfw++ // Frame written count 135 | c.mets.tbw += f.Size(false) // Bytes written count 136 | // 137 | d.errchan <- nil 138 | return 139 | } 140 | 141 | /* 142 | Physical frame write to the wire. 143 | */ 144 | func (f *Frame) writeFrame(w *bufio.Writer, c *Connection) error { 145 | 146 | var sctok bool 147 | // Content type. Always add it if the client does not suppress and does not 148 | // supply it. 149 | _, sctok = f.Headers.Contains(HK_SUPPRESS_CT) 150 | if !sctok { 151 | if _, ctok := f.Headers.Contains(HK_CONTENT_TYPE); !ctok { 152 | f.Headers = append(f.Headers, HK_CONTENT_TYPE, 153 | DFLT_CONTENT_TYPE) 154 | } 155 | } 156 | 157 | var sclok bool 158 | // Content length - Always add it if client does not suppress it and 159 | // does not supply it. 160 | _, sclok = f.Headers.Contains(HK_SUPPRESS_CL) 161 | if !sclok { 162 | if _, clok := f.Headers.Contains(HK_CONTENT_LENGTH); !clok { 163 | f.Headers = append(f.Headers, HK_CONTENT_LENGTH, strconv.Itoa(len(f.Body))) 164 | } 165 | } 166 | // Encode the headers if needed 167 | if c.Protocol() > SPL_10 && f.Command != CONNECT { 168 | for i := 0; i < len(f.Headers); i += 2 { 169 | f.Headers[i] = encode(f.Headers[i]) 170 | f.Headers[i+1] = encode(f.Headers[i+1]) 171 | } 172 | } 173 | 174 | if sclok { 175 | nz := bytes.IndexByte(f.Body, 0) 176 | // fmt.Printf("WDBG41 ok:%v\n", nz) 177 | if nz == 0 { 178 | f.Body = []byte{} 179 | // fmt.Printf("WDBG42 body:%v bodystring: %v\n", f.Body, string(f.Body)) 180 | } else if nz > 0 { 181 | f.Body = f.Body[0:nz] 182 | // fmt.Printf("WDBG43 body:%v bodystring: %v\n", f.Body, string(f.Body)) 183 | } 184 | } 185 | 186 | if c.dld.wde && c.dld.wds { 187 | _ = c.netconn.SetWriteDeadline(time.Now().Add(c.dld.wdld)) 188 | } 189 | 190 | // Writes start 191 | 192 | // Write the frame Command 193 | var e error 194 | if c.eltd != nil { 195 | st := time.Now().UnixNano() 196 | _, e = w.WriteString(f.Command + "\n") 197 | c.eltd.wcmd.ens += time.Now().UnixNano() - st 198 | c.eltd.wcmd.ec++ 199 | } else { 200 | _, e = w.WriteString(f.Command + "\n") 201 | } 202 | 203 | if c.checkWriteError(e) != nil { 204 | return e 205 | } 206 | // fmt.Println("WRCMD", f.Command) 207 | // Write the frame Headers 208 | for i := 0; i < len(f.Headers); i += 2 { 209 | if c.dld.wde && c.dld.wds { 210 | _ = c.netconn.SetWriteDeadline(time.Now().Add(c.dld.wdld)) 211 | } 212 | 213 | if c.eltd != nil { 214 | st := time.Now().UnixNano() 215 | _, e = w.WriteString(f.Headers[i] + ":" + f.Headers[i+1] + "\n") 216 | c.eltd.wivh.ens += time.Now().UnixNano() - st 217 | c.eltd.wivh.ec++ 218 | } else { 219 | _, e = w.WriteString(f.Headers[i] + ":" + f.Headers[i+1] + "\n") 220 | } 221 | 222 | if c.checkWriteError(e) != nil { 223 | return e 224 | } 225 | // fmt.Println("WRHDR", f.Headers[i]+":"+f.Headers[i+1]+"\n") 226 | } 227 | 228 | // Write the last Header LF 229 | if c.dld.wde && c.dld.wds { 230 | _ = c.netconn.SetWriteDeadline(time.Now().Add(c.dld.wdld)) 231 | } 232 | e = w.WriteByte('\n') 233 | if c.checkWriteError(e) != nil { 234 | return e 235 | } 236 | // fmt.Printf("WDBG40 ok:%v\n", sclok) 237 | 238 | // Write the body 239 | if len(f.Body) != 0 { // Foolish to write 0 length data 240 | // fmt.Println("WRBDY", f.Body) 241 | e := c.writeBody(f) 242 | if c.checkWriteError(e) != nil { 243 | return e 244 | } 245 | } 246 | if c.dld.wde && c.dld.wds { 247 | _ = c.netconn.SetWriteDeadline(time.Now().Add(c.dld.wdld)) 248 | } 249 | e = w.WriteByte(0) 250 | if c.checkWriteError(e) != nil { 251 | return e 252 | } 253 | // End of write loop - set no deadline 254 | if c.dld.wde { 255 | _ = c.netconn.SetWriteDeadline(c.dld.t0) 256 | } 257 | return nil 258 | } 259 | 260 | func (c *Connection) checkWriteError(e error) error { 261 | if e == nil { 262 | return e 263 | } 264 | ne, ok := e.(net.Error) 265 | if !ok { 266 | return e 267 | } 268 | if ne.Timeout() { 269 | if c.dld.dns { 270 | c.log("invoking write deadline callback 1") 271 | c.dld.dlnotify(e, true) 272 | } 273 | } 274 | return e 275 | } 276 | 277 | func (c *Connection) writeBody(f *Frame) error { 278 | // fmt.Printf("WDBG99 body:%v bodystring: %v\n", f.Body, string(f.Body)) 279 | var n = 0 280 | var e error 281 | for { 282 | if c.dld.wde && c.dld.wds { 283 | _ = c.netconn.SetWriteDeadline(time.Now().Add(c.dld.wdld)) 284 | } 285 | if c.eltd != nil { 286 | st := time.Now().UnixNano() 287 | n, e = c.wtr.Write(f.Body) 288 | c.eltd.wbdy.ens += time.Now().UnixNano() - st 289 | c.eltd.wbdy.ec++ 290 | } else { 291 | n, e = c.wtr.Write(f.Body) 292 | } 293 | if n == len(f.Body) { 294 | return e 295 | } 296 | c.log("SHORT WRITE", n, len(f.Body)) 297 | if n == 0 { // Zero bytes would mean something is seriously wrong. 298 | return e 299 | } 300 | if !c.dld.rfsw { 301 | return e 302 | } 303 | if c.dld.wde && c.dld.wds && c.dld.dns && isErrorTimeout(e) { 304 | c.log("invoking write deadline callback 2") 305 | c.dld.dlnotify(e, true) 306 | } 307 | // *Any* error from a bufio.Writer is *not* recoverable. See code in 308 | // bufio.go to understand this. We get a new writer here, to clear any 309 | // error condition. 310 | c.wtr = bufio.NewWriter(c.netconn) // Create new writer 311 | f.Body = f.Body[n:] 312 | } 313 | } 314 | 315 | func isErrorTimeout(e error) bool { 316 | if e == nil { 317 | return false 318 | } 319 | _, ok := e.(net.Error) 320 | if !ok { 321 | return false 322 | } 323 | return true 324 | } 325 | -------------------------------------------------------------------------------- /misc_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2012-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | /* 24 | Test A zero Byte Message, a corner case. 25 | */ 26 | func TestMiscBytes0(t *testing.T) { 27 | for _, sp := range Protocols() { 28 | //tlg.Printf("TestMiscBytes0 protocol:%s\n", sp) 29 | // Write phase 30 | //tlg.Printf("TestMiscBytes0 WRITEPHASE\n") 31 | n, _ = openConn(t) 32 | ch := login_headers 33 | ch = headersProtocol(ch, sp) 34 | conn, e = Connect(n, ch) 35 | if e != nil { 36 | t.Fatalf("TestMiscBytes0 CONNECT expected nil, got %v\n", e) 37 | } 38 | // 39 | ms := "" // No data 40 | d := tdest("/queue/misc.zero.byte.msg." + sp) 41 | sh := Headers{HK_DESTINATION, d} 42 | e = conn.Send(sh, ms) 43 | if e != nil { 44 | t.Fatalf("TestMiscBytes0 Expected nil error, got [%v]\n", e) 45 | } 46 | // 47 | checkReceived(t, conn, false) 48 | e = conn.Disconnect(empty_headers) 49 | checkDisconnectError(t, e) 50 | _ = closeConn(t, n) 51 | 52 | // Read phase 53 | //tlg.Printf("TestMiscBytes0 READPHASE\n") 54 | n, _ = openConn(t) 55 | ch = login_headers 56 | ch = headersProtocol(ch, sp) 57 | conn, _ = Connect(n, ch) 58 | // 59 | sbh := sh.Add(HK_ID, d) 60 | sc, e = conn.Subscribe(sbh) 61 | if e != nil { 62 | t.Fatalf("TestMiscBytes0 Expected no subscribe error, got [%v]\n", e) 63 | } 64 | if sc == nil { 65 | t.Fatalf("TestMiscBytes0 Expected subscribe channel, got [nil]\n") 66 | } 67 | 68 | // Read MessageData 69 | var md MessageData 70 | select { 71 | case md = <-sc: 72 | case md = <-conn.MessageData: 73 | t.Fatalf("TestMiscBytes0 read channel error: expected [nil], got: [%v]\n", 74 | md.Message.Command) 75 | } 76 | 77 | if md.Error != nil { 78 | t.Fatalf("TestMiscBytes0 Expected no message data error, got [%v]\n", 79 | md.Error) 80 | } 81 | 82 | // The real tests here 83 | if len(md.Message.Body) != 0 { 84 | t.Fatalf("TestMiscBytes0 Expected body length 0, got [%v]\n", 85 | len(md.Message.Body)) 86 | } 87 | if string(md.Message.Body) != ms { 88 | t.Fatalf("TestMiscBytes0 Expected [%v], got [%v]\n", 89 | ms, string(md.Message.Body)) 90 | } 91 | // 92 | //tlg.Printf("TestMiscBytes0 CLEANUP\n") 93 | checkReceived(t, conn, false) 94 | e = conn.Disconnect(empty_headers) 95 | checkDisconnectError(t, e) 96 | _ = closeConn(t, n) 97 | } 98 | } 99 | 100 | /* 101 | Test A One Byte Message, a corner case. 102 | */ 103 | func TestMiscBytes1(t *testing.T) { 104 | for _, sp := range Protocols() { 105 | //tlg.Printf("TestMiscBytes0 protocol:%s\n", sp) 106 | // Write phase 107 | //tlg.Printf("TestMiscBytes0 WRITEPHASE\n") 108 | n, _ = openConn(t) 109 | ch := login_headers 110 | ch = headersProtocol(ch, sp) 111 | conn, e = Connect(n, ch) 112 | if e != nil { 113 | t.Fatalf("TestMiscBytes1 CONNECT expected nil, got %v\n", e) 114 | } 115 | // 116 | ms := "Z" // One Byte 117 | d := tdest("/queue/misc.zero.byte.msg." + sp) 118 | sh := Headers{HK_DESTINATION, d} 119 | e = conn.Send(sh, ms) 120 | if e != nil { 121 | t.Fatalf("TestMiscBytes1 Expected nil error, got [%v]\n", e) 122 | } 123 | // 124 | checkReceived(t, conn, false) 125 | e = conn.Disconnect(empty_headers) 126 | checkDisconnectError(t, e) 127 | _ = closeConn(t, n) 128 | 129 | // Read phase 130 | //tlg.Printf("TestMiscBytes1 READPHASE\n") 131 | n, _ = openConn(t) 132 | ch = login_headers 133 | ch = headersProtocol(ch, sp) 134 | conn, _ = Connect(n, ch) 135 | // 136 | sbh := sh.Add(HK_ID, d) 137 | sc, e = conn.Subscribe(sbh) 138 | if e != nil { 139 | t.Fatalf("TestMiscBytes1 Expected no subscribe error, got [%v]\n", e) 140 | } 141 | if sc == nil { 142 | t.Fatalf("TestMiscBytes1 Expected subscribe channel, got [nil]\n") 143 | } 144 | 145 | // Read MessageData 146 | var md MessageData 147 | select { 148 | case md = <-sc: 149 | case md = <-conn.MessageData: 150 | t.Fatalf("TestMiscBytes1 read channel error: expected [nil], got: [%v]\n", 151 | md.Message.Command) 152 | } 153 | 154 | if md.Error != nil { 155 | t.Fatalf("TestMiscBytes1 Expected no message data error, got [%v]\n", 156 | md.Error) 157 | } 158 | 159 | // The real tests here 160 | if len(md.Message.Body) != 1 { 161 | t.Fatalf("TestMiscBytes1 Expected body length 1, got [%v]\n", 162 | len(md.Message.Body)) 163 | } 164 | if string(md.Message.Body) != ms { 165 | t.Fatalf("TestMiscBytes1 Expected [%v], got [%v]\n", 166 | ms, string(md.Message.Body)) 167 | } 168 | // 169 | //tlg.Printf("TestMiscBytes1 CLEANUP\n") 170 | checkReceived(t, conn, false) 171 | e = conn.Disconnect(empty_headers) 172 | checkDisconnectError(t, e) 173 | _ = closeConn(t, n) 174 | } 175 | } 176 | 177 | /* 178 | Test nil Headers. 179 | */ 180 | func TestMiscNilHeaders(t *testing.T) { 181 | for _, _ = range Protocols() { 182 | n, _ = openConn(t) 183 | // 184 | _, e = Connect(n, nil) 185 | if e == nil { 186 | t.Fatalf("TestMiscNilHeaders Expected [%v], got [nil]\n", 187 | EHDRNIL) 188 | } 189 | if e != EHDRNIL { 190 | t.Fatalf("TestMiscNilHeaders Expected [%v], got [%v]\n", 191 | EHDRNIL, e) 192 | } 193 | // 194 | ch := check11(TEST_HEADERS) 195 | conn, _ = Connect(n, ch) 196 | // 197 | e = nil // reset 198 | e = conn.Abort(nil) 199 | if e == nil { 200 | t.Fatalf("TestMiscNilHeaders Abort Expected [%v], got [nil]\n", 201 | EHDRNIL) 202 | } 203 | // 204 | e = nil // reset 205 | e = conn.Ack(nil) 206 | if e == nil { 207 | t.Fatalf("TestMiscNilHeaders Ack Expected [%v], got [nil]\n", 208 | EHDRNIL) 209 | } 210 | // 211 | e = nil // reset 212 | e = conn.Begin(nil) 213 | if e == nil { 214 | t.Fatalf("TestMiscNilHeaders Begin Expected [%v], got [nil]\n", 215 | EHDRNIL) 216 | } 217 | // 218 | e = nil // reset 219 | e = conn.Commit(nil) 220 | if e == nil { 221 | t.Fatalf("TestMiscNilHeaders Commit Expected [%v], got [nil]\n", 222 | EHDRNIL) 223 | } 224 | // 225 | e = nil // reset 226 | e = conn.Disconnect(nil) 227 | if e == nil { 228 | t.Fatalf("TestMiscNilHeaders Disconnect Expected [%v], got [nil]\n", 229 | EHDRNIL) 230 | } 231 | // 232 | if conn.Protocol() > SPL_10 { 233 | e = nil // reset 234 | e = conn.Disconnect(nil) 235 | if e == nil { 236 | t.Fatalf("TestMiscNilHeaders Nack Expected [%v], got [nil]\n", 237 | EHDRNIL) 238 | } 239 | } 240 | // 241 | e = nil // reset 242 | e = conn.Send(nil, "") 243 | if e == nil { 244 | t.Fatalf("TestMiscNilHeaders Send Expected [%v], got [nil]\n", 245 | EHDRNIL) 246 | } 247 | // 248 | } 249 | } 250 | 251 | /* 252 | Test max function. 253 | */ 254 | func TestMiscMax(t *testing.T) { 255 | for _, _ = range Protocols() { 256 | var l int64 = 1 // low 257 | var h int64 = 2 // high 258 | mr := max(l, h) 259 | if mr != 2 { 260 | t.Fatalf("TestMiscMax Expected [%v], got [%v]\n", h, mr) 261 | } 262 | mr = max(h, l) 263 | if mr != 2 { 264 | t.Fatalf("TestMiscMax Expected [%v], got [%v]\n", h, mr) 265 | } 266 | } 267 | } 268 | 269 | /* 270 | Test hasValue function. 271 | */ 272 | func TestMiscHasValue(t *testing.T) { 273 | for _, _ = range Protocols() { 274 | sa := []string{"a", "b"} 275 | if !hasValue(sa, "a") { 276 | t.Fatalf("TestMiscHasValue Expected [true], got [false] for [%v]\n", "a") 277 | } 278 | if hasValue(sa, "z") { 279 | t.Fatalf("TestMiscHasValue Expected [false], got [true] for [%v]\n", "z") 280 | } 281 | } 282 | } 283 | 284 | /* 285 | Test Uuid function. 286 | */ 287 | func TestMiscUuid(t *testing.T) { 288 | for _, _ = range Protocols() { 289 | id := Uuid() 290 | if id == "" { 291 | t.Fatalf("TestMiscUuid Expected a UUID, got empty string\n") 292 | } 293 | if len(id) != 36 { 294 | t.Fatalf("TestMiscUuid Expected a 36 character UUID, got length [%v]\n", 295 | len(id)) 296 | } 297 | } 298 | } 299 | 300 | /* 301 | Test Bad Headers 302 | */ 303 | func TestMiscBadHeaders(t *testing.T) { 304 | for _, sp = range Protocols() { 305 | // 306 | n, _ = openConn(t) 307 | neh := Headers{"a", "b", "c"} // not even number header count 308 | conn, e = Connect(n, neh) 309 | 310 | // Connection should be nil (i.e. no connection) 311 | if e == nil { 312 | t.Fatalf("TestMiscBadHeaders Expected [%v], got [nil]\n", EHDRLEN) 313 | } 314 | if e != EHDRLEN { 315 | t.Fatalf("TestMiscBadHeaders Expected [%v], got [%v]\n", EHDRLEN, e) 316 | } 317 | // 318 | bvh := Headers{HK_HOST, "localhost", HK_ACCEPT_VERSION, "3.14159"} 319 | conn, e = Connect(n, bvh) 320 | if e == nil { 321 | t.Fatalf("TestMiscBadHeaders Expected [%v], got [nil]\n", EBADVERCLI) 322 | } 323 | if e != EBADVERCLI { 324 | t.Fatalf("TestMiscBadHeaders Expected [%v], got [%v]\n", EBADVERCLI, e) 325 | } 326 | 327 | // 328 | ch := login_headers 329 | ch = headersProtocol(ch, sp) 330 | //log.Printf("TestMiscBadHeaders Protocol %s, CONNECT Headers: %v\n", sp, ch) 331 | conn, e = Connect(n, ch) 332 | if e != nil { 333 | t.Fatalf("TestMiscBadHeaders CONNECT 2 expected nil, got %v connectresponse: %v\n", 334 | e, conn.ConnectResponse) 335 | } 336 | 337 | // Connection should not be nil (i.e. good connection) 338 | _, e = conn.Subscribe(neh) 339 | if e == nil { 340 | t.Fatalf("TestMiscBadHeaders Expected [%v], got [nil]\n", EHDRLEN) 341 | } 342 | if e != EHDRLEN { 343 | t.Fatalf("TestMiscBadHeaders Expected [%v], got [%v]\n", EHDRLEN, e) 344 | } 345 | // 346 | e = conn.Unsubscribe(neh) 347 | if e == nil { 348 | t.Fatalf("TestMiscBadHeaders Expected [%v], got [nil]\n", EHDRLEN) 349 | } 350 | if e != EHDRLEN { 351 | t.Fatalf("TestMiscBadHeaders Expected [%v], got [%v]\n", EHDRLEN, e) 352 | } 353 | // 354 | if conn != nil && conn.Connected() { 355 | e = conn.Disconnect(empty_headers) 356 | checkDisconnectError(t, e) 357 | } 358 | _ = closeConn(t, n) 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /conndisc_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2011-2019 Guy M. Allard 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | package stompngo 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | /* 24 | ConnDisc Test: net.Conn. 25 | */ 26 | func TestConnCDDiscNetconn(t *testing.T) { 27 | n, _ = openConn(t) 28 | _ = closeConn(t, n) 29 | } 30 | 31 | /* 32 | ConnDisc Test: stompngo.Connect. 33 | */ 34 | func TestConnCDDisc(t *testing.T) { 35 | for _, sp := range Protocols() { 36 | n, _ = openConn(t) 37 | ch := login_headers 38 | ch = headersProtocol(ch, sp) 39 | conn, e = Connect(n, ch) 40 | if e != nil { 41 | t.Fatalf("TestConnCDDisc Expected no connect error, got [%v]\n", e) 42 | } 43 | if conn == nil { 44 | t.Fatalf("TestConnCDDisc Expected a connection, got [nil]\n") 45 | } 46 | if conn.ConnectResponse.Command != CONNECTED { 47 | t.Fatalf("TestConnCDDisc Expected command [%v], got [%v]\n", CONNECTED, 48 | conn.ConnectResponse.Command) 49 | } 50 | if !conn.isConnected() { 51 | t.Fatalf("TestConnCDDisc Expected connected [true], got [false]\n") 52 | } 53 | if !conn.Connected() { 54 | t.Fatalf("TestConnCDDisc Expected connected [true], got [false]\n") 55 | } 56 | // 57 | if conn.Session() == "" { 58 | t.Fatalf("TestConnCDDisc Expected connected session, got [default value]\n") 59 | } 60 | // 61 | if conn.SendTickerInterval() != 0 { 62 | t.Fatalf("TestConnCDDisc Expected zero SendTickerInterval, got [%v]\n", 63 | conn.SendTickerInterval()) 64 | } 65 | if conn.ReceiveTickerInterval() != 0 { 66 | t.Fatalf("TestConnCDDisc Expected zero ReceiveTickerInterval, got [%v]\n", 67 | conn.SendTickerInterval()) 68 | } 69 | if conn.SendTickerCount() != 0 { 70 | t.Fatalf("TestConnCDDisc Expected zero SendTickerCount, got [%v]\n", 71 | conn.SendTickerCount()) 72 | } 73 | if conn.ReceiveTickerCount() != 0 { 74 | t.Fatalf("TestConnCDDisc Expected zero ReceiveTickerCount, got [%v]\n", 75 | conn.SendTickerCount()) 76 | } 77 | // 78 | if conn.FramesRead() != 1 { 79 | t.Fatalf("TestConnCDDisc Expected 1 frame read, got [%d]\n", conn.FramesRead()) 80 | } 81 | if conn.BytesRead() <= 0 { 82 | t.Fatalf("TestConnCDDisc Expected non-zero bytes read, got [%d]\n", conn.BytesRead()) 83 | } 84 | if conn.FramesWritten() != 1 { 85 | t.Fatalf("TestConnCDDisc Expected 1 frame written, got [%d]\n", conn.FramesWritten()) 86 | } 87 | if conn.BytesWritten() <= 0 { 88 | t.Fatalf("ETestConnCDDisc xpected non-zero bytes written, got [%d]\n", 89 | conn.BytesWritten()) 90 | } 91 | if conn.Running().Nanoseconds() == 0 { 92 | t.Fatalf("TestConnCDDisc Expected non-zero runtime, got [0]\n") 93 | } 94 | // 95 | checkReceived(t, conn, false) 96 | e = conn.Disconnect(empty_headers) 97 | checkDisconnectError(t, e) 98 | _ = closeConn(t, n) 99 | } 100 | } 101 | 102 | /* 103 | ConnDisc Test: stompngo.Disconnect with client bypassing a receipt. 104 | */ 105 | func TestConnCDDiscNoDiscReceipt(t *testing.T) { 106 | for _, sp := range Protocols() { 107 | n, _ = openConn(t) 108 | ch := login_headers 109 | ch = headersProtocol(ch, sp) 110 | conn, e = Connect(n, ch) 111 | if e != nil { 112 | t.Fatalf("TestConnCDDiscNoDiscReceipt Expected no connect error, got [%v]\n", e) 113 | } 114 | // DISCONNECT Here 115 | checkReceived(t, conn, false) 116 | e = conn.Disconnect(NoDiscReceipt) 117 | checkDisconnectError(t, e) 118 | if e != nil { 119 | t.Fatalf("TestConnCDDiscNoDiscReceipt Expected no disconnect error, got [%v]\n", e) 120 | } 121 | if conn.DisconnectReceipt.Message.Command != "" { 122 | t.Fatalf("TestConnCDDiscNoDiscReceipt Expected no disconnect receipt command, got [%v]\n", 123 | conn.DisconnectReceipt.Message.Command) 124 | } 125 | // NO DISCONNECT checks here 126 | _ = closeConn(t, n) 127 | } 128 | } 129 | 130 | /* 131 | ConnDisc Test: stompngo.Disconnect with receipt requested. 132 | */ 133 | func TestConnCDDiscStompDiscReceipt(t *testing.T) { 134 | for _, sp := range Protocols() { 135 | n, _ = openConn(t) 136 | ch := login_headers 137 | ch = headersProtocol(ch, sp) 138 | conn, e = Connect(n, ch) 139 | if e != nil { 140 | t.Fatalf("TestConnCDDiscStompDiscReceipt Expected no connect error, got [%v]\n", 141 | e) 142 | } 143 | // DISCONNECT Here 144 | checkReceived(t, conn, false) 145 | e = conn.Disconnect(Headers{HK_RECEIPT, rid}) 146 | if e != nil { 147 | 148 | t.Fatalf("TestConnCDDiscStompDiscReceipt Expected no disconnect error, got [%v]\n", 149 | e) 150 | } 151 | if conn.DisconnectReceipt.Error != nil { 152 | t.Fatalf("TestConnCDDiscStompDiscReceipt Expected no receipt error, got [%v]\n", 153 | conn.DisconnectReceipt.Error) 154 | } 155 | md := conn.DisconnectReceipt.Message 156 | irid, ok := md.Headers.Contains(HK_RECEIPT_ID) 157 | if !ok { 158 | t.Fatalf("TestConnCDDiscStompDiscReceipt Expected receipt-id, not received\n") 159 | } 160 | if rid != irid { 161 | t.Fatalf("TestConnCDDiscStompDiscReceipt Expected receipt-id [%q], got [%q]\n", 162 | rid, irid) 163 | } 164 | // NO DISCONNECT checks here 165 | _ = closeConn(t, n) 166 | } 167 | } 168 | 169 | /* 170 | ConnDisc Test: Body Length of CONNECTED response. 171 | */ 172 | func TestConnCDBodyLen(t *testing.T) { 173 | for _, sp := range Protocols() { 174 | n, _ = openConn(t) 175 | ch := login_headers 176 | ch = headersProtocol(ch, sp) 177 | conn, e = Connect(n, ch) 178 | if e != nil { 179 | t.Fatalf("TestConnCDBodyLen Expected no connect error, got [%v]\n", e) 180 | } 181 | if len(conn.ConnectResponse.Body) != 0 { 182 | t.Fatalf("TestConnCDBodyLen Expected body length 0, got [%v]\n", 183 | len(conn.ConnectResponse.Body)) 184 | } 185 | e = conn.Disconnect(empty_headers) 186 | if e != nil { 187 | t.Fatalf("TestConnCDBodyLen Expected no disconnect error, got [%v]\n", e) 188 | } 189 | _ = closeConn(t, n) 190 | } 191 | } 192 | 193 | /* 194 | Conn11 Test: Test Protocol level 195 | */ 196 | func TestConnCDProto(t *testing.T) { 197 | for _, sp := range Protocols() { 198 | n, _ = openConn(t) 199 | ch := login_headers 200 | ch = headersProtocol(ch, sp) 201 | conn, e = Connect(n, ch) 202 | if e != nil { 203 | t.Fatalf("TestConnCDProto Expected no connect error, got [%v]\n", e) 204 | } 205 | if conn.Protocol() != sp { 206 | t.Fatalf("TestConnCDProto Expected protocol %v, got [%v]\n", sp, conn.Protocol()) 207 | } 208 | checkReceived(t, conn, false) 209 | e = conn.Disconnect(empty_headers) 210 | if e != nil { 211 | t.Fatalf("TestConnCDProto Expected no disconnect error, got [%v]\n", e) 212 | } 213 | _ = closeConn(t, n) 214 | } 215 | } 216 | 217 | /* 218 | ConnReceipt Test: Test receipt not allowed on connect. 219 | */ 220 | func TestConnCDReceipt(t *testing.T) { 221 | for _, sp := range Protocols() { 222 | n, _ = openConn(t) 223 | ch := login_headers 224 | ch = headersProtocol(ch, sp) 225 | ch = ch.Add(HK_RECEIPT, "abcd1234") 226 | _, e = Connect(n, ch) 227 | if e == nil { 228 | t.Fatalf("TestConnCDReceipt Expected connect error, got nil\n") 229 | } 230 | if e != ENORECPT { 231 | t.Fatalf("TestConnCDReceipt Expected [%v], got [%v]\n", ENORECPT, e) 232 | } 233 | // No DISCONNECT checks for this test. 234 | _ = closeConn(t, n) 235 | } 236 | } 237 | 238 | /* 239 | ConnDisc Test: ECONBAD 240 | */ 241 | func TestConnCDEconBad(t *testing.T) { 242 | for _, sp := range Protocols() { 243 | n, _ = openConn(t) 244 | ch := login_headers 245 | ch = headersProtocol(ch, sp) 246 | conn, e = Connect(n, ch) 247 | if e != nil { 248 | t.Fatalf("TestConnCDEconBad Expected no connect error, got [%v]\n", e) 249 | } 250 | checkReceived(t, conn, false) 251 | e = conn.Disconnect(empty_headers) 252 | if e != nil { 253 | t.Fatalf("TestConnCDEconBad Expected no disconnect error, got [%v]\n", e) 254 | } 255 | _ = closeConn(t, n) 256 | // 257 | e = conn.Abort(empty_headers) 258 | if e != ECONBAD { 259 | t.Fatalf("TestConnCDEconBad Abort expected [%v] got [%v]\n", ECONBAD, e) 260 | } 261 | e = conn.Ack(empty_headers) 262 | if e != ECONBAD { 263 | t.Fatalf("TestConnCDEconBad Ack expected [%v] got [%v]\n", ECONBAD, e) 264 | } 265 | e = conn.Begin(empty_headers) 266 | if e != ECONBAD { 267 | t.Fatalf("TestConnCDEconBad Begin expected [%v] got [%v]\n", ECONBAD, e) 268 | } 269 | e = conn.Commit(empty_headers) 270 | if e != ECONBAD { 271 | t.Fatalf("TestConnCDEconBad Commit expected [%v] got [%v]\n", ECONBAD, e) 272 | } 273 | e = conn.Nack(empty_headers) 274 | if e != ECONBAD { 275 | t.Fatalf("TestConnCDEconBad Nack expected [%v] got [%v]\n", ECONBAD, e) 276 | } 277 | e = conn.Send(empty_headers, "") 278 | if e != ECONBAD { 279 | t.Fatalf("TestConnCDEconBad Send expected [%v] got [%v]\n", ECONBAD, e) 280 | } 281 | _, e = conn.Subscribe(empty_headers) 282 | if e != ECONBAD { 283 | t.Fatalf("TestConnCDEconBad Subscribe expected [%v] got [%v]\n", ECONBAD, e) 284 | } 285 | e = conn.Unsubscribe(empty_headers) 286 | if e != ECONBAD { 287 | t.Fatalf("TestConnCDEconBad Unsubscribe expected [%v] got [%v]\n", ECONBAD, e) 288 | } 289 | } 290 | } 291 | 292 | /* 293 | ConnDisc Test: ECONBAD 294 | */ 295 | func TestConnCDEconDiscDone(t *testing.T) { 296 | for _, sp := range Protocols() { 297 | n, _ = openConn(t) 298 | ch := login_headers 299 | ch = headersProtocol(ch, sp) 300 | conn, e = Connect(n, ch) 301 | if e != nil { 302 | t.Fatalf("TestConnCDEconDiscDone Expected no connect error, got [%v]\n", e) 303 | } 304 | e = conn.Disconnect(empty_headers) 305 | if e != nil { 306 | t.Fatalf("TestConnCDEconDiscDone Expected no disconnect error, got [%v]\n", e) 307 | } 308 | _ = closeConn(t, n) 309 | // 310 | e = conn.Disconnect(empty_headers) 311 | if e != ECONBAD { 312 | t.Fatalf("TestConnCDEconDiscDone Previous disconnect expected [%v] got [%v]\n", ECONBAD, e) 313 | } 314 | } 315 | } 316 | 317 | /* 318 | ConnDisc Test: setProtocolLevel 319 | */ 320 | func TestConnCDCDSetProtocolLevel(t *testing.T) { 321 | for _, sp := range Protocols() { 322 | n, _ = openConn(t) 323 | ch := login_headers 324 | ch = headersProtocol(ch, sp) 325 | conn, e = Connect(n, ch) 326 | if e != nil { 327 | t.Fatalf("TestConnCDCDSetProtocolLevel Expected no connect error, got [%v]\n", e) 328 | } // 329 | for i, v := range verChecks { 330 | conn.protocol = SPL_10 // reset 331 | e = conn.setProtocolLevel(v.ch, v.sh) 332 | if e != v.e { 333 | t.Fatalf("TestConnCDCDSetProtocolLevel Verdata Item [%d], expected [%v], got [%v]\n", i, v.e, e) 334 | } 335 | } 336 | // 337 | checkReceived(t, conn, false) 338 | e = conn.Disconnect(empty_headers) 339 | checkDisconnectError(t, e) 340 | _ = closeConn(t, n) 341 | } 342 | } 343 | 344 | /* 345 | ConnDisc Test: connRespData 346 | */ 347 | func TestConnCDRespData(t *testing.T) { 348 | for _, sp := range Protocols() { 349 | n, _ = openConn(t) 350 | ch := login_headers 351 | ch = headersProtocol(ch, sp) 352 | conn, e = Connect(n, ch) 353 | if e != nil { 354 | t.Fatalf("TestConnCDRespData Expected no connect error, got [%v]\n", e) 355 | } 356 | for i, f := range frames { 357 | _, e = connectResponse(f.data) 358 | if e != f.resp { 359 | t.Fatalf("TestConnCDRespData Index [%v], expected [%v], got [%v]\n", i, f.resp, e) 360 | } 361 | } 362 | checkReceived(t, conn, false) 363 | e = conn.Disconnect(empty_headers) 364 | checkDisconnectError(t, e) 365 | _ = closeConn(t, n) 366 | } 367 | } 368 | --------------------------------------------------------------------------------