├── 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 | |
12 | First Author Date
13 | |
14 |
15 | (Commit Count)
16 | |
17 |
18 | Name / E-mail
19 | |
20 |
21 |
22 | |
23 | 2011-10-10
24 | |
25 |
26 | (0240)
27 | |
28 |
29 |
30 | gmallard
31 |
32 | / <allard.guy.m@gmail.com>
33 | |
34 |
35 |
36 | |
37 | 2014-02-02
38 | |
39 |
40 | (0001)
41 | |
42 |
43 |
44 | Kelsey Hightower
45 |
46 | / <kelsey.hightower@gmail.com>
47 | |
48 |
49 |
50 | |
51 | 2014-12-12
52 | |
53 |
54 | (0001)
55 | |
56 |
57 |
58 | Logan Attwood
59 |
60 | / <logan@therounds.ca>
61 | |
62 |
63 |
64 | |
65 | 2015-04-16
66 | |
67 |
68 | (0002)
69 | |
70 |
71 |
72 | Max Garvey
73 |
74 | / <mgarvey@monsooncommerce.com>
75 | |
76 |
77 |
78 | |
79 | 2016-04-11
80 | |
81 |
82 | (0001)
83 | |
84 |
85 |
86 | Joakim Sernbrant
87 |
88 | / <joakim.sernbrant@trioptima.com>
89 | |
90 |
91 |
92 | |
93 | 2016-07-30
94 | |
95 |
96 | (0152)
97 | |
98 |
99 |
100 | Guy M. Allard
101 |
102 | / <allard.guy.m@gmail.com>
103 | |
104 |
105 |
106 | |
107 | 2017-03-01
108 | |
109 |
110 | (0001)
111 | |
112 |
113 |
114 | Dan Corin
115 |
116 | / <danielcorin@users.noreply.github.com>
117 | |
118 |
119 |
120 | |
121 | 2017-05-05
122 | |
123 |
124 | (0001)
125 | |
126 |
127 |
128 | Jason Libbey
129 |
130 | / <jlibbey@uber.com>
131 | |
132 |
133 |
134 | |
135 | 2017-06-05
136 | |
137 |
138 | (0001)
139 | |
140 |
141 |
142 | Dan Corin
143 |
144 | / <dancorin@uber.com>
145 | |
146 |
147 |
148 | |
149 | 2017-11-06
150 | |
151 |
152 | (0006)
153 | |
154 |
155 |
156 | tomsawyer
157 |
158 | / <tomsawyer126@gmail.com>
159 | |
160 |
161 |
162 | |
163 | 2017-11-20
164 | |
165 |
166 | (0001)
167 | |
168 |
169 |
170 | itomsawyer
171 |
172 | / <tomsawyer126@gmail.com>
173 | |
174 |
175 |
176 | |
177 | 2018-03-12
178 | |
179 |
180 | (0001)
181 | |
182 |
183 |
184 | GAOXIANG4
185 |
186 | / <gaoxiang4@kingsoft.com>
187 | |
188 |
189 |
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 | |
32 | Environment Variable Name
33 | |
34 |
35 | Usage
36 | |
37 |
38 |
39 |
40 | |
41 | STOMP_DEST
42 | |
43 |
44 | A destination to be used by the client.
45 | Default: /queue/sng.sample.stomp.destination
46 | |
47 |
48 |
49 |
50 | |
51 | STOMP_HEARTBEATS
52 | |
53 |
54 | For protocol 1.1+, the heart-beat value to be used by the client in the CONNECT frame.
55 | Default: 0,0
56 | |
57 |
58 |
59 |
60 | |
61 | STOMP_HOST
62 | |
63 |
64 | The broker host to connect to.
65 | Default: localhost
66 | |
67 |
68 |
69 |
70 | |
71 | STOMP_LOGIN
72 | |
73 |
74 | The login to be used by the client in the CONNECT frame.
75 | Default: guest
76 | |
77 |
78 |
79 |
80 | |
81 | STOMP_NMSGS
82 | |
83 |
84 | A default nummber of messages to receive or send. Useful for some clients.
85 | Default: 1
86 | |
87 |
88 |
89 |
90 | |
91 | STOMP_PASSCODE
92 | |
93 |
94 | The passcode to be used by the client in the CONNECT frame.
95 | Default: guest
96 | |
97 |
98 |
99 |
100 | |
101 | STOMP_PERSISTENT
102 | |
103 |
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 | |
109 |
110 |
111 |
112 | |
113 | STOMP_PORT
114 | |
115 |
116 | The broker port to connect to.
117 | Default: 61613
118 | |
119 |
120 |
121 |
122 | |
123 | STOMP_PROTOCOL
124 | |
125 |
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 | |
131 |
132 |
133 |
134 | |
135 | STOMP_SUBCHANCAP
136 | |
137 |
138 | Used to possibly override the default capacity of _stompngo_ subscription channels.
139 | Default: 1 (the same as the _stompngo_ default.)
140 | |
141 |
142 |
143 |
144 | |
145 | STOMP_VHOST
146 | |
147 |
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 | |
151 |
152 |
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 | |
165 | Function Name
166 | |
167 |
168 | Usage
169 | |
170 |
171 |
172 |
173 | |
174 | HostAndPort()
175 | |
176 |
177 | This function returns two values, the STOMP_HOST and STOMP_PORT values.
178 | Example:
179 | h, p := senv.HostAndPort()
180 | |
181 |
182 |
183 |
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 |
--------------------------------------------------------------------------------