├── rebar.lock
├── rebar.config
├── rebar3
├── docs
├── images
│ ├── contributing.png
│ ├── erlangzmq_socket.png
│ ├── erlangzmq_entities.png
│ └── erlangzmq_system_map.png
└── architecture.md
├── README.md
├── .gitignore
├── contributors.txt
├── CLA.md
├── HEADER.txt
├── examples
├── pub_connect.erl
├── sub_bind.erl
├── Makefile
├── pull.erl
├── rep_server.erl
├── push.erl
├── pair_client.erl
├── publisher.erl
├── pair_server.erl
├── subscriber.erl
├── resource_server.erl
├── router_with_req.erl
├── req_server.erl
├── router_with_dealer.erl
├── req_client.erl
└── resource_client.erl
├── CONTRIBUTING.md
├── test
├── erlangzmq_socket_test.erl
├── erlangzmq_acceptance_version.erl
├── erlangzmq_lb_test.erl
├── erlangzmq_acceptance_error_handler.erl
├── erlangzmq_acceptance_router_with_req.erl
├── erlangzmq_acceptance_router_with_dealer.erl
├── erlangzmq_subscriptions_test.erl
├── erlangzmq_lbs_test.erl
├── erlangzmq_acceptance_resource_with_req.erl
├── erlangzmq_acceptance_pair.erl
├── erlangzmq_acceptance_push_with_push.erl
├── erlangzmq_acceptance_req_test.erl
├── erlangzmq_command_test.erl
├── erlangzmq_acceptance_rep_test.erl
├── erlangzmq_acceptance_xpub_with_xsub.erl
└── erlangzmq_acceptance_pub_with_sub.erl
├── python-test
├── pull.py
├── publisher.py
├── push.py
├── pair_server.py
├── pair_client.py
├── subscriber.py
├── rep_server.py
├── req_server2.py
├── req_server.py
├── rep_client.py
├── req_client.py
└── dealer_client.py
├── src
├── erlangzmq.app.src
├── erlangzmq_lb.erl
├── erlangzmq_sup.erl
├── erlangzmq_xsub.erl
├── erlangzmq_xpub.erl
├── erlangzmq_pattern.erl
├── erlangzmq_push.erl
├── erlangzmq_bind.erl
├── erlangzmq_subscriptions.erl
├── erlangzmq_lbs.erl
├── erlangzmq_resource.erl
├── erlangzmq_router.erl
├── erlangzmq_pull.erl
├── erlangzmq_dealer.erl
├── erlangzmq_rep.erl
├── erlangzmq_pub.erl
├── erlangzmq.erl
├── erlangzmq_pair.erl
├── erlangzmq_sub.erl
├── erlangzmq_req.erl
├── erlangzmq_socket.erl
└── erlangzmq_command.erl
├── include
└── erlangzmq.hrl
└── README_old.md
/rebar.lock:
--------------------------------------------------------------------------------
1 | [].
2 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | {erl_opts, [debug_info]}.
2 | {deps, []}.
--------------------------------------------------------------------------------
/rebar3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chovencorp/erlangzmq/HEAD/rebar3
--------------------------------------------------------------------------------
/docs/images/contributing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chovencorp/erlangzmq/HEAD/docs/images/contributing.png
--------------------------------------------------------------------------------
/docs/images/erlangzmq_socket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chovencorp/erlangzmq/HEAD/docs/images/erlangzmq_socket.png
--------------------------------------------------------------------------------
/docs/images/erlangzmq_entities.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chovencorp/erlangzmq/HEAD/docs/images/erlangzmq_entities.png
--------------------------------------------------------------------------------
/docs/images/erlangzmq_system_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chovencorp/erlangzmq/HEAD/docs/images/erlangzmq_system_map.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # erlangzmq is now chumak
2 |
3 | `erlangzmq` has been relicensed under Mozilla Public License 2.0 and is now [chumak](https://github.com/chovencorp/chumak).
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .rebar3
3 | _*
4 | .eunit
5 | *.o
6 | *.beam
7 | *.plt
8 | *.swp
9 | *.swo
10 | .erlang.cookie
11 | ebin
12 | log
13 | erl_crash.dump
14 | .rebar
15 | logs
16 | _build
17 | doc
18 |
--------------------------------------------------------------------------------
/contributors.txt:
--------------------------------------------------------------------------------
1 | 1. Place the following phrase *at the top* of the list below on a separate line:
2 |
3 | "I, username, agree to the CLA."
4 |
5 | 2. The `username` must match the *github* username with which you are submitting the pull request.
6 |
7 | Contributors
8 | ============
9 | * I, drozzy, agree to the CLA.
--------------------------------------------------------------------------------
/CLA.md:
--------------------------------------------------------------------------------
1 | Contributor License Agreement (CLA)
2 | ===================================
3 |
4 | By placing your github username in "contributors.txt",
5 | you agree to grant Choven Corp. all copyright to any code
6 | that you contribute to erlangzmq github project, located at:
7 |
8 | https://github.com/chovencorp/erlangzmq
9 |
10 | By submitting a pull request to the `erlangzmq` github project, you agree to
11 | grant Choven Corp. copyright to all the code you contribute.
12 |
13 | All present and future contributions to `erlangzmq` project will fall under this condition.
14 |
15 |
--------------------------------------------------------------------------------
/HEADER.txt:
--------------------------------------------------------------------------------
1 | @copyright 2016 Choven Corp.
2 |
3 | This file is part of erlangzmq.
4 |
5 | erlangzmq is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Affero General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | erlangzmq is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU Affero General Public License for more details.
14 |
15 | You should have received a copy of the GNU Affero General Public License
16 | along with erlangzmq. If not, see
--------------------------------------------------------------------------------
/examples/pub_connect.erl:
--------------------------------------------------------------------------------
1 | %% This examples demonstrates using a pub socket with connect.
2 | %% Make sure to start the sub socket on the other end with a bind.
3 | -module(pub_connect).
4 | -export([main/0]).
5 |
6 | main() ->
7 | application:start(erlangzmq),
8 | {ok, Socket} = erlangzmq:socket(pub),
9 |
10 | case erlangzmq:connect(Socket, tcp, "localhost", 5555) of
11 | {ok, _BindPid} ->
12 | io:format("Binding OK with Pid: ~p\n", [Socket]);
13 | {error, Reason} ->
14 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
15 | X ->
16 | io:format("Unhandled reply for bind ~p \n", [X])
17 | end,
18 | loop(Socket).
19 |
20 | loop(Socket) ->
21 | ok = erlangzmq:send(Socket, <<" ", "Hello world">>),
22 | timer:sleep(1000),
23 | loop(Socket).
--------------------------------------------------------------------------------
/examples/sub_bind.erl:
--------------------------------------------------------------------------------
1 |
2 | %% This examples demonstrates running a sub socket with a bind.
3 | %% Make sure to start the pub socket on the other end with connect.
4 | -module(sub_bind).
5 | -export([main/0]).
6 |
7 | main() ->
8 | application:start(erlangzmq),
9 | {ok, Socket} = erlangzmq:socket(sub),
10 | Topic = <<" ">>,
11 | erlangzmq:subscribe(Socket, Topic),
12 | case erlangzmq:bind(Socket, tcp, "localhost", 5555) of
13 | {ok, _BindPid} ->
14 | io:format("Binding OK with Pid: ~p\n", [Socket]);
15 | {error, Reason} ->
16 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
17 | X ->
18 | io:format("Unhandled reply for bind ~p \n", [X])
19 | end,
20 | loop(Socket).
21 |
22 | loop(Socket) ->
23 | {ok, Data1} = erlangzmq:recv(Socket),
24 | io:format("Received ~p\n", [Data1]),
25 | loop(Socket).
--------------------------------------------------------------------------------
/docs/architecture.md:
--------------------------------------------------------------------------------
1 | Architecture
2 | ============
3 |
4 | This document aims to describe the architecture of the system to make it easier
5 | for contributors to understand its structure and behavior.
6 |
7 | System Overview
8 | ---------------
9 |
10 | The system map below presents an overview of the system.
11 |
12 | 
13 |
14 | Sockets
15 | -------
16 |
17 | Each socket creates a peer process for each remote peer it communicates with.
18 |
19 | 
20 |
21 | Entity Relationship Diagrams
22 | ----------------------------
23 |
24 | Here we can see different types of sockets and also which relationships
25 | the sockets participate in.
26 |
27 | 
28 |
29 | Notation
30 | --------
31 |
32 | The notation used in diagrams follows the [FMC](http://www.fmc-modeling.org/notation_reference) standard.
33 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ============
3 |
4 | Thank you for your interest in contributing to this project. Please follow these steps:
5 |
6 |
7 | 1. Read and understand the contributor license agreement found in [CLA](CLA.md).
8 | 2. If you are a first time contributor, you will need to add your github username to the `contributors.txt` file. See the diagram below for the visual description.
9 |
10 | 
11 |
12 | 3. If you are a returning contributor, just create the pull request as you would normally do. You are still bound by the terms previously agreed to.
13 |
14 | Guidelines
15 | ----------
16 |
17 | 1. Code clarity SHOULD be emphasized over performance.
18 | 2. Code MUST be cross-platform.
19 | 3. Each change SHOULD address some problem. If there is no issue for it, consider creating one.
20 |
21 | Questions?
22 | ----------
23 | If you have any questions, please open an issue or email us at contact@choven.ca.
--------------------------------------------------------------------------------
/test/erlangzmq_socket_test.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_socket_test).
19 |
20 | -include_lib("eunit/include/eunit.hrl").
21 |
22 | init_with_invalid_pattern_test() ->
23 | {stop, Reason} = erlangzmq_socket:init({foo, "my-identity"}),
24 | ?assertEqual(Reason, invalid_socket_type).
25 |
--------------------------------------------------------------------------------
/python-test/pull.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.PULL)
25 | socket.bind("tcp://*:5555")
26 |
27 | for request in range(100):
28 | time.sleep(2)
29 | message = socket.recv()
30 | print("Received reply %s [ %s ]" % (request, message))
31 |
--------------------------------------------------------------------------------
/python-test/publisher.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.PUB)
25 | socket.bind("tcp://*:5555")
26 |
27 | for request in range(100):
28 | message = socket.send("oi")
29 | print("Received reply %s [ %s ]" % (request, message))
30 | time.sleep(1)
31 |
--------------------------------------------------------------------------------
/python-test/push.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.PUSH)
25 | socket.connect("tcp://localhost:5555")
26 |
27 | for request in range(100):
28 | message = socket.send("freedom")
29 | print("send %s [ %s ]" % (request, message))
30 | time.sleep(1)
31 |
--------------------------------------------------------------------------------
/python-test/pair_server.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('PAIR server')
24 | socket = context.socket(zmq.PAIR)
25 | socket.bind("tcp://*:5555")
26 |
27 | for request in range(100):
28 | socket.send("I am a python man!")
29 | message = socket.recv()
30 | print("Recv %d, %s" % (request, message))
31 | time.sleep(1)
32 |
--------------------------------------------------------------------------------
/python-test/pair_client.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 |
20 | import zmq
21 | import time
22 | context = zmq.Context()
23 |
24 | print('PAIR client')
25 | socket = context.socket(zmq.PAIR)
26 | socket.connect("tcp://localhost:5555")
27 |
28 | for request in range(100):
29 | message = socket.recv()
30 | print("Recv %d, %s" % (request, message))
31 | time.sleep(1)
32 | socket.send("I am a python man!")
33 |
--------------------------------------------------------------------------------
/python-test/subscriber.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.SUB)
25 | socket.setsockopt(zmq.SUBSCRIBE, b"")
26 | socket.connect("tcp://localhost:5555")
27 |
28 | for request in range(100):
29 | message = socket.recv()
30 | print("Received reply %s [ %s ]" % (request, message))
31 |
--------------------------------------------------------------------------------
/python-test/rep_server.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.REP)
25 | socket.bind("tcp://*:5555")
26 |
27 |
28 | for request in range(100):
29 | print("Sending request %s" % request)
30 | time.sleep(1)
31 | message = socket.recv()
32 | socket.send(b"Hello1")
33 | print("Received reply %s [ %s ]" % (request, message))
34 |
--------------------------------------------------------------------------------
/examples/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: *.erl
2 |
3 | RUN_ARGS = erl -pa ../_build/default/lib/erlangzmq/ebin -eval
4 |
5 | *.erl:
6 | erlc $@
7 |
8 | req_client: req_client.erl
9 | ${RUN_ARGS} "req_client:main()"
10 |
11 | req_server: req_server.erl
12 | ${RUN_ARGS} "req_server:main()"
13 |
14 | rep_server: rep_server.erl
15 | ${RUN_ARGS} "rep_server:main()"
16 |
17 | router_with_dealer: router_with_dealer.erl
18 | ${RUN_ARGS} "router_with_dealer:main()"
19 |
20 | router_with_req: router_with_req.erl
21 | ${RUN_ARGS} "router_with_req:main()"
22 |
23 | publisher: publisher.erl
24 | ${RUN_ARGS} "publisher:main()"
25 |
26 | subscriber_a: subscriber.erl
27 | ${RUN_ARGS} 'subscriber:main(<<"A">>)'
28 |
29 | subscriber_b: subscriber.erl
30 | ${RUN_ARGS} 'subscriber:main(<<"B">>)'
31 |
32 | push: push.erl
33 | ${RUN_ARGS} "push:main()"
34 |
35 | pull: pull.erl
36 | ${RUN_ARGS} 'pull:main()'
37 |
38 | pair_server: pair_server.erl
39 | ${RUN_ARGS} "pair_server:main()"
40 |
41 | pair_client: pair_client.erl
42 | ${RUN_ARGS} 'pair_client:main()'
43 |
44 | resource_server: resource_server.erl
45 | ${RUN_ARGS} 'resource_server:main()'
46 |
47 | resource_client: resource_client.erl
48 | ${RUN_ARGS} 'resource_client:main()'
49 |
--------------------------------------------------------------------------------
/python-test/req_server2.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.REP)
25 |
26 | socket.bind("tcp://*:5556")
27 |
28 |
29 | while True:
30 | message = socket.recv()
31 | print("Received reply [ %s ]" % message)
32 |
33 | if message == 'delay':
34 | time.sleep(0.2)
35 |
36 | print("Sending response")
37 | socket.send(message)
38 |
39 |
--------------------------------------------------------------------------------
/python-test/req_server.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.REP)
25 | socket.setsockopt(zmq.IDENTITY, b"PEER")
26 |
27 | socket.bind("tcp://*:5555")
28 |
29 |
30 | while True:
31 | message = socket.recv()
32 | print("Received reply [ %s ]" % message)
33 |
34 | if message == 'delay':
35 | time.sleep(0.2)
36 |
37 | print("Sending response")
38 | socket.send(message)
39 |
40 |
--------------------------------------------------------------------------------
/python-test/rep_client.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.REP)
25 | socket.connect("tcp://localhost:5555")
26 |
27 |
28 | for request in range(1):
29 | print("Sending request %s" % request)
30 | time.sleep(1)
31 | socket.send(b"Hello1")
32 | message = socket.recv()
33 | time.sleep(1)
34 | socket.send(b"Hello")
35 | message = socket.recv()
36 | print("Received reply %s [ %s ]" % (request, message))
37 |
--------------------------------------------------------------------------------
/src/erlangzmq.app.src:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | {application, erlangzmq,
19 | [{description, "THIS PROJECT HAS BEEN RENAMED TO chumak."},
20 | {vsn, "1.1.2"},
21 | {registered, []},
22 | {mod, { erlangzmq, []}},
23 | {applications,
24 | [kernel,
25 | stdlib
26 | ]},
27 | {env,[]},
28 | {modules, []},
29 |
30 | {maintainers, ["Andriy Drozdyuk"]},
31 | {licenses, ["AGPL", "Commercial"]},
32 | {links, [
33 | {"Chumak", "https://github.com/chovencorp/chumak"},
34 | {"Github", "https://github.com/chovencorp/erlangzmq"}]}
35 | ]}.
36 |
--------------------------------------------------------------------------------
/python-test/req_client.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.REQ)
25 | socket.connect("tcp://localhost:5555")
26 | #socket.connect("tcp://localhost:5556")
27 |
28 |
29 | for request in range(100):
30 | print("Sending request %s" % request)
31 | time.sleep(1)
32 | socket.send(b"Hello1")
33 | message = socket.recv()
34 | time.sleep(1)
35 | socket.send(b"Hello")
36 | message = socket.recv()
37 | print("Received reply %s [ %s ]" % (request, message))
38 |
--------------------------------------------------------------------------------
/include/erlangzmq.hrl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc Erlang common types for all modules
19 |
20 | -export_type([transport/0, socket_type/0]).
21 |
22 | -type transport() :: tcp.
23 | -type socket_type() :: req | rep |
24 | dealer | router |
25 | pub | xpub |
26 | sub | xsub |
27 | push | pull |
28 | pair.
29 | -define(SOCKET_OPTS(Opts), lists:append([binary, {active, false}, {reuseaddr, true}], Opts)).
30 | -define(GREETINGS_TIMEOUT, 1000).
31 | -define(RECONNECT_TIMEOUT, 2000).
32 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_version.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_version).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | -define(PORT, 3010).
22 |
23 | single_test_() ->
24 | [
25 | {
26 | "Should return valid version when started",
27 | {setup, fun version_error/0, fun start/1, fun version_ok/1}
28 | }
29 | ].
30 |
31 | version_error() ->
32 | ?_assertEqual({error, application_not_started}, erlangzmq:version()).
33 |
34 | start(_) ->
35 | application:ensure_started(erlangzmq).
36 |
37 |
38 | version_ok(_) ->
39 | ?_assertMatch({ok, _}, erlangzmq:version()).
40 |
41 |
--------------------------------------------------------------------------------
/examples/pull.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(pull).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 | {ok, Socket} = erlangzmq:socket(pull),
23 |
24 | case erlangzmq:bind(Socket, tcp, "localhost", 5555) of
25 | {ok, _BindPid} ->
26 | io:format("Binding OK with Pid: ~p\n", [Socket]);
27 | {error, Reason} ->
28 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
29 | X ->
30 | io:format("Unhandled reply for bind ~p \n", [X])
31 | end,
32 | loop(Socket).
33 |
34 | loop(Socket) ->
35 | {ok, Data} = erlangzmq:recv(Socket),
36 | io:format("Received ~p\n", [Data]),
37 | loop(Socket).
38 |
--------------------------------------------------------------------------------
/python-test/dealer_client.py:
--------------------------------------------------------------------------------
1 | """
2 | @copyright 2016 Choven Corp.
3 |
4 | This file is part of erlangzmq.
5 |
6 | erlangzmq is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | erlangzmq is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public License
17 | along with erlangzmq. If not, see
18 | """
19 | import zmq
20 | import time
21 | context = zmq.Context()
22 |
23 | print('Connection to hello world server')
24 | socket = context.socket(zmq.DEALER)
25 | socket.connect("tcp://localhost:5555")
26 | socket.connect("tcp://localhost:5556")
27 |
28 |
29 | for request in range(100):
30 | print("Sending request %s" % request)
31 | socket.send_multipart([b"", b"Hello1"])
32 | socket.send_multipart([b"", b"Hello2"])
33 | socket.send_multipart([b"", b"Hello2"])
34 | message1 = socket.recv_multipart()
35 | message2 = socket.recv_multipart()
36 | message3 = socket.recv_multipart()
37 | time.sleep(1)
38 | print("Received reply %s [ %s ]" % (request, message1))
39 | print("Received reply %s [ %s ]" % (request, message2))
40 |
--------------------------------------------------------------------------------
/examples/rep_server.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(rep_server).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 | {ok, Socket} = erlangzmq:socket(rep, "my-rep"),
23 |
24 | case erlangzmq:bind(Socket, tcp, "localhost", 5555) of
25 | {ok, _BindPid} ->
26 | io:format("Binding OK with Pid: ~p\n", [Socket]);
27 | {error, Reason} ->
28 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
29 | X ->
30 | io:format("Unhandled reply for bind ~p \n", [X])
31 | end,
32 | loop(Socket).
33 |
34 | loop(Socket) ->
35 | Reply = erlangzmq:recv(Socket),
36 | io:format("Question: ~p\n", [Reply]),
37 | erlangzmq:send(Socket, <<"Hello reply">>),
38 | loop(Socket).
39 |
--------------------------------------------------------------------------------
/examples/push.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(push).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 | {ok, Socket} = erlangzmq:socket(push),
23 |
24 | case erlangzmq:connect(Socket, tcp, "localhost", 5555) of
25 | {ok, _BindPid} ->
26 | io:format("Binding OK with Pid: ~p\n", [Socket]);
27 | {error, Reason} ->
28 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
29 | X ->
30 | io:format("Unhandled reply for bind ~p \n", [X])
31 | end,
32 | loop(Socket, 1).
33 |
34 | loop(Socket, Pos) ->
35 | io:format("Send..."),
36 | ok = erlangzmq:send(Socket, <<"Hello A">>),
37 | ok = erlangzmq:send(Socket, <<"Hello B">>),
38 | timer:sleep(1000),
39 | loop(Socket, Pos + 1).
40 |
--------------------------------------------------------------------------------
/examples/pair_client.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(pair_client).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 | {ok, Socket} = erlangzmq:socket(pair),
23 |
24 | case erlangzmq:connect(Socket, tcp, "localhost", 5555) of
25 | {ok, _BindPid} ->
26 | io:format("Connected OK with Pid: ~p\n", [Socket]);
27 | {error, Reason} ->
28 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
29 | X ->
30 | io:format("Unhandled reply for bind ~p \n", [X])
31 | end,
32 | loop(Socket).
33 |
34 | loop(Socket) ->
35 | {ok, Data1} = erlangzmq:recv_multipart(Socket),
36 | io:format("Received ~p\n", [Data1]),
37 | ok = erlangzmq:send_multipart(Socket, [<<"Hey Jude">>]),
38 | loop(Socket).
39 |
--------------------------------------------------------------------------------
/examples/publisher.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(publisher).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 | {ok, Socket} = erlangzmq:socket(pub),
23 |
24 | case erlangzmq:bind(Socket, tcp, "localhost", 5555) of
25 | {ok, _BindPid} ->
26 | io:format("Binding OK with Pid: ~p\n", [Socket]);
27 | {error, Reason} ->
28 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
29 | X ->
30 | io:format("Unhandled reply for bind ~p \n", [X])
31 | end,
32 | loop(Socket, 1).
33 |
34 | loop(Socket, Pos) ->
35 | ok = erlangzmq:send(Socket, <<"A", Pos, "Hello A">>),
36 | ok = erlangzmq:send(Socket, <<"B", Pos, "Hello B">>),
37 | io:format("."),
38 | timer:sleep(1000),
39 | loop(Socket, Pos + 1).
40 |
--------------------------------------------------------------------------------
/examples/pair_server.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(pair_server).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 | {ok, Socket} = erlangzmq:socket(pair),
23 |
24 | case erlangzmq:bind(Socket, tcp, "localhost", 5555) of
25 | {ok, _BindPid} ->
26 | io:format("Binding OK with Pid: ~p\n", [Socket]);
27 | {error, Reason} ->
28 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
29 | X ->
30 | io:format("Unhandled reply for bind ~p \n", [X])
31 | end,
32 | loop(Socket).
33 |
34 | loop(Socket) ->
35 | ok = erlangzmq:send_multipart(Socket, [<<"Hey Jude">>]),
36 | io:format("Sent\n"),
37 | {ok, Data2} = erlangzmq:recv_multipart(Socket),
38 | io:format("Received ~p\n", [Data2]),
39 | loop(Socket).
40 |
--------------------------------------------------------------------------------
/examples/subscriber.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(subscriber).
18 | -export([main/1]).
19 |
20 | main(Topic) ->
21 | application:start(erlangzmq),
22 | {ok, Socket} = erlangzmq:socket(sub),
23 | erlangzmq:subscribe(Socket, Topic),
24 |
25 | case erlangzmq:connect(Socket, tcp, "localhost", 5555) of
26 | {ok, _BindPid} ->
27 | io:format("Binding OK with Pid: ~p\n", [Socket]);
28 | {error, Reason} ->
29 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
30 | X ->
31 | io:format("Unhandled reply for bind ~p \n", [X])
32 | end,
33 | loop(Socket).
34 |
35 | loop(Socket) ->
36 | {ok, Data1} = erlangzmq:recv_multipart(Socket),
37 | io:format("Received by multipart ~p\n", [Data1]),
38 | {ok, Data2} = erlangzmq:recv(Socket),
39 | io:format("Received ~p\n", [Data2]),
40 | loop(Socket).
41 |
--------------------------------------------------------------------------------
/examples/resource_server.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(resource_server).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 | {ok, Resource} = erlangzmq:resource(),
23 | {ok, SocketA} = erlangzmq:socket(rep, "A"),
24 | {ok, SocketB} = erlangzmq:socket(rep, "B"),
25 |
26 | erlangzmq:attach_resource(Resource, "service/a", SocketA),
27 | erlangzmq:attach_resource(Resource, "service/b", SocketB),
28 |
29 | spawn_link(fun () ->
30 | loop(SocketA, <<"Hello A">>)
31 | end),
32 | spawn_link(fun () ->
33 | loop(SocketB, <<"Hello B">>)
34 | end),
35 | {ok, _BindPid} = erlangzmq:bind(Resource, tcp, "localhost", 5555),
36 |
37 | receive
38 | _ -> ok
39 | end.
40 |
41 | loop(Socket, Msg) ->
42 | Data = erlangzmq:recv(Socket),
43 | erlangzmq:send(Socket, <<"Reply from: ", Msg/binary, " is ", Msg/binary>>),
44 | loop(Socket, Msg).
45 |
--------------------------------------------------------------------------------
/src/erlangzmq_lb.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Simple Round-robin load-balancer
19 |
20 | -module(erlangzmq_lb).
21 | -export([new/0, put/2, get/1, delete/2, is_empty/1]).
22 |
23 | -type lb() :: list().
24 |
25 | %% @doc returns an empty load-balancer
26 | -spec new() -> NewLB::lb().
27 | new() ->
28 | [].
29 |
30 | %% @doc put a item to be balanced.
31 | -spec put(LB::lb(), Item::term()) -> NewLB::lb().
32 | put(LB, Item) ->
33 | [Item|LB].
34 |
35 | %% @doc get the next available item from load-balancer, return none if empty.
36 | -spec get(LB::lb()) -> none | {NewLB::lb(), Item::term()}.
37 | get([]) ->
38 | none;
39 | get([Head|Tail]) ->
40 | {Tail ++ [Head], Head}.
41 |
42 | %% @doc remove item from load-balancer
43 | -spec delete(LB::lb(), Item::term()) -> NewLB::lb().
44 | delete(LB, Item)->
45 | lists:delete(Item, LB).
46 |
47 | %% @doc return if true or false this LB is empty
48 | -spec is_empty(LB::lb()) -> true | false.
49 | is_empty([]) -> true;
50 | is_empty(_) -> false.
51 |
--------------------------------------------------------------------------------
/test/erlangzmq_lb_test.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_lb_test).
19 |
20 | -include_lib("eunit/include/eunit.hrl").
21 |
22 | new_test() ->
23 | ?assertEqual(erlangzmq_lb:new(), []).
24 |
25 | put_test() ->
26 | Q1 = erlangzmq_lb:new(),
27 | Q2 = erlangzmq_lb:put(Q1, 1),
28 | ?assertEqual(Q2, [1]),
29 | Q3 = erlangzmq_lb:put(Q2, 3),
30 | ?assertEqual(Q3, [3, 1]).
31 |
32 | get_test() ->
33 | Q1 = erlangzmq_lb:put(
34 | erlangzmq_lb:put(erlangzmq_lb:new(), 1),
35 | 3),
36 | {Q2, 3} = erlangzmq_lb:get(Q1),
37 | {Q3, 1} = erlangzmq_lb:get(Q2),
38 | {Q4, 3} = erlangzmq_lb:get(Q3),
39 | ?assertEqual(Q4, [1, 3]).
40 |
41 | get_empty_test() ->
42 | ?assertEqual(erlangzmq_lb:get(erlangzmq_lb:new()), none).
43 |
44 | delete_test() ->
45 | Q1 = erlangzmq_lb:put(
46 | erlangzmq_lb:put(erlangzmq_lb:new(), 1),
47 | 3),
48 | Q2 = erlangzmq_lb:delete(Q1, 1),
49 | Q3 = erlangzmq_lb:delete(Q1, 3),
50 |
51 | ?assertEqual(Q2, [3]),
52 | ?assertEqual(Q3, [1]).
53 |
--------------------------------------------------------------------------------
/examples/router_with_req.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(router_with_req).
18 | -export([main/0]).
19 |
20 | start_worker(Identity) ->
21 | Parent = self(),
22 | spawn_link(
23 | fun () ->
24 | {ok, Socket} = erlangzmq:socket(req, Identity),
25 | {ok, _PeerPid} = erlangzmq:connect(Socket, tcp, "localhost", 5576),
26 | worker_loop(Socket, Identity, Parent)
27 | end
28 | ).
29 |
30 | worker_loop(Socket, Identity, Parent) ->
31 | erlangzmq:send(Socket, <<"ready">>),
32 | {ok, Message} = erlangzmq:recv(Socket),
33 |
34 | io:format("Message received from router ~p\n", [Message]).
35 |
36 |
37 | main() ->
38 | application:ensure_started(erlangzmq),
39 | {ok, Socket} = erlangzmq:socket(router),
40 | {ok, _BindPid} = erlangzmq:bind(Socket, tcp, "localhost", 5576),
41 |
42 | start_worker("REQ-A"),
43 | start_worker("REQ-B"),
44 | loop(Socket).
45 |
46 |
47 | loop(Socket) ->
48 | {ok, [Identity, <<>>, <<"ready">>]} = erlangzmq:recv_multipart(Socket),
49 | ok = erlangzmq:send_multipart(Socket, [Identity, <<>>, <<"Reply">>]),
50 | loop(Socket).
51 |
--------------------------------------------------------------------------------
/examples/req_server.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(req_server).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 | {ok, Socket} = erlangzmq:socket(req, "my-req"),
23 |
24 | case erlangzmq:bind(Socket, tcp, "localhost", 5555) of
25 | {ok, Pid} ->
26 | send_messages(Socket, [
27 | <<"Hello my dear friend">>,
28 | <<"Hello my old friend">>,
29 | <<"Hello all the things">>
30 | ]);
31 | {error, Reason} ->
32 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
33 | Reply ->
34 | io:format("Unhandled reply for connect ~p \n", [Reply])
35 | end.
36 |
37 | send_messages(Socket, [Message|Messages]) ->
38 | case erlangzmq:send(Socket, Message) of
39 | ok ->
40 | io:format("Send message: ~p\n", [Message]);
41 | {error, Reason} ->
42 | io:format("Failed to send message: ~p, reason: ~p\n", [Message, Reason])
43 | end,
44 | case erlangzmq:recv(Socket) of
45 | {ok, RecvMessage} ->
46 | io:format("Recv message: ~p\n", [RecvMessage]);
47 | {error, RecvReason} ->
48 | io:format("Failed to recv, reason: ~p\n", [RecvReason])
49 | end,
50 | send_messages(Socket, Messages).
51 |
--------------------------------------------------------------------------------
/src/erlangzmq_sup.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_sup).
19 | -behaviour(supervisor).
20 |
21 | -include("erlangzmq.hrl").
22 |
23 | -define(SUPERVISOR_FLAGS, #{strategy => one_for_one}).
24 | -define(CHILD_PROCESS_PREFIX, "erlangzmq_socket_").
25 | -define(SOCKET, erlangzmq_socket).
26 | -define(RESOURCE, erlangzmq_resource).
27 |
28 |
29 | -export([start_link/0, init/1]).
30 | -export([start_socket/1, start_socket/2, start_resource/0]).
31 |
32 |
33 | start_link() ->
34 | supervisor:start_link({local, ?MODULE}, ?MODULE, []).
35 |
36 |
37 | init(_Args) ->
38 | {ok, {?SUPERVISOR_FLAGS, []}}.
39 |
40 | -spec start_socket(Type::socket_type(), Identity::string()) -> {ok, SocketPid::pid()} | {error, Reason::atom()}.
41 | start_socket(Type, Identity) ->
42 | ProcessId = list_to_atom(string:concat(?CHILD_PROCESS_PREFIX, Identity)),
43 | supervisor:start_child(?MODULE, #{
44 | id=>ProcessId,
45 | start=>{?SOCKET, start_link, [Type, Identity]}
46 | }).
47 |
48 | start_socket(Type) ->
49 | %% socket without identity not use supervisor because the identity
50 | %% is used to localize process inside supervisor.
51 | ?SOCKET:start_link(Type, "").
52 |
53 | start_resource() ->
54 | %% Resource not use supervisor yet, because it's needed a identifier
55 | ?RESOURCE:start_link().
56 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_error_handler.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_error_handler).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | -define(PORT, 3010).
22 |
23 | single_test_() ->
24 | [
25 | {
26 | "Should not start connection with invalid servers",
27 | {setup, fun start/0, fun stop/1, fun negotiate_messages/1}
28 | }
29 | ].
30 |
31 | start() ->
32 | application:ensure_started(erlangzmq),
33 | {ok, ServerPid} = erlangzmq:socket(rep),
34 | {ok, _BindPid} = erlangzmq:bind(ServerPid, tcp, "localhost", ?PORT),
35 | ServerPid.
36 |
37 | invalid_client() ->
38 | Parent = self(),
39 | spawn(
40 | fun () ->
41 | process_flag(trap_exit, true),
42 | {ok, Socket} = erlangzmq:socket(push),
43 | {ok, PeerPid} = erlangzmq:connect(Socket, tcp, "localhost", ?PORT),
44 | link(PeerPid), %% to wait modifications
45 | client_loop(Parent, PeerPid)
46 | end
47 | ).
48 |
49 | client_loop(Parent, PeerPid) ->
50 | receive
51 | {'EXIT', PeerPid, {shutdown, Reason}} ->
52 | Parent ! {peer_finish, Reason}
53 | end.
54 |
55 | stop(Pid) ->
56 | gen_server:stop(Pid).
57 |
58 |
59 | negotiate_messages(_ServerPid) ->
60 | invalid_client(),
61 |
62 | Message = wait_for_msg(),
63 |
64 | [
65 | ?_assertEqual(Message, {server_error, "Invalid socket-type push for rep server"})
66 | ].
67 |
68 | wait_for_msg() ->
69 | receive
70 | {peer_finish, Msg} ->
71 | Msg
72 | end.
73 |
--------------------------------------------------------------------------------
/examples/router_with_dealer.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(router_with_dealer).
18 |
19 | -export([main/0]).
20 |
21 |
22 | start_worker(Identity) ->
23 | Parent = self(),
24 | spawn_link(
25 | fun () ->
26 | {ok, Socket} = erlangzmq:socket(dealer, Identity),
27 | {ok, _PeerPid} = erlangzmq:connect(Socket, tcp, "localhost", 5585),
28 | worker_loop(Socket, Identity, Parent)
29 | end
30 | ).
31 |
32 | worker_loop(Socket, Identity, Parent) ->
33 | {ok, Multipart} = erlangzmq:recv_multipart(Socket),
34 | case Multipart of
35 | [<<"EXIT">>] ->
36 | ok;
37 | _ ->
38 | Parent ! {recv, Identity, Multipart}
39 | end.
40 |
41 | main() ->
42 | application:ensure_started(erlangzmq),
43 | {ok, Socket} = erlangzmq:socket(router),
44 | {ok, _BindPid} = erlangzmq:bind(Socket, tcp, "localhost", 5585),
45 |
46 | start_worker("A"),
47 | start_worker("B"),
48 |
49 | timer:sleep(100), %% wait workers to be established
50 |
51 | ok = erlangzmq:send_multipart(Socket, [<<"A">>, <<"My message one">>]),
52 | ok = erlangzmq:send_multipart(Socket, [<<"B">>, <<"My message two">>]),
53 |
54 | ok = erlangzmq:send_multipart(Socket, [<<"A">>, <<"EXIT">>]),
55 | ok = erlangzmq:send_multipart(Socket, [<<"B">>, <<"EXIT">>]),
56 |
57 | MessageA = receive
58 | {recv, "A", MultipartA} ->
59 | MultipartA
60 | end,
61 |
62 | MessageB = receive
63 | {recv, "B", MultipartB} ->
64 | MultipartB
65 | end,
66 | io:format("Received: ~p...~p\n", [MessageA, MessageB]).
67 |
--------------------------------------------------------------------------------
/examples/req_client.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(req_client).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 | {ok, Socket} = erlangzmq:socket(req, "my-req"),
23 |
24 | case erlangzmq:connect(Socket, tcp, "localhost", 5555) of
25 | {ok, Pid} ->
26 | send_messages(Socket, [
27 | <<"Hello my dear friend">>,
28 | <<"Hello my old friend">>,
29 | <<"Hello all the things">>
30 | ]);
31 | {error, Reason} ->
32 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
33 | Reply ->
34 | io:format("Unhandled reply for connect ~p \n", [Reply])
35 | end.
36 |
37 | send_messages(Socket, []) ->
38 | send_messages(Socket, [
39 | <<"Hello my dear friend">>,
40 | <<"Hello my old friend">>,
41 | <<"Hello all the things">>
42 | ]);
43 |
44 | send_messages(Socket, [Message|Messages]) ->
45 | case erlangzmq:send(Socket, Message) of
46 | ok ->
47 | io:format("Send message: ~p\n", [Message]);
48 | {error, Reason} ->
49 | io:format("Failed to send message: ~p, reason: ~p\n", [Message, Reason])
50 | end,
51 | case erlangzmq:recv(Socket) of
52 | {ok, RecvMessage} ->
53 | io:format("Recv message: ~p\n", [RecvMessage]);
54 | {error, RecvReason} ->
55 | io:format("Failed to recv, reason: ~p\n", [RecvReason])
56 | end,
57 | timer:sleep(1000),
58 | send_messages(Socket, Messages).
59 |
--------------------------------------------------------------------------------
/src/erlangzmq_xsub.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ XSub Pattern for Erlang
19 | %%
20 | %% This pattern implement XSub especification
21 | %% from: http://rfc.zeromq.org/spec:29/PUBSUB#toc6
22 |
23 | -module(erlangzmq_xsub).
24 | -define (SUB, erlangzmq_sub).
25 |
26 | -export([valid_peer_type/1, init/1, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, subscribe/2, cancel/2,
30 | peer_reconnected/2, identity/1
31 | ]).
32 |
33 | init(Identity) ->
34 | ?SUB:init(Identity, [xsub]).
35 |
36 | identity(State) -> ?SUB:identity(State).
37 | valid_peer_type(SocketType) -> ?SUB:valid_peer_type(SocketType).
38 | peer_flags(State) -> ?SUB:peer_flags(State).
39 | accept_peer(State, PeerPid) -> ?SUB:accept_peer(State, PeerPid).
40 | peer_ready(State, PeerPid, Identity) -> ?SUB:peer_ready(State, PeerPid, Identity).
41 | send(State, Data, From) -> ?SUB:send(State, Data, From).
42 | recv(State, From) -> ?SUB:recv(State, From).
43 | send_multipart(State, Data, From) -> ?SUB:send_multipart(State, Data, From).
44 | recv_multipart(State, From) -> ?SUB:recv_multipart(State, From).
45 | peer_recv_message(State, Message, From) -> ?SUB:peer_recv_message(State, Message, From).
46 | queue_ready(State, Identity, From) -> ?SUB:queue_ready(State, Identity, From).
47 | peer_disconected(State, PeerPid) -> ?SUB:peer_disconected(State, PeerPid).
48 |
49 | %% Other sub-specific
50 | cancel(State, Topic) -> ?SUB:cancel(State, Topic).
51 | peer_reconnected(State, PeerPid) -> ?SUB:peer_reconnected(State, PeerPid).
52 | subscribe(State, Topic) -> ?SUB:subscribe(State, Topic).
53 |
--------------------------------------------------------------------------------
/src/erlangzmq_xpub.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ XPub Pattern for Erlang
19 | %%
20 | %% This pattern implement XPub especification
21 | %% from: http://rfc.zeromq.org/spec:29/PUBSUB#toc4
22 |
23 | -module(erlangzmq_xpub).
24 | -behaviour(erlangzmq_pattern).
25 | -define(PUB, erlangzmq_pub).
26 | -export([valid_peer_type/1, init/1, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, peer_subscribe/3, peer_cancel_subscribe/3,
30 | identity/1
31 | ]).
32 |
33 | init(Identity) ->
34 | ?PUB:init(Identity, [xpub]).
35 |
36 | identity(State) -> ?PUB:identity(State).
37 | valid_peer_type(SocketType) -> ?PUB:valid_peer_type(SocketType).
38 | peer_flags(State) -> ?PUB:peer_flags(State).
39 | accept_peer(State, PeerPid) -> ?PUB:accept_peer(State, PeerPid).
40 | peer_ready(State, PeerPid, Identity) -> ?PUB:peer_ready(State, PeerPid, Identity).
41 | send(State, Data, From) -> ?PUB:send(State, Data, From).
42 | recv(State, From) -> ?PUB:recv(State, From).
43 | send_multipart(State, Data, From) -> ?PUB:send_multipart(State, Data, From).
44 | recv_multipart(State, From) -> ?PUB:recv_multipart(State, From).
45 | peer_recv_message(State, Message, From) -> ?PUB:peer_recv_message(State, Message, From).
46 | queue_ready(State, Identity, From) -> ?PUB:queue_ready(State, Identity, From).
47 | peer_disconected(State, PeerPid) -> ?PUB:peer_disconected(State, PeerPid).
48 |
49 | %% Pub specific
50 | peer_subscribe(State, PeerPid, Subscription) ->
51 | ?PUB:peer_subscribe(State, PeerPid, Subscription).
52 | peer_cancel_subscribe(State, PeerPid, Subscription) ->
53 | ?PUB:peer_cancel_subscribe(State, PeerPid, Subscription).
54 |
--------------------------------------------------------------------------------
/examples/resource_client.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 | -module(resource_client).
18 | -export([main/0]).
19 |
20 | main() ->
21 | application:start(erlangzmq),
22 |
23 | spawn_link(fun () ->
24 | service("service/a")
25 | end),
26 | spawn_link(fun () ->
27 | service("service/b")
28 | end),
29 | spawn_link(fun () ->
30 | service("service/c")
31 | end),
32 |
33 | receive
34 | _ -> ok
35 | end.
36 |
37 | service(Resource) ->
38 | {ok, Socket} = erlangzmq:socket(req),
39 | case erlangzmq:connect(Socket, tcp, "localhost", 5555, Resource) of
40 | {ok, Pid} ->
41 | send_messages(Socket, []);
42 |
43 | {error, Reason} ->
44 | io:format("Connection Failed for this reason: ~p\n", [Reason]);
45 | Reply ->
46 | io:format("Unhandled reply for connect ~p \n", [Reply])
47 | end.
48 |
49 | send_messages(Socket, []) ->
50 | send_messages(Socket, [
51 | <<"Hello my dear friend">>,
52 | <<"Hello my old friend">>,
53 | <<"Hello all the things">>
54 | ]);
55 |
56 | send_messages(Socket, [Message|Messages]) ->
57 | case erlangzmq:send(Socket, Message) of
58 | ok ->
59 | io:format("Send message: ~p\n", [Message]);
60 | {error, Reason} ->
61 | io:format("Failed to send message: ~p, reason: ~p\n", [Message, Reason])
62 | end,
63 | case erlangzmq:recv(Socket) of
64 | {ok, RecvMessage} ->
65 | io:format("Recv message: ~p\n", [RecvMessage]);
66 | {error, RecvReason} ->
67 | io:format("Failed to recv, reason: ~p\n", [RecvReason])
68 | end,
69 | timer:sleep(1000),
70 | send_messages(Socket, Messages).
71 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_router_with_req.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_router_with_req).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | single_test_() ->
22 | [
23 | {
24 | "Should route message with two REQ",
25 | {setup, fun start/0, fun stop/1, fun negotiate_multiparts/1}
26 | }
27 | ].
28 |
29 | start() ->
30 | application:ensure_started(erlangzmq),
31 | {ok, Socket} = erlangzmq:socket(router),
32 | {ok, _BindPid} = erlangzmq:bind(Socket, tcp, "localhost", 5576),
33 | Socket.
34 |
35 | start_worker(Identity) ->
36 | Parent = self(),
37 | spawn_link(
38 | fun () ->
39 | {ok, Socket} = erlangzmq:socket(req, Identity),
40 | {ok, _PeerPid} = erlangzmq:connect(Socket, tcp, "localhost", 5576),
41 | worker_loop(Socket, Identity, Parent)
42 | end
43 | ).
44 |
45 | worker_loop(Socket, Identity, Parent) ->
46 | erlangzmq:send(Socket, <<"ready">>),
47 | {ok, Message} = erlangzmq:recv(Socket),
48 | Parent ! {recv, Identity, Message}.
49 |
50 |
51 | stop(Pid) ->
52 | gen_server:stop(Pid).
53 |
54 |
55 | negotiate_multiparts(Socket) ->
56 | start_worker("REQ-A"),
57 | start_worker("REQ-B"),
58 | recv_and_send_message(Socket),
59 | timer:sleep(200),
60 | recv_and_send_message(Socket),
61 |
62 | MessageA = receive
63 | {recv, "REQ-A", MultipartA} ->
64 | MultipartA
65 | end,
66 |
67 | MessageB = receive
68 | {recv, "REQ-B", MultipartB} ->
69 | MultipartB
70 | end,
71 |
72 | [
73 | ?_assertEqual(MessageA, <<"Reply: REQ-A">>),
74 | ?_assertEqual(MessageB, <<"Reply: REQ-B">>)
75 | ].
76 |
77 | recv_and_send_message(Socket) ->
78 | {ok, [Identity, <<>>, <<"ready">>]} = erlangzmq:recv_multipart(Socket),
79 | ok = erlangzmq:send_multipart(Socket, [Identity, <<>>, <<"Reply: ", Identity/binary>>]).
80 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_router_with_dealer.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_router_with_dealer).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | single_test_() ->
22 | [
23 | {
24 | "Should route message with two DEALERS",
25 | {setup, fun start/0, fun stop/1, fun negotiate_multiparts/1}
26 | }
27 | ].
28 |
29 | start() ->
30 | application:ensure_started(erlangzmq),
31 | {ok, Socket} = erlangzmq:socket(router),
32 | {ok, _BindPid} = erlangzmq:bind(Socket, tcp, "localhost", 5575),
33 | Socket.
34 |
35 | start_worker(Identity) ->
36 | Parent = self(),
37 | spawn_link(
38 | fun () ->
39 | {ok, Socket} = erlangzmq:socket(dealer, Identity),
40 | {ok, _PeerPid} = erlangzmq:connect(Socket, tcp, "localhost", 5575),
41 | worker_loop(Socket, Identity, Parent)
42 | end
43 | ).
44 |
45 | worker_loop(Socket, Identity, Parent) ->
46 | {ok, Multipart} = erlangzmq:recv_multipart(Socket),
47 | case Multipart of
48 | [<<"EXIT">>] ->
49 | ok;
50 | _ ->
51 | Parent ! {recv, Identity, Multipart}
52 | end.
53 |
54 | stop(Pid) ->
55 | gen_server:stop(Pid).
56 |
57 |
58 | negotiate_multiparts(Socket) ->
59 | start_worker("A"),
60 | start_worker("B"),
61 | timer:sleep(200), %% wait client sockets to be estabilished
62 | ok = erlangzmq:send_multipart(Socket, [<<"A">>, <<"My message one">>]),
63 | ok = erlangzmq:send_multipart(Socket, [<<"B">>, <<"My message two">>]),
64 |
65 | ok = erlangzmq:send_multipart(Socket, [<<"A">>, <<"EXIT">>]),
66 | ok = erlangzmq:send_multipart(Socket, [<<"B">>, <<"EXIT">>]),
67 |
68 | MessageA = receive
69 | {recv, "A", MultipartA} ->
70 | MultipartA
71 | end,
72 |
73 | MessageB = receive
74 | {recv, "B", MultipartB} ->
75 | MultipartB
76 | end,
77 |
78 | [
79 | ?_assertEqual(MessageA, [<<"My message one">>]),
80 | ?_assertEqual(MessageB, [<<"My message two">>])
81 | ].
82 |
--------------------------------------------------------------------------------
/test/erlangzmq_subscriptions_test.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_subscriptions_test).
19 |
20 | -include_lib("eunit/include/eunit.hrl").
21 |
22 | new_test() ->
23 | ?assertEqual(erlangzmq_subscriptions:new(), #{}).
24 |
25 | put_test() ->
26 | S1 = erlangzmq_subscriptions:new(),
27 | S2 = erlangzmq_subscriptions:put(S1, self(), <<"A">>),
28 | ?assertEqual(#{self() => [<<"A">>]}, S2),
29 |
30 | S3 = erlangzmq_subscriptions:put(S2, self(), <<"B">>),
31 | ?assertEqual(#{self() => [<<"A">>, <<"B">>]}, S3).
32 |
33 |
34 | delete_by_peer_and_subscription_test() ->
35 | S1 = erlangzmq_subscriptions:new(),
36 | S2 = erlangzmq_subscriptions:put(S1, self(), <<"A">>),
37 | S3 = erlangzmq_subscriptions:put(S2, self(), <<"B">>),
38 | S4 = erlangzmq_subscriptions:delete(S3, self(), <<"A">>),
39 | S5 = erlangzmq_subscriptions:delete(S3, self(), <<"C">>),
40 | ?assertEqual(#{self() => [<<"B">>]}, S4),
41 | ?assertEqual(#{self() => [<<"A">>, <<"B">>]}, S5).
42 |
43 |
44 | delete_by_peer_test() ->
45 | S1 = erlangzmq_subscriptions:new(),
46 | S2 = erlangzmq_subscriptions:put(S1, self(), <<"A">>),
47 | S3 = erlangzmq_subscriptions:put(S2, self(), <<"B">>),
48 | S4 = erlangzmq_subscriptions:delete(S3, self()),
49 | ?assertEqual(#{}, S4).
50 |
51 | match_test() ->
52 | OtherPid = spawn_link(fun () -> ok end),
53 | S1 = erlangzmq_subscriptions:new(),
54 | S2 = erlangzmq_subscriptions:put(S1, self(), <<"A">>),
55 | S3 = erlangzmq_subscriptions:put(S2, self(), <<"B">>),
56 | S4 = erlangzmq_subscriptions:put(S3, OtherPid, <<"D">>),
57 | S5 = erlangzmq_subscriptions:put(S4, self(), <<"AB">>),
58 | S6 = erlangzmq_subscriptions:put(S5, OtherPid, <<>>),
59 |
60 | M1 = erlangzmq_subscriptions:match(S5, <<"A">>),
61 | M2 = erlangzmq_subscriptions:match(S5, <<"B">>),
62 | M3 = erlangzmq_subscriptions:match(S5, <<"C">>),
63 | M4 = erlangzmq_subscriptions:match(S5, <<"D">>),
64 | M5 = erlangzmq_subscriptions:match(S5, <<"AB">>),
65 | M6 = erlangzmq_subscriptions:match(S6, <<"W">>),
66 |
67 | ?assertEqual([self()], M1),
68 | ?assertEqual([self()], M2),
69 | ?assertEqual([], M3),
70 | ?assertEqual([OtherPid], M4),
71 | ?assertEqual([self()], M5),
72 | ?assertEqual([OtherPid], M6).
73 |
--------------------------------------------------------------------------------
/src/erlangzmq_pattern.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Pattern behaviour for Erlang
19 | %%
20 | %% This behaviour defines all methods that a pattern needs to implement.
21 |
22 | -module(erlangzmq_pattern).
23 | -include("erlangzmq.hrl").
24 |
25 | -export([module/1, error_msg/1]).
26 |
27 | -type pattern_state() :: tuple().
28 | -type module_name() :: erlangzmq_pattern_req.
29 |
30 | -callback valid_peer_type(SocketType::socket_type()) -> valid | invalid.
31 | -callback init(Identity::string()) -> {ok, pattern_state()}.
32 | -callback peer_flags(State::pattern_state()) -> {SocketType::socket_type(), [PeerFlag::term()]}.
33 | -callback accept_peer(State::pattern_state(), PeerPid::pid()) -> Reply::term().
34 | -callback peer_ready(State::pattern_state(), PeerPid::pid(), Identity::binary()) -> Reply::term().
35 | -callback send(State::pattern_state(), Data::binary(), From::term()) -> Reply::term().
36 | -callback recv(State::pattern_state(), From::term()) -> Reply::term().
37 | -callback identity(State::pattern_state()) -> Identity::string().
38 |
39 | %% Multipart support
40 | -callback send_multipart(State::pattern_state(), [Data::binary()], From::term()) -> Reply::term().
41 | -callback recv_multipart(State::pattern_state(), From::term()) -> Reply::term().
42 |
43 | -callback peer_recv_message(State::pattern_state(), Message::erlangzmq_protocol:message(), From::pid()) -> Reply::term().
44 | -callback queue_ready(State::pattern_state(), Identity::string(), From::pid()) -> Reply::term().
45 | -callback peer_disconected(State::pattern_state(), PeerPid::pid()) -> Reply::term().
46 |
47 |
48 | %% @doc find matching pattern for a socket type.
49 | -spec module(SocketType::socket_type()) -> module_name() | {error, invalid_socket_type}.
50 | module(req) -> erlangzmq_req;
51 | module(rep) -> erlangzmq_rep;
52 | module(dealer) -> erlangzmq_dealer;
53 | module(router) -> erlangzmq_router;
54 | module(pub) -> erlangzmq_pub;
55 | module(sub) -> erlangzmq_sub;
56 | module(xpub) -> erlangzmq_xpub;
57 | module(xsub) -> erlangzmq_xsub;
58 | module(push) -> erlangzmq_push;
59 | module(pull) -> erlangzmq_pull;
60 | module(pair) -> erlangzmq_pair;
61 | module(_) -> {error, invalid_socket_type}.
62 |
63 | %% @doc helper to translate reason atom to human-readable
64 | error_msg(efsm) ->
65 | "operation cannot be performed on this socket at the moment".
66 |
--------------------------------------------------------------------------------
/src/erlangzmq_push.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Push Pattern for Erlang
19 | %%
20 | %% This pattern implement Push especification
21 | %% from: http://rfc.zeromq.org/spec:30/PIPELINE#toc3
22 |
23 | -module(erlangzmq_push).
24 | -behaviour(erlangzmq_pattern).
25 |
26 | -export([valid_peer_type/1, init/1, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, identity/1
30 | ]).
31 |
32 | -record(erlangzmq_push, {
33 | identity :: string(),
34 | lb :: list()
35 | }).
36 |
37 | valid_peer_type(pull) -> valid;
38 | valid_peer_type(_) -> invalid.
39 |
40 | init(Identity) ->
41 | State = #erlangzmq_push{
42 | identity=Identity,
43 | lb=erlangzmq_lb:new()
44 | },
45 | {ok, State}.
46 |
47 | identity(#erlangzmq_push{identity=Identity}) -> Identity.
48 |
49 | peer_flags(_State) ->
50 | {push, []}.
51 |
52 | accept_peer(State, PeerPid) ->
53 | NewLb = erlangzmq_lb:put(State#erlangzmq_push.lb, PeerPid),
54 | {reply, {ok, PeerPid}, State#erlangzmq_push{lb=NewLb}}.
55 |
56 | peer_ready(State, _PeerPid, _Identity) ->
57 | {noreply, State}.
58 |
59 | send(State, Data, From) ->
60 | send_multipart(State, [Data], From).
61 |
62 | recv(State, From) ->
63 | recv_multipart(State, From).
64 |
65 | send_multipart(#erlangzmq_push{lb=LB}=State, Multipart, From) ->
66 | Traffic = erlangzmq_protocol:encode_message_multipart(Multipart),
67 |
68 | case erlangzmq_lb:get(LB) of
69 | none ->
70 | {reply, {error, no_connected_peers}, State};
71 | {NewLB, PeerPid} ->
72 | erlangzmq_peer:send(PeerPid, Traffic, From),
73 | {noreply, State#erlangzmq_push{lb=NewLB}}
74 | end.
75 |
76 | recv_multipart(State, _From) ->
77 | {reply, {error, not_use}, State}.
78 |
79 | peer_recv_message(State, _Message, _From) ->
80 | %% This function will never called, because use PUSH not receive messages
81 | {noreply, State}.
82 |
83 | queue_ready(State, _Identity, _PeerPid) ->
84 | %% This function will never called, because use PUB not receive messages
85 | {noreply, State}.
86 |
87 | peer_disconected(#erlangzmq_push{lb=LB}=State, PeerPid) ->
88 | NewLB = erlangzmq_lb:delete(LB, PeerPid),
89 | {noreply, State#erlangzmq_push{lb=NewLB}}.
90 |
--------------------------------------------------------------------------------
/test/erlangzmq_lbs_test.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_lbs_test).
19 |
20 | -include_lib("eunit/include/eunit.hrl").
21 |
22 | new_test() ->
23 | ?assertEqual(erlangzmq_lbs:new(), {lbs, #{}, #{}}).
24 |
25 | put_test() ->
26 | Q1 = erlangzmq_lbs:new(),
27 | Q2 = erlangzmq_lbs:put(Q1, "A", 1),
28 | ?assertEqual(Q2, {lbs,
29 | #{"A" => [1]},
30 | #{1 => "A"}}
31 | ),
32 |
33 | Q3 = erlangzmq_lbs:put(Q2, "B", 3),
34 | ?assertEqual(Q3, {lbs,
35 | #{"A" => [1], "B" => [3]},
36 | #{1 => "A", 3 => "B"}}
37 | ).
38 |
39 | get_test() ->
40 | Q1 = put_items(erlangzmq_lbs:new(), [
41 | {a, 1},
42 | {b, 2},
43 | {a, 3},
44 | {b, 4},
45 | {c, 6}
46 | ]),
47 | {Q2, 3} = erlangzmq_lbs:get(Q1, a),
48 | {Q3, 1} = erlangzmq_lbs:get(Q2, a),
49 | {Q4, 3} = erlangzmq_lbs:get(Q3, a),
50 | {Q5, 4} = erlangzmq_lbs:get(Q4, b),
51 | {Q6, 2} = erlangzmq_lbs:get(Q5, b),
52 | {Q7, 6} = erlangzmq_lbs:get(Q6, c),
53 | ?assertEqual(Q7, {
54 | lbs,
55 | #{a => [1,3], b => [4,2], c => [6]},
56 | #{1 => a, 2 => b, 3 => a, 4 => b, 6 => c}
57 | }).
58 |
59 | get_empty_test() ->
60 | ?assertEqual(erlangzmq_lbs:get(erlangzmq_lbs:new(), a), none).
61 |
62 | delete_test() ->
63 | Q1 = put_items(erlangzmq_lbs:new(), [
64 | {a, 1},
65 | {b, 2},
66 | {b, 3}
67 | ]),
68 | Q2 = erlangzmq_lbs:delete(Q1, 1),
69 | Q3 = erlangzmq_lbs:delete(Q1, 3),
70 | Q4 = erlangzmq_lbs:delete(Q3, 2),
71 | Q5 = erlangzmq_lbs:delete(Q4, 1),
72 |
73 | ?assertEqual({lbs, #{b => [3,2]}, #{2 => b, 3 => b}}, Q2),
74 | ?assertEqual({lbs, #{a => [1],b => [2]}, #{1 => a, 2 => b}}, Q3),
75 | ?assertEqual({lbs, #{a => [1]}, #{1 => a}}, Q4),
76 | ?assertEqual({lbs, #{}, #{}}, Q5).
77 |
78 | put_items(LBs, []) ->
79 | LBs;
80 | put_items(LBs, [{Key, Value} | Tail]) ->
81 | NewLBs = erlangzmq_lbs:put(LBs, Key, Value),
82 | put_items(NewLBs, Tail).
83 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_resource_with_req.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_resource_with_req).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | -define(PORT, 2710).
22 |
23 | single_test_() ->
24 | [
25 | {
26 | "Should route message with two distinct servers",
27 | {setup, fun start/0, fun stop/1, fun negotiate_messages/1}
28 | }
29 | ].
30 |
31 | start() ->
32 | application:ensure_started(erlangzmq),
33 | {ok, ResourceServerPid} = erlangzmq:resource(),
34 | {ok, _BindPid} = erlangzmq:bind(ResourceServerPid, tcp, "localhost", ?PORT),
35 | ResourceServerPid.
36 |
37 | rep_server(ResourceServer, Resource, Reply) ->
38 | spawn_link(
39 | fun () ->
40 | {ok, Socket} = erlangzmq:socket(rep),
41 | erlangzmq:attach_resource(ResourceServer, Resource, Socket),
42 | rep_server_loop(Socket, Reply)
43 | end
44 | ).
45 |
46 | rep_server_loop(Socket, Reply) ->
47 | {ok, Data} = erlangzmq:recv(Socket),
48 | erlangzmq:send(Socket, <>),
49 | rep_server_loop(Socket, Reply).
50 |
51 |
52 | start_req_client(Identity, Resource, SendMsg) ->
53 | Parent = self(),
54 | spawn_link(
55 | fun () ->
56 | {ok, Socket} = erlangzmq:socket(req),
57 | {ok, _PeerPid} = erlangzmq:connect(Socket, tcp, "localhost", ?PORT, Resource),
58 | erlangzmq:send(Socket, SendMsg),
59 | {ok, Message} = erlangzmq:recv(Socket),
60 | Parent ! {req_recv, Identity, Message}
61 | end
62 | ).
63 |
64 |
65 | stop(Pid) ->
66 | gen_server:stop(Pid).
67 |
68 |
69 | negotiate_messages(ResourceServerPid) ->
70 | rep_server(ResourceServerPid, "service/a", <<"Hello, I am the red ranger">>),
71 | rep_server(ResourceServerPid, "service/b", <<"Hello, I am the black ranger">>),
72 |
73 | timer:sleep(200),
74 |
75 | start_req_client("A", "service/a", <<"I am ready to rock">>),
76 | start_req_client("B", "service/b", <<"I am ready to beat">>),
77 |
78 | MessageA = wait_for_msg("A"),
79 | MessageB = wait_for_msg("B"),
80 |
81 | [
82 | ?_assertEqual(MessageA, <<"Hello, I am the red ranger, I am ready to rock">>),
83 | ?_assertEqual(MessageB, <<"Hello, I am the black ranger, I am ready to beat">>)
84 | ].
85 |
86 | wait_for_msg(Id) ->
87 | receive
88 | {req_recv, Id, Message} ->
89 | Message
90 | end.
91 |
--------------------------------------------------------------------------------
/src/erlangzmq_bind.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Listener for new connections.
19 | -module(erlangzmq_bind).
20 | -include("erlangzmq.hrl").
21 |
22 | -export([start_link/2, listener/2]).
23 |
24 | -spec start_link(Host::string(), Port::number()) -> {ok, BindPid::pid()} | {error, Reason::term()}.
25 | start_link(Host, Port) ->
26 | ParentPid = self(),
27 |
28 | case inet:getaddr(Host, inet) of
29 | {ok, Addr} ->
30 | case gen_tcp:listen(Port, ?SOCKET_OPTS([{ip, Addr}])) of
31 | {ok, ListenSocket} ->
32 | Pid = spawn_link(?MODULE, listener, [ListenSocket, ParentPid]),
33 | {ok, Pid};
34 | {error, Reason} ->
35 | error_logger:error_report([
36 | bind_error,
37 | {host, Host},
38 | {addr, Addr},
39 | {port, Port},
40 | listen_error,
41 | {error, Reason}
42 | ]),
43 | {error, Reason}
44 | end;
45 |
46 | {error, IpReason} ->
47 | error_logger:error_report([
48 | bind_error,
49 | {host, Host},
50 | getaddr_error,
51 | {error, IpReason}
52 | ]),
53 | {error, IpReason}
54 | end.
55 |
56 |
57 | listener(ListenSocket, ParentPid) ->
58 | try
59 | {ok, Socket} = gen_tcp:accept(ListenSocket),
60 | {ok, PeerPid} = gen_server:call(ParentPid, {accept, Socket}),
61 | ok = gen_tcp:controlling_process(Socket, PeerPid),
62 | %% Start to negociate greetings after new process is owner
63 | gen_server:cast(PeerPid, negotiate_greetings),
64 | listener(ListenSocket, ParentPid)
65 | catch
66 | error:{badmatch, {error, closed}} ->
67 | error_logger:info_report({bind_closed});
68 | error:{badmatch, Error} ->
69 | error_logger:error_report([
70 | accept_error,
71 | {error, Error}
72 | ]),
73 | listener(ListenSocket, ParentPid)
74 | end.
75 |
--------------------------------------------------------------------------------
/src/erlangzmq_subscriptions.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Subscription manager
19 |
20 | -module(erlangzmq_subscriptions).
21 | -export([new/0, put/3, delete/2, delete/3, match/2]).
22 |
23 |
24 | -type subscriptions() :: #{PeerPid::pid => [Subscription::binary()]}.
25 |
26 | %% @doc returns new subscriptions
27 | -spec new() -> NewSubscriptions::subscriptions().
28 | new() ->
29 | #{}.
30 |
31 | %% @doc put a new subscription
32 | -spec put(Subscriptions1::subscriptions(), PeerPid::pid(), Subscription::binary()) -> Subscriptions2::subscriptions().
33 | put(Subscriptions, PeerPid, Subscription) ->
34 | PeerSubscriptions = maps:get(PeerPid, Subscriptions, []) ++ [Subscription],
35 | maps:put(PeerPid, PeerSubscriptions, Subscriptions).
36 |
37 | %% @doc delete a new subscription by peer-pid and subscription
38 | -spec delete(Subscriptions1::subscriptions(), PeerPid::pid(), Subscription::binary()) -> Subscriptions2::subscriptions().
39 | delete(Subscriptions, PeerPid, Subscription) ->
40 | PeerSubscriptions1 = maps:get(PeerPid, Subscriptions, []),
41 | PeerSubscriptions2 = lists:delete(Subscription, PeerSubscriptions1),
42 |
43 | case PeerSubscriptions2 of
44 | [] ->
45 | maps:remove(PeerPid, Subscriptions);
46 | _ ->
47 | maps:put(PeerPid, PeerSubscriptions2, Subscriptions)
48 | end.
49 |
50 | %% @doc delete a new subscription by peer-pid
51 | -spec delete(Subscriptions1::subscriptions(), PeerPid::pid()) -> Subscriptions2::subscriptions().
52 | delete(Subscriptions, PeerPid) ->
53 | maps:remove(PeerPid, Subscriptions).
54 |
55 | %% @doc return the list of peers that matches with subscription
56 | -spec match(Subscriptions::subscriptions(), FirstPart::binary()) -> [PeerPid::pid()].
57 | match(Subscriptions, FirstPart) ->
58 | PeerPids = maps:keys(Subscriptions),
59 | lists:filter(fun (PeerPid) ->
60 | peer_match(Subscriptions, PeerPid, FirstPart)
61 | end, PeerPids).
62 |
63 | %% Private API
64 | peer_match(Subscriptions, PeerPid, FirstPart) ->
65 | PeerSubscriptions = maps:get(PeerPid, Subscriptions, []),
66 | lists:any(fun (<<>>) ->
67 | true;
68 |
69 | (PeerSubscription) ->
70 | case binary:match(FirstPart, PeerSubscription) of
71 | nomatch ->
72 | false;
73 | _ ->
74 | true
75 | end
76 | end, PeerSubscriptions).
77 |
--------------------------------------------------------------------------------
/src/erlangzmq_lbs.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Round-robin load-balancer based on an identifier
19 |
20 | -module(erlangzmq_lbs).
21 | -export([new/0, put/3, get/2, delete/2]).
22 |
23 | -record(lbs, {
24 | map :: map(), %% map of load balancers
25 | xref %% reverse reference useful to locate identity at deletion
26 | }).
27 | -type lbs() :: #lbs{}.
28 |
29 |
30 | %% @doc returns an empty load-balancer by identifier
31 | -spec new() -> NewLBs::lbs().
32 | new() ->
33 | #lbs{map=#{}, xref=#{}}.
34 |
35 | %% @doc put a item to be balanced.
36 | -spec put(LBs::lbs(), Identifier::term(), Item::term()) -> NewLB::lbs().
37 | put(#lbs{map=LBMap, xref=XRef}=LBs, Identifier, Item) ->
38 | LB = get_sub_lb(LBMap, Identifier),
39 | NewLB = erlangzmq_lb:put(LB, Item),
40 |
41 | NewLBMap = LBMap#{Identifier => NewLB},
42 | NewXRef = XRef#{Item => Identifier},
43 |
44 | LBs#lbs{map=NewLBMap, xref=NewXRef}.
45 |
46 | %% @doc get the next available item from load-balancer, return none if empty.
47 | -spec get(LBs::lbs(), Identifier::term()) -> none | {NewLBs::lbs(), Item::term()}.
48 | get(#lbs{map=LBMap}=LBs, Identifier) ->
49 | LB = get_sub_lb(LBMap, Identifier),
50 |
51 | case erlangzmq_lb:get(LB) of
52 | {NewLB, Item} ->
53 | NewLBMap = LBMap#{Identifier => NewLB},
54 | NewLBs = LBs#lbs{map=NewLBMap},
55 | {NewLBs, Item};
56 |
57 | none ->
58 | none
59 | end.
60 |
61 | %% @doc remove item from load-balancer
62 | -spec delete(LBs::lbs(), Item::term()) -> NewLBs::lbs().
63 | delete(#lbs{xref=LBXRef}=LBs, Item)->
64 | case maps:find(Item, LBXRef) of
65 | {ok, Identifier} ->
66 | delete_by_identifier(LBs, Identifier, Item);
67 | error ->
68 | LBs
69 | end.
70 |
71 | %% Private API
72 | delete_by_identifier(#lbs{map=LBMap, xref=LBXRef}=LBs, Identifier, Item) ->
73 | LB = get_sub_lb(LBMap, Identifier),
74 | NewLB = erlangzmq_lb:delete(LB, Item),
75 |
76 | NewLBMap = case erlangzmq_lb:is_empty(NewLB) of
77 | true ->
78 | maps:remove(Identifier, LBMap);
79 | false ->
80 | LBMap#{Identifier => NewLB}
81 | end,
82 |
83 | NewXRef = maps:remove(Item, LBXRef),
84 | LBs#lbs{map=NewLBMap, xref=NewXRef}.
85 |
86 | get_sub_lb(LbsMap, Identity) ->
87 | case maps:find(Identity, LbsMap) of
88 | {ok, X} ->
89 | X;
90 | error ->
91 | erlangzmq_lb:new()
92 | end.
93 |
--------------------------------------------------------------------------------
/src/erlangzmq_resource.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Resource Router implementation for Erlang
19 | %% @hidden
20 |
21 | -module(erlangzmq_resource).
22 | -behaviour(gen_server).
23 |
24 | %% api behaviour
25 | -export([start_link/0]).
26 |
27 | %% gen_server behaviors
28 | -export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, init/1, terminate/2]).
29 |
30 | %% public API implementation
31 | -spec start_link() -> {ok, Pid::pid()} | {error, Reason::term()}.
32 | start_link() ->
33 | gen_server:start_link(?MODULE, {}, []).
34 |
35 |
36 | -record(state, {
37 | resources :: map()
38 | }).
39 |
40 | %% gen_server implementation
41 | init(_Args) ->
42 | process_flag(trap_exit, true),
43 | State = #state{
44 | resources=#{}
45 | },
46 | {ok, State}.
47 |
48 | code_change(_OldVsn, State, _Extra) ->
49 | {ok, State}.
50 |
51 | handle_call({accept, SocketPid}, _From, State) ->
52 | case erlangzmq_peer:accept(none, SocketPid, [multi_socket_type]) of
53 | {ok, Pid} ->
54 | {reply, {ok, Pid}, State};
55 | {error, Reason} ->
56 | {reply, {error, Reason}, State}
57 | end;
58 |
59 | handle_call({bind, tcp, Host, Port}, _From, State) ->
60 | Reply = erlangzmq_bind:start_link(Host, Port),
61 | {reply, Reply, State};
62 |
63 | handle_call({bind, Protocol, _Host, _Port}, _From, State) ->
64 | {reply, {error, {unsupported_protocol, Protocol}}, State};
65 |
66 | handle_call({route_resource, Resource}, _From, #state{resources=Resources}=State) ->
67 | case maps:find(Resource, Resources) of
68 | {ok, NewSocket} ->
69 | Flags = gen_server:call(NewSocket, get_flags),
70 | {reply, {change_socket, NewSocket, Flags}, State};
71 | error ->
72 | {reply, close, State}
73 | end.
74 |
75 | handle_cast({attach, Resource, SocketPid}, #state{resources=Resources}=State) ->
76 | NewResources = Resources#{Resource => SocketPid},
77 | {noreply, State#state{resources=NewResources}};
78 |
79 | handle_cast(CastMsg, State) ->
80 | error_logger:info_report([
81 | unhandled_handle_cast,
82 | {module, ?MODULE},
83 | {msg, CastMsg}
84 | ]),
85 | {noreply, State}.
86 |
87 | handle_info({'EXIT', _Pid, {shutdown, invalid_resource}}, State) ->
88 | {noreply, State};
89 |
90 | handle_info(InfoMsg, State) ->
91 | error_logger:info_report([
92 | unhandled_handle_info,
93 | {module, ?MODULE},
94 | {msg, InfoMsg}
95 | ]),
96 | {noreply, State}.
97 |
98 | terminate(_Reason, _State) ->
99 | %% TODO: close all resources
100 | ok.
101 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_pair.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_pair).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | -define(PORT, 5587).
22 |
23 | normal_test_() ->
24 | [
25 | {
26 | "Should deliver message for the paired peer",
27 | {setup, fun start/0, fun stop/1, fun negotiate_without_multipart/1}
28 | }
29 | , {
30 | "Should deliver message for the paired peer using multipart",
31 | {setup, fun start/0, fun stop/1, fun negotiate_with_multipart/1}
32 | }
33 | , {
34 | "Should deliver message for the paired peer without timeout",
35 | {setup, fun start/0, fun stop/1, fun negotiate_without_timeout/1}
36 | }
37 | , {
38 | "Should deliver message for the paired peer with delayed message",
39 | {setup, fun start/0, fun stop/1, fun negotiate_with_delay/1}
40 | }
41 | ].
42 |
43 | start() ->
44 | application:ensure_started(erlangzmq),
45 | {ok, Socket} = erlangzmq:socket(pair),
46 | {ok, _BindPid} = erlangzmq:bind(Socket, tcp, "localhost", ?PORT),
47 | Socket.
48 |
49 | start_worker(Identity, Func) ->
50 | Parent = self(),
51 | spawn_link(
52 | fun () ->
53 | {ok, Socket} = erlangzmq:socket(pair, Identity),
54 | {ok, _PeerPid} = erlangzmq:connect(Socket, tcp, "localhost", ?PORT),
55 | Func(Socket, Identity, Parent)
56 | end
57 | ).
58 |
59 | stop(Pid) ->
60 | gen_server:stop(Pid).
61 |
62 | negotiate_without_multipart(Socket) ->
63 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
64 | {ok, Message} = erlangzmq:recv(ClientSocket),
65 | Parent ! {recv, Identity, Message}
66 | end,
67 | start_worker("PAIR-A", NegociateFunc),
68 | timer:sleep(200),
69 | ok = erlangzmq:send(Socket, <<"Hey brother">>),
70 |
71 | Message = receive
72 | {recv, "PAIR-A", MultipartA} ->
73 | MultipartA
74 | end,
75 | [
76 | ?_assertEqual(Message, <<"Hey brother">>)
77 | ].
78 |
79 | negotiate_with_multipart(Socket) ->
80 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
81 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
82 | Parent ! {recv, Identity, Message}
83 | end,
84 | start_worker("PAIR-B", NegociateFunc),
85 | timer:sleep(200),
86 | ok = erlangzmq:send_multipart(Socket, [<<"Hey">>, <<"Jude">>]),
87 |
88 | Message = receive
89 | {recv, "PAIR-B", MultipartA} ->
90 | MultipartA
91 | end,
92 | [
93 | ?_assertEqual(Message, [<<"Hey">>, <<"Jude">>])
94 | ].
95 |
96 | negotiate_without_timeout(Socket) ->
97 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
98 | {ok, Message} = erlangzmq:recv(ClientSocket),
99 | Parent ! {recv, Identity, Message}
100 | end,
101 | start_worker("PAIR-C", NegociateFunc),
102 | ok = erlangzmq:send(Socket, <<"Hey mother">>),
103 |
104 | Message = receive
105 | {recv, "PAIR-C", MultipartA} ->
106 | MultipartA
107 | end,
108 | [
109 | ?_assertEqual(Message, <<"Hey mother">>)
110 | ].
111 |
112 | negotiate_with_delay(Socket) ->
113 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
114 | timer:sleep(200),
115 | {ok, Message} = erlangzmq:recv(ClientSocket),
116 | Parent ! {recv, Identity, Message}
117 | end,
118 | start_worker("PAIR-D", NegociateFunc),
119 | ok = erlangzmq:send(Socket, <<"Hey father">>),
120 |
121 | Message = receive
122 | {recv, "PAIR-D", MultipartA} ->
123 | MultipartA
124 | end,
125 | [
126 | ?_assertEqual(Message, <<"Hey father">>)
127 | ].
128 |
--------------------------------------------------------------------------------
/src/erlangzmq_router.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Router Pattern for Erlang
19 | %%
20 | %% This pattern implement Router especification
21 | %% from: http://rfc.zeromq.org/spec:28/REQREP#toc6
22 |
23 | -module(erlangzmq_router).
24 | -behaviour(erlangzmq_pattern).
25 |
26 | -export([valid_peer_type/1, init/1, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, identity/1]).
30 |
31 | -record(erlangzmq_router, {
32 | identity :: string(),
33 | lbs, %% loadbalancers based on identity
34 | pending_recv :: nil | {from, From::term()},
35 | recv_queue :: queue:queue()
36 | }).
37 |
38 | valid_peer_type(req) -> valid;
39 | valid_peer_type(router) -> valid;
40 | valid_peer_type(dealer) -> valid;
41 | valid_peer_type(_) -> invalid.
42 |
43 | init(Identity) ->
44 | State = #erlangzmq_router{
45 | identity=Identity,
46 | lbs=erlangzmq_lbs:new(),
47 | recv_queue=queue:new(),
48 | pending_recv=nil
49 | },
50 | {ok, State}.
51 |
52 | identity(#erlangzmq_router{identity=I}) -> I.
53 |
54 | peer_flags(_State) ->
55 | {router, [incomming_queue]}.
56 |
57 | accept_peer(State, PeerPid) ->
58 | {reply, {ok, PeerPid}, State}.
59 |
60 | peer_ready(#erlangzmq_router{lbs=LBs}=State, PeerPid, Identity) ->
61 | NewLBs = erlangzmq_lbs:put(LBs, Identity, PeerPid),
62 | {noreply, State#erlangzmq_router{lbs=NewLBs}}.
63 |
64 | send(State, _Data, _From) ->
65 | {reply, {error, send_implemented_yet}, State}.
66 |
67 | recv(State, _From) ->
68 | {reply, {error, recv_implemented_yet}, State}.
69 |
70 | send_multipart(#erlangzmq_router{lbs=LBs}=State, Multipart, _From) when length(Multipart) >= 2 ->
71 | [Identity|RemmaingMultipart] = Multipart,
72 | case erlangzmq_lbs:get(LBs, binary_to_list(Identity)) of
73 | {NewLBs, PeerPid} ->
74 | Traffic = erlangzmq_protocol:encode_message_multipart(RemmaingMultipart),
75 | erlangzmq_peer:send(PeerPid, Traffic),
76 | {reply, ok, State#erlangzmq_router{lbs=NewLBs}};
77 |
78 | none ->
79 | {reply, {error, no_peers}, State}
80 | end;
81 |
82 | send_multipart(State, _Multipart, _From) ->
83 | {reply, {error, identity_missing}, State}.
84 |
85 | recv_multipart(#erlangzmq_router{recv_queue=RecvQueue, pending_recv=nil}=State, From) ->
86 | case queue:out(RecvQueue) of
87 | {{value, Multipart}, NewRecvQueue} ->
88 | {reply, {ok, Multipart}, State#erlangzmq_router{recv_queue=NewRecvQueue}};
89 | {empty, _RecvQueue} ->
90 | {noreply, State#erlangzmq_router{pending_recv={from, From}}}
91 | end;
92 | recv_multipart(State, _From) ->
93 | {reply, {error, efsm}, State}.
94 |
95 | peer_recv_message(State, _Message, _From) ->
96 | %% This function will never called, because use incomming_queue property
97 | {noreply, State}.
98 |
99 | queue_ready(#erlangzmq_router{recv_queue=RecvQueue, pending_recv=nil}=State, Identity, PeerPid) ->
100 | MultiPart = recv_message(Identity, PeerPid),
101 | NewRecvQueue = queue:in(MultiPart, RecvQueue),
102 | {noreply, State#erlangzmq_router{recv_queue=NewRecvQueue}};
103 |
104 | queue_ready(#erlangzmq_router{pending_recv={from, PendingRecv}}=State, Identity, PeerPid) ->
105 | MultiPart = recv_message(Identity, PeerPid),
106 | gen_server:reply(PendingRecv, {ok, MultiPart}),
107 | {noreply, State#erlangzmq_router{pending_recv=nil}}.
108 |
109 | peer_disconected(#erlangzmq_router{lbs=LBs}=State, PeerPid) ->
110 | NewLBs = erlangzmq_lb:delete(LBs, PeerPid),
111 | {noreply, State#erlangzmq_router{lbs=NewLBs}}.
112 |
113 | recv_message(Identity, PeerPid) ->
114 | IdentityBin = list_to_binary(Identity),
115 | {out, Multipart} = erlangzmq_peer:incomming_queue_out(PeerPid),
116 | [IdentityBin | Multipart].
117 |
--------------------------------------------------------------------------------
/src/erlangzmq_pull.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Pull Pattern for Erlang
19 | %%
20 | %% This pattern implement Pull especification
21 | %% from: http://rfc.zeromq.org/spec:30/PIPELINE#toc4
22 |
23 | -module(erlangzmq_pull).
24 | -behaviour(erlangzmq_pattern).
25 |
26 | -export([valid_peer_type/1, init/1, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, identity/1
30 | ]).
31 |
32 | -record(erlangzmq_pull, {
33 | identity :: string(),
34 | pending_recv :: nil | {from, From::term()},
35 | pending_recv_multipart :: nil | {from, From::term()},
36 | recv_queue :: queue:queue()
37 | }).
38 |
39 | valid_peer_type(push) -> valid;
40 | valid_peer_type(_) -> invalid.
41 |
42 | init(Identity) ->
43 | State = #erlangzmq_pull{
44 | identity=Identity,
45 | recv_queue=queue:new(),
46 | pending_recv=nil,
47 | pending_recv_multipart=nil
48 | },
49 | {ok, State}.
50 |
51 | identity(#erlangzmq_pull{identity=Identity}) -> Identity.
52 |
53 | peer_flags(_State) ->
54 | {pull, [incomming_queue]}.
55 |
56 | accept_peer(State, PeerPid) ->
57 | {reply, {ok, PeerPid}, State}.
58 |
59 | peer_ready(State, _PeerPid, _Identity) ->
60 | {noreply, State}.
61 |
62 | send(State, Data, From) ->
63 | send_multipart(State, [Data], From).
64 |
65 | recv(#erlangzmq_pull{pending_recv=nil, pending_recv_multipart=nil}=State, From) ->
66 | case queue:out(State#erlangzmq_pull.recv_queue) of
67 | {{value, Multipart}, NewRecvQueue} ->
68 | Msg = binary:list_to_bin(Multipart),
69 | {reply, {ok, Msg}, State#erlangzmq_pull{recv_queue=NewRecvQueue}};
70 | {empty, _RecvQueue} ->
71 | {noreply, State#erlangzmq_pull{pending_recv={from, From}}}
72 | end;
73 |
74 | recv(State, _From) ->
75 | {reply, {error, already_pending_recv}, State}.
76 |
77 | send_multipart(State, _Multipart, _From) ->
78 | {reply, {error, not_use}, State}.
79 |
80 | recv_multipart(#erlangzmq_pull{pending_recv=nil, pending_recv_multipart=nil}=State, From) ->
81 | case queue:out(State#erlangzmq_pull.recv_queue) of
82 | {{value, Multipart}, NewRecvQueue} ->
83 | {reply, {ok, Multipart}, State#erlangzmq_pull{recv_queue=NewRecvQueue}};
84 |
85 | {empty, _RecvQueue} ->
86 | {noreply, State#erlangzmq_pull{pending_recv_multipart={from, From}}}
87 | end;
88 |
89 | recv_multipart(State, _From) ->
90 | {reply, {error, already_pending_recv}, State}.
91 |
92 | peer_recv_message(State, _Message, _From) ->
93 | %% This function will never called, because use incomming_queue property
94 | {noreply, State}.
95 |
96 | queue_ready(#erlangzmq_pull{pending_recv=nil, pending_recv_multipart=nil}=State, _Identity, PeerPid) ->
97 | {out, Multipart} = erlangzmq_peer:incomming_queue_out(PeerPid),
98 | NewRecvQueue = queue:in(Multipart, State#erlangzmq_pull.recv_queue),
99 | {noreply, State#erlangzmq_pull{recv_queue=NewRecvQueue}};
100 |
101 | %% when pending recv
102 | queue_ready(#erlangzmq_pull{pending_recv={from, PendingRecv}, pending_recv_multipart=nil}=State, _Identity, PeerPid) ->
103 | {out, Multipart} = erlangzmq_peer:incomming_queue_out(PeerPid),
104 | Msg = binary:list_to_bin(Multipart),
105 | gen_server:reply(PendingRecv, {ok, Msg}),
106 | {noreply, State#erlangzmq_pull{pending_recv=nil}};
107 |
108 | %% when pending recv_multipart
109 | queue_ready(#erlangzmq_pull{pending_recv=nil, pending_recv_multipart={from, PendingRecv}}=State, _Identity, PeerPid) ->
110 | {out, Multipart} = erlangzmq_peer:incomming_queue_out(PeerPid),
111 | gen_server:reply(PendingRecv, {ok, Multipart}),
112 | {noreply, State#erlangzmq_pull{pending_recv_multipart=nil}}.
113 |
114 | peer_disconected(State, _PeerPid) ->
115 | {noreply, State}.
116 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_push_with_push.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_push_with_push).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | -define(PORT, 5595).
22 |
23 | push_test_() ->
24 | [
25 | {
26 | "Should send with round robin strategy",
27 | {setup, fun start/0, fun stop/1, fun send_round_robin/1}
28 | },
29 | {
30 | "Should send and recv multi_part",
31 | {setup, fun start/0, fun stop/1, fun send_multi_part/1}
32 | },
33 | {
34 | "When disconnected, should return no connected peers",
35 | {setup, fun start/0, fun stop/1,
36 | fun send_message_without_connect/1}
37 | },
38 | {
39 | "When call recv(), should deny recv message",
40 | {setup, fun start/0, fun stop/1,
41 | fun send_and_recv/1}
42 | }
43 | ].
44 |
45 |
46 | start() ->
47 | application:ensure_started(erlangzmq),
48 | {ok, Socket} = erlangzmq:socket(push),
49 | {ok, _Client} = erlangzmq:bind(Socket, tcp, "localhost", ?PORT),
50 | Socket.
51 |
52 | stop(Pid) ->
53 | gen_server:stop(Pid).
54 |
55 | send_round_robin(SocketPid) ->
56 | Parent = self(),
57 |
58 | spawn_link(
59 | fun() ->
60 | {ok, Client} = erlangzmq:socket(pull),
61 | {ok, _PeerPid} = erlangzmq:connect(Client, tcp, "localhost", ?PORT),
62 | {ok, Data} = erlangzmq:recv(Client),
63 | Parent ! {recv, 1, Data}
64 | end
65 | ),
66 | timer:sleep(100),
67 | spawn_link(
68 | fun() ->
69 | {ok, Client} = erlangzmq:socket(pull),
70 | {ok, _PeerPid} = erlangzmq:connect(Client, tcp, "localhost", ?PORT),
71 | {ok, Data} = erlangzmq:recv(Client),
72 | Parent ! {recv, 2, Data}
73 | end
74 | ),
75 | timer:sleep(200),
76 |
77 | ok = erlangzmq:send(SocketPid, <<"first message">>),
78 | ok = erlangzmq:send(SocketPid, <<"second message">>),
79 |
80 | Message1 = receive
81 | {recv, 1, Data1} ->
82 | Data1
83 | end,
84 | Message2 = receive
85 | {recv, 2, Data2} ->
86 | Data2
87 | end,
88 |
89 | [
90 | ?_assertEqual(<<"first message">>, Message2),
91 | ?_assertEqual(<<"second message">>, Message1)
92 | ].
93 |
94 | send_multi_part(SocketPid) ->
95 | Parent = self(),
96 |
97 | spawn_link(
98 | fun() ->
99 | {ok, Client} = erlangzmq:socket(pull),
100 | {ok, _PeerPid} = erlangzmq:connect(Client, tcp, "localhost", ?PORT),
101 | {ok, Data} = erlangzmq:recv_multipart(Client),
102 | Parent ! {recv, 3, Data}
103 | end
104 | ),
105 |
106 | timer:sleep(100),
107 | spawn_link(
108 | fun() ->
109 | {ok, Client} = erlangzmq:socket(pull),
110 | {ok, _PeerPid} = erlangzmq:connect(Client, tcp, "localhost", ?PORT),
111 | {ok, Data} = erlangzmq:recv_multipart(Client),
112 | Parent ! {recv, 4, Data}
113 | end
114 | ),
115 |
116 | timer:sleep(200),
117 |
118 | ok = erlangzmq:send_multipart(SocketPid, [<<"Hey">>, <<"Joe">>]),
119 | ok = erlangzmq:send_multipart(SocketPid, [<<"Lucy">>, <<"Sky">>]),
120 |
121 | Message1 = receive
122 | {recv, 3, Data1} ->
123 | Data1
124 | end,
125 | Message2 = receive
126 | {recv, 4, Data2} ->
127 | Data2
128 | end,
129 |
130 | [
131 | ?_assertEqual([<<"Lucy">>, <<"Sky">>], Message1),
132 | ?_assertEqual([<<"Hey">>, <<"Joe">>], Message2)
133 | ].
134 |
135 |
136 | send_message_without_connect(SocketPid) ->
137 | [
138 | ?_assertEqual({error,no_connected_peers}, erlangzmq:send(SocketPid, <<"first message">>))
139 | ].
140 |
141 | send_and_recv(SocketPid) ->
142 | [
143 | ?_assertEqual({error,not_use}, erlangzmq:recv(SocketPid))
144 | ].
145 |
--------------------------------------------------------------------------------
/README_old.md:
--------------------------------------------------------------------------------
1 | erlangzmq
2 | =========
3 |
4 | What is erlangzmq?
5 | -------------------
6 |
7 | `erlangzmq` is a library written in [Erlang](https://www.erlang.org/). It implements the ZeroMQ Message Transport Protocol (ZMTP). `erlangzmq` supports ZMTP version [3.1](http://rfc.zeromq.org/spec:37/ZMTP/).
8 |
9 | Goal
10 | ----
11 |
12 | The goal of `erlangzmq` application is to provide up-to-date native Erlang implementation of ZMTP.
13 |
14 | Features
15 | --------
16 | 1. Resource Property *(NEW in 3.1!)*
17 | 2. Request-Reply pattern
18 | 3. Publish-Subscribe pattern
19 | 4. Pipeline Pattern
20 | 5. Exclusive Pair Pattern
21 | 6. Version Negotiation
22 | 7. NULL Security Mechanism
23 | 8. Error Handling
24 | 9. Framing
25 | 10. Socket-Type Property & Identity Property
26 | 11. Backwards Interoperability with ZMTP 3.0
27 |
28 | Not implemented
29 | --------------
30 | 1. CurveZMQ - security is not currently implemented. As a work-around, consider
31 | using a proxy running [libzmq](https://github.com/zeromq/libzmq).
32 |
33 | Install
34 | -------
35 |
36 | You can install `erlangzmq` from Hex: https://hex.pm/packages/erlangzmq or by referencing a specific tag directly:
37 |
38 | ```
39 | {deps,[
40 | {erlangzmq, {git, "git@github.com:chovencorp/erlangzmq.git", {tag, "1.0.0"}}}
41 | ]}.
42 | ```
43 |
44 | For more info on rebar3 dependencies see the [rebar3 docs](http://www.rebar3.org/docs/dependencies).
45 |
46 | Usage
47 | -----
48 |
49 | See [examples](https://github.com/chovencorp/erlangzmq/tree/master/examples). Otherwise use just like a regular Erlang/OTP application.
50 |
51 | If you would like to use [python tests](https://github.com/chovencorp/erlangzmq/tree/master/python-test) to try language interop, you need to have [pyzmq](https://github.com/zeromq/pyzmq) installed.
52 |
53 | Build
54 | -----
55 | ```
56 | $ rebar3 compile
57 | ```
58 |
59 | Test
60 | ----
61 | ```
62 | $ rebar3 eunit -c
63 | ```
64 | The `-c` will allow you to see the test coverage by running the command below.
65 |
66 | Coverage
67 | --------
68 | ```
69 | $ rebar3 cover
70 | ```
71 |
72 | Generate Docs
73 | -------------
74 | ```
75 | $ rebar3 edoc
76 | ```
77 |
78 | Architecture
79 | -------------
80 | [Architecture](docs/architecture.md) describes the system structure.
81 |
82 | Contributing
83 | ------------
84 |
85 | See [Contributing](CONTRIBUTING.md).
86 |
87 | Copyright
88 | ----------
89 | Copyright [Choven Corp.](http://choven.ca) 2016. All Rights Reserved.
90 |
91 | FAQ
92 | ---
93 | 1. Why another Erlang implementation?
94 |
95 | Because the existing Erlang implementations and bindings are out of date.
96 |
97 |
98 | 2. Why a dual-license?
99 |
100 | To keep the code up-to-date. Having a commercial license allows us to charge money for code. Erlang is not a very
101 | trendy language, so the number of contributors to the open source project
102 | like this is small. In order to keep it from dying, we think it makes sense
103 | to run it as a commercial project.
104 |
105 | 3. Can I use `erlangzmq` for free?
106 |
107 | Yes, as long as you abide by the terms of the [AGPL license](COPYING.txt). In short, AGPL is a viral license,
108 | in that any code it touches has to be similarly licensed. So if you are working on an open-source project
109 | which has a [compatible](https://www.gnu.org/licenses/license-list.en.html) license, you can use `erlangzmq`. For more information see the [license](COPYING.txt) terms.
110 |
111 | 3. Why do I have to sign over my copyright when contributing?
112 |
113 | Short answer: for us to make money. Long answer: without your copyright, we could
114 | not dual-license the code, see "Why a dual-license?" above.
115 |
116 |
117 | License
118 | --------
119 | erlangzmq is free software: you can redistribute it and/or modify
120 | it under the terms of the GNU Affero General Public License as published by
121 | the Free Software Foundation, either version 3 of the License, or
122 | (at your option) any later version.
123 |
124 | erlangzmq is distributed in the hope that it will be useful,
125 | but WITHOUT ANY WARRANTY; without even the implied warranty of
126 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
127 | GNU Affero General Public License for more details.
128 |
129 | You should have received a copy of the GNU Affero General Public License
130 | along with erlangzmq. If not, see
131 |
132 | Commercial License
133 | ------------------
134 |
135 | If you would like to use _erlangzmq_ without the restrictions of AGPL, please purchase a commercial license [here](http://choven.ca/#/softwaredev).
136 |
137 | Acknowledgements
138 | ----------------
139 | Our thanks to Wilson Júnior [Wpjunior](https://github.com/Wpjunior) for the great work he did on the initial
140 | version of the codebase.
141 |
142 | Contact
143 | -------
144 | You can contact a real human by emailing drozzy@choven.ca
145 |
--------------------------------------------------------------------------------
/src/erlangzmq_dealer.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 |
19 | %% @doc ZeroMQ Dealer Pattern for Erlang
20 | %%
21 | %% This pattern implement Dealer especification
22 | %% from: http://rfc.zeromq.org/spec:28/REQREP#toc5
23 |
24 | -module(erlangzmq_dealer).
25 | -behaviour(erlangzmq_pattern).
26 |
27 | -export([valid_peer_type/1, init/1, peer_flags/1, accept_peer/2, peer_ready/3,
28 | send/3, recv/2,
29 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
30 | queue_ready/3, peer_disconected/2, identity/1]).
31 |
32 | -record(erlangzmq_dealer, {
33 | identity :: string(),
34 | lb :: list(),
35 | pending_recv=none :: none | {from, From::term()},
36 | state=idle :: idle | wait_req
37 | }).
38 |
39 | valid_peer_type(rep) -> valid;
40 | valid_peer_type(router) -> valid;
41 | valid_peer_type(_) -> invalid.
42 |
43 | init(Identity) ->
44 | State = #erlangzmq_dealer{
45 | identity=Identity,
46 | lb=erlangzmq_lb:new()
47 | },
48 | {ok, State}.
49 |
50 | identity(#erlangzmq_dealer{identity=I}) -> I.
51 |
52 | peer_flags(_State) ->
53 | {dealer, [incomming_queue]}.
54 |
55 | accept_peer(State, PeerPid) ->
56 | NewLb = erlangzmq_lb:put(State#erlangzmq_dealer.lb, PeerPid),
57 | {reply, {ok, PeerPid}, State#erlangzmq_dealer{lb=NewLb}}.
58 |
59 | peer_ready(State, _PeerPid, _Identity) ->
60 | {noreply, State}.
61 |
62 | send(State, _Data, _From) ->
63 | {reply, {error, not_implemented_yet}, State}.
64 |
65 | recv(State, _From) ->
66 | {reply, {error, not_implemented_yet}, State}.
67 |
68 | send_multipart(#erlangzmq_dealer{lb=LB}=State, Multipart, From) ->
69 | Traffic = erlangzmq_protocol:encode_message_multipart(Multipart),
70 |
71 | case erlangzmq_lb:get(LB) of
72 | none ->
73 | {reply, {error, no_connected_peers}, State};
74 | {NewLB, PeerPid} ->
75 | erlangzmq_peer:send(PeerPid, Traffic, From),
76 | {noreply, State#erlangzmq_dealer{lb=NewLB}}
77 | end.
78 |
79 | recv_multipart(#erlangzmq_dealer{state=idle, lb=LB}=State, From) ->
80 | case erlangzmq_lb:get(LB) of
81 | none ->
82 | {noreply, State#erlangzmq_dealer{state=wait_req, pending_recv={from, From}}};
83 | {NewLB, PeerPid} ->
84 | direct_recv_multipart(State#erlangzmq_dealer{lb=NewLB}, PeerPid, PeerPid, From)
85 | end;
86 | recv_multipart(State, _From) ->
87 | {reply, {error, efsm}, State}.
88 |
89 | peer_recv_message(State, _Message, _From) ->
90 | %% This function will never called, because use incomming_queue property
91 | {noreply, State}.
92 |
93 | queue_ready(#erlangzmq_dealer{state=wait_req, pending_recv={from, PendingRecv}}=State, _Identity, PeerPid) ->
94 | case erlangzmq_peer:incomming_queue_out(PeerPid) of
95 | {out, Messages} ->
96 | gen_server:reply(PendingRecv, {ok, Messages});
97 | empty ->
98 | gen_server:reply(PendingRecv, {error, queue_empty})
99 | end,
100 |
101 | FutureState = State#erlangzmq_dealer{state=idle, pending_recv=none},
102 | {noreply, FutureState};
103 |
104 | queue_ready(State, _Identity, _PeerPid) ->
105 | {noreply, State}.
106 |
107 | peer_disconected(#erlangzmq_dealer{lb=LB}=State, PeerPid) ->
108 | NewLB = erlangzmq_lb:delete(LB, PeerPid),
109 | {noreply, State#erlangzmq_dealer{lb=NewLB}}.
110 |
111 | %% implement direct recv from peer queues
112 | direct_recv_multipart(#erlangzmq_dealer{lb=LB}=State, FirstPeerPid, PeerPid, From) ->
113 | case erlangzmq_peer:incomming_queue_out(PeerPid) of
114 | {out, Messages} ->
115 | {reply, {ok, Messages}, State};
116 |
117 | empty ->
118 | case erlangzmq_lb:get(LB) of
119 | {NewLB, FirstPeerPid} ->
120 | {noreply, State#erlangzmq_dealer{state=wait_req, pending_recv={from, From}, lb=NewLB}};
121 | {NewLB, OtherPeerPid} ->
122 | direct_recv_multipart(State#erlangzmq_dealer{lb=NewLB}, FirstPeerPid, OtherPeerPid, From)
123 | end
124 | end.
125 |
--------------------------------------------------------------------------------
/src/erlangzmq_rep.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Rep Pattern for Erlang
19 | %%
20 | %% This pattern implement REP especification
21 | %% from: http://rfc.zeromq.org/spec:28/REQREP#toc4
22 |
23 | -module(erlangzmq_rep).
24 | -behaviour(erlangzmq_pattern).
25 |
26 | -export([valid_peer_type/1, init/1, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, identity/1]).
30 |
31 | %% state for a pattern always to be module name.
32 | -record(erlangzmq_rep, {
33 | identity :: string(),
34 | pending_recv=nil :: nil | {from, From::term()},
35 | state=idle :: idle | wait_req,
36 | lb :: list(),
37 | last_recv_peer=nil :: nil | pid()
38 | }).
39 |
40 | valid_peer_type(req) -> valid;
41 | valid_peer_type(dealer) -> valid;
42 | valid_peer_type(_) -> invalid.
43 |
44 | init(Identity) ->
45 | State = #erlangzmq_rep{
46 | identity=Identity,
47 | lb=erlangzmq_lb:new()
48 | },
49 | {ok, State}.
50 |
51 | identity(#erlangzmq_rep{identity=I}) -> I.
52 |
53 | peer_flags(_State) ->
54 | {rep, [incomming_queue]}.
55 |
56 | accept_peer(State, PeerPid) ->
57 | NewLb = erlangzmq_lb:put(State#erlangzmq_rep.lb, PeerPid),
58 | {reply, {ok, PeerPid}, State#erlangzmq_rep{lb=NewLb}}.
59 |
60 | peer_ready(State, _PeerPid, _Identity) ->
61 | {noreply, State}.
62 |
63 | send(#erlangzmq_rep{last_recv_peer=nil}=State, _Data, _From) ->
64 | {reply, {error, efsm}, State};
65 |
66 | send(#erlangzmq_rep{last_recv_peer=LastRecvPeer}=State, Data, _From)
67 | when is_pid(LastRecvPeer) ->
68 | Traffic = erlangzmq_protocol:encode_message_multipart([<<>>, Data]),
69 | erlangzmq_peer:send(LastRecvPeer, Traffic),
70 | {reply, ok, State#erlangzmq_rep{last_recv_peer=nil}}.
71 |
72 |
73 | recv(#erlangzmq_rep{state=idle, lb=LB}=State, From) ->
74 | case erlangzmq_lb:get(LB) of
75 | none ->
76 | {noreply, State#erlangzmq_rep{state=wait_req, pending_recv={from, From}}};
77 | {NewLB, PeerPid} ->
78 | direct_recv(State#erlangzmq_rep{lb=NewLB}, PeerPid, PeerPid, From)
79 | end;
80 |
81 | recv(State, _From) ->
82 | {reply, {error, efsm}, State}.
83 |
84 | send_multipart(State, _Multipart, _From) ->
85 | {reply, {error, not_implemented_yet}, State}.
86 |
87 | recv_multipart(State, _From) ->
88 | {reply, {error, not_implemented_yet}, State}.
89 |
90 | peer_recv_message(State, _Message, _From) ->
91 | %% This function will never called, because use incomming_queue property
92 | {noreply, State}.
93 |
94 | queue_ready(#erlangzmq_rep{state=wait_req, pending_recv={from, PendingRecv}}=State, _Identity, PeerPid) ->
95 | FutureState = State#erlangzmq_rep{state=idle, pending_recv=nil},
96 | case recv_from_peer(PeerPid) of
97 | {ok, Message} ->
98 | gen_server:reply(PendingRecv, {ok, Message}),
99 | {noreply, FutureState#erlangzmq_rep{last_recv_peer=PeerPid}};
100 |
101 | {error, Reason} ->
102 | gen_server:reply(PendingRecv, {error, Reason}),
103 | {noreply, FutureState};
104 |
105 | empty ->
106 | gen_server:reply(PendingRecv, {error, queue_empty}),
107 | {noreply, FutureState}
108 | end;
109 |
110 | queue_ready(State, _Identity, _PeerPid) ->
111 | %% Not used in iddle state
112 | {noreply, State}.
113 |
114 | peer_disconected(#erlangzmq_rep{lb=LB}=State, PeerPid) ->
115 | NewLB = erlangzmq_lb:delete(LB, PeerPid),
116 | {noreply, State#erlangzmq_rep{lb=NewLB}}.
117 |
118 | %% implement direct recv from peer queues
119 | direct_recv(#erlangzmq_rep{lb=LB}=State, FirstPeerPid, PeerPid, From) ->
120 | case recv_from_peer(PeerPid) of
121 | {ok, Message} ->
122 | {reply, {ok, Message}, State#erlangzmq_rep{last_recv_peer=PeerPid}};
123 |
124 | {error, Reason} ->
125 | {reply, {error, Reason}, State};
126 |
127 | empty ->
128 | case erlangzmq_lb:get(LB) of
129 | {NewLB, FirstPeerPid} ->
130 | {noreply, State#erlangzmq_rep{state=wait_req, pending_recv={from, From}, lb=NewLB}};
131 | {NewLB, OtherPeerPid} ->
132 | direct_recv(State#erlangzmq_rep{lb=NewLB}, FirstPeerPid, OtherPeerPid, From)
133 | end
134 | end.
135 |
136 | recv_from_peer(PeerPid) ->
137 | case erlangzmq_peer:incomming_queue_out(PeerPid) of
138 | {out, Messages} ->
139 | decode_messages(Messages);
140 | empty ->
141 | empty
142 | end.
143 |
144 | decode_messages([<<>>|Tail])->
145 | {ok, binary:list_to_bin(Tail)};
146 | decode_messages([Delimiter|_Tail]) ->
147 | error_logger:warning_report({
148 | invalid_delimiter_frame,
149 | {pattern, rep},
150 | {obtained_frame, Delimiter},
151 | {expected_frame, <<>>}
152 | }),
153 | {error, invalid_delimiter_frame}.
154 |
--------------------------------------------------------------------------------
/src/erlangzmq_pub.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Pub Pattern for Erlang
19 | %%
20 | %% This pattern implement Pub especification
21 | %% from: http://rfc.zeromq.org/spec:29/PUBSUB#toc3
22 |
23 | -module(erlangzmq_pub).
24 | -behaviour(erlangzmq_pattern).
25 |
26 | -export([valid_peer_type/1, init/1, init/2, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, peer_subscribe/3, peer_cancel_subscribe/3,
30 | identity/1
31 | ]).
32 |
33 | -record(erlangzmq_pub, {
34 | identity :: string(),
35 | subscriptions :: #{PeerPid::pid => [Subscription::binary()]},
36 | xpub=false :: false | true,
37 | recv_queue=nil :: nil | {some, queue:queue()}, %% only for xpub
38 | pending_recv=nil :: nil | {from, From::term()}
39 | }).
40 |
41 | valid_peer_type(sub) -> valid;
42 | valid_peer_type(xsub) -> valid;
43 | valid_peer_type(_) -> invalid.
44 |
45 | init(Identity) ->
46 | init(Identity, []).
47 |
48 | init(Identity, Opts) ->
49 | State = #erlangzmq_pub{
50 | identity=Identity,
51 | subscriptions=erlangzmq_subscriptions:new()
52 | },
53 | {ok, apply_opts(State, Opts)}.
54 |
55 | identity(#erlangzmq_pub{identity=I}) -> I.
56 |
57 | apply_opts(State, []) ->
58 | State;
59 |
60 | apply_opts(State, [xpub | Opts]) ->
61 | apply_opts(State#erlangzmq_pub{
62 | xpub=true,
63 | recv_queue={some, queue:new()}
64 | }, Opts).
65 |
66 | peer_flags(#erlangzmq_pub{xpub=true}) ->
67 | {xpub, [incomming_queue]};
68 |
69 | peer_flags(_State) ->
70 | %% pub_compatible_layer is used to allow decoder to understand old style of subscription
71 | %% in the 3.0 version of ZeroMQ.
72 | {pub, [pub_compatible_layer]}.
73 |
74 | accept_peer(State, PeerPid) ->
75 | {reply, {ok, PeerPid}, State}.
76 |
77 | peer_ready(State, _PeerPid, _Identity) ->
78 | {noreply, State}.
79 |
80 | send(State, Data, From) ->
81 | send_multipart(State, [Data], From).
82 |
83 | recv(#erlangzmq_pub{xpub=true}=State, _From) ->
84 | {reply, {error, not_implemented_yet}, State};
85 |
86 | recv(State, _From) ->
87 | {reply, {error, not_use}, State}.
88 |
89 | send_multipart(#erlangzmq_pub{subscriptions=Subscriptions}=State, Multipart, _From) ->
90 | [FirstPart | _] = Multipart,
91 | Traffic = erlangzmq_protocol:encode_message_multipart(Multipart),
92 | PeersPids = erlangzmq_subscriptions:match(Subscriptions, FirstPart),
93 |
94 | lists:foreach(fun (PeerPid) ->
95 | erlangzmq_peer:send(PeerPid, Traffic)
96 | end, PeersPids),
97 |
98 | {reply, ok, State}.
99 |
100 | recv_multipart(#erlangzmq_pub{pending_recv=nil, xpub=true, recv_queue={some, RecvQueue}}=State, From) ->
101 | case queue:out(RecvQueue) of
102 | {{value, Multipart}, NewRecvQueue} ->
103 | {reply, {ok, Multipart}, State#erlangzmq_pub{recv_queue={some, NewRecvQueue}}};
104 |
105 | {empty, _RecvQueue} ->
106 | {noreply, State#erlangzmq_pub{pending_recv={from, From}}}
107 | end;
108 |
109 | recv_multipart(#erlangzmq_pub{xpub=true}=State, _From) ->
110 | {reply, {error, efsm}, State};
111 |
112 | recv_multipart(State, _From) ->
113 | {reply, {error, not_use}, State}.
114 |
115 | peer_recv_message(State, _Message, _From) ->
116 | %% This function will never called, because use PUB not receive messages
117 | {noreply, State}.
118 |
119 | queue_ready(#erlangzmq_pub{xpub=true, pending_recv=nil, recv_queue={some, RecvQueue}}=State, _Identity, PeerPid) ->
120 | %% queue ready for XPUB pattern
121 | {out, Messages} = erlangzmq_peer:incomming_queue_out(PeerPid),
122 | NewRecvQueue = queue:in(Messages, RecvQueue),
123 | {noreply, State#erlangzmq_pub{recv_queue={some, NewRecvQueue}}};
124 |
125 | queue_ready(#erlangzmq_pub{xpub=true, pending_recv={from, PendingRecv}}=State, _Identity, PeerPid) ->
126 | {out, Multipart} = erlangzmq_peer:incomming_queue_out(PeerPid),
127 | gen_server:reply(PendingRecv, {ok, Multipart}),
128 | {noreply, State#erlangzmq_pub{pending_recv=nil}};
129 |
130 | queue_ready(State, _Identity, _PeerPid) ->
131 | %% This function will never called, because use PUB not receive messages
132 | {noreply, State}.
133 |
134 | peer_disconected(#erlangzmq_pub{subscriptions=Subscriptions}=State, PeerPid) ->
135 | NewSubscriptions = erlangzmq_subscriptions:delete(Subscriptions, PeerPid),
136 | {noreply, State#erlangzmq_pub{subscriptions=NewSubscriptions}}.
137 |
138 | peer_subscribe(#erlangzmq_pub{subscriptions=Subscriptions}=State, PeerPid, Subscription) ->
139 | NewSubscriptions = erlangzmq_subscriptions:put(Subscriptions, PeerPid, Subscription),
140 | {noreply, State#erlangzmq_pub{subscriptions=NewSubscriptions}}.
141 |
142 | peer_cancel_subscribe(#erlangzmq_pub{subscriptions=Subscriptions}=State, PeerPid, Subscription) ->
143 | NewSubscriptions = erlangzmq_subscriptions:delete(Subscriptions, PeerPid, Subscription),
144 | {noreply, State#erlangzmq_pub{subscriptions=NewSubscriptions}}.
145 |
--------------------------------------------------------------------------------
/src/erlangzmq.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc Erlang bindings for ZeroMQ.
19 | %%
20 | %% see ZeroMQ 3.1 RFC in http://rfc.zeromq.org/spec:37
21 |
22 | -module(erlangzmq).
23 | -include("erlangzmq.hrl").
24 | -behaviour(application).
25 |
26 | -export([start/2, stop/1]).
27 | -export([socket/1, socket/2, connect/4, connect/5, bind/4, send/2, recv/1, send_multipart/2, recv_multipart/1,
28 | cancel/2, subscribe/2,
29 | resource/0, attach_resource/3,
30 | version/0]).
31 |
32 | -define(SUPERVISOR, erlangzmq_sup).
33 |
34 | -type version()::{Major::integer(), Minor::integer(), Patch::integer()}.
35 |
36 | %%
37 | %% OTP/Application behaviour
38 | %%
39 | %% @hidden
40 | start(_StartType, _StartArgs) ->
41 | ?SUPERVISOR:start_link().
42 |
43 |
44 | %% @hidden
45 | stop(_State) ->
46 | ok.
47 |
48 |
49 | %% @doc start a new socket
50 | -spec socket(Type::socket_type(), Identity::string()) ->
51 | {ok, SocketPid::pid()} | {error, Reason::atom()}.
52 | socket(Type, Identity)
53 | when is_atom(Type),
54 | is_list(Identity) ->
55 | ?SUPERVISOR:start_socket(Type, Identity).
56 |
57 | socket(Type)
58 | when is_atom(Type) ->
59 | ?SUPERVISOR:start_socket(Type).
60 |
61 |
62 | %% @doc socket to a peer
63 | -spec connect(SocketPid::pid(), Transport::transport(), Host::string(), Port::integer()) ->
64 | {ok, PeerPid::pid()} | {error, Reason::atom()}.
65 | connect(SocketPid, Transport, Host, Port, Resource)
66 | when is_pid(SocketPid),
67 | is_atom(Transport),
68 | is_list(Host),
69 | is_number(Port),
70 | is_list(Resource)->
71 |
72 | gen_server:call(SocketPid, {connect, Transport, Host, Port, Resource}).
73 |
74 | connect(SocketPid, Transport, Host, Port) ->
75 | connect(SocketPid, Transport, Host, Port, "").
76 |
77 | %% @doc bind in a host and port
78 | -spec bind(SocketPid::pid(), Transport::transport(), Host::string(), Port::integer()) -> ok.
79 | bind(SocketPid, Transport, Host, Port)
80 | when is_pid(SocketPid),
81 | is_atom(Transport),
82 | is_list(Host),
83 | is_number(Port)->
84 |
85 | gen_server:call(SocketPid, {bind, Transport, Host, Port}).
86 |
87 |
88 | %% @doc send a message for peers
89 | -spec send(SocketPid::pid(), Data::binary()) -> ok.
90 | send(SocketPid, Data)
91 | when is_pid(SocketPid),
92 | is_binary(Data) ->
93 |
94 | gen_server:call(SocketPid, {send, Data}, infinity);
95 |
96 | send(SocketPid, Data)
97 | when is_pid(SocketPid),
98 | is_list(Data) ->
99 | send(SocketPid, list_to_binary(Data)).
100 |
101 | %% @doc send a message for peers using a list of binaries
102 | -spec send_multipart(SocketPid::pid(), [Data::binary()]) -> ok.
103 | send_multipart(SocketPid, Multipart)
104 | when is_pid(SocketPid),
105 | is_list(Multipart) ->
106 |
107 | gen_server:call(SocketPid, {send_multipart, Multipart}, infinity).
108 |
109 | %% @doc recv a message for peers
110 | -spec recv(SocketPid::pid()) -> {ok, Data::binary()} | {error, Reason::atom()}.
111 | recv(SocketPid)
112 | when is_pid(SocketPid) ->
113 | gen_server:call(SocketPid, recv, infinity).
114 |
115 |
116 | %% @doc recv a message for peers by a list of binaries
117 | -spec recv_multipart(SocketPid::pid()) -> {ok, [Data::binary]} | {error, Reason::atom()}.
118 | recv_multipart(SocketPid)
119 | when is_pid(SocketPid) ->
120 | gen_server:call(SocketPid, recv_multipart, infinity).
121 |
122 |
123 | %% @doc subscribe a topic, only supported in SUB and XSUB patterns.
124 | -spec subscribe(SocketPid::pid(), Topic::binary()) -> ok.
125 |
126 | subscribe(SocketPid, Topic)
127 | when is_pid(SocketPid),
128 | is_binary(Topic) ->
129 | gen_server:cast(SocketPid, {subscribe, Topic});
130 | subscribe(SocketPid, Topic)
131 | when is_pid(SocketPid),
132 | is_list(Topic) ->
133 | subscribe(SocketPid, list_to_binary(Topic)).
134 |
135 | %% @doc cancel a subscription for a topic, only supported in SUB and XSUB patterns.
136 | -spec cancel(SocketPid::pid(), Topic::binary()) -> ok.
137 | cancel(SocketPid, Topic)
138 | when is_pid(SocketPid),
139 | is_binary(Topic) ->
140 | gen_server:cast(SocketPid, {cancel, Topic});
141 | cancel(SocketPid, Topic)
142 | when is_pid(SocketPid),
143 | is_list(Topic) ->
144 | cancel(SocketPid, list_to_binary(Topic)).
145 |
146 | %% @doc start a new resource server.
147 | -spec resource() -> {ok, ResourcePid::pid()} | {error, Reason::atom()}.
148 | resource() ->
149 | ?SUPERVISOR:start_resource().
150 |
151 | %% @doc attach a new socket as a resource into resource server.
152 | -spec attach_resource(ResourcePid::pid(), Resource::binary(), SocketPid::pid()) -> ok.
153 | attach_resource(ResourcePid, Resource, SocketPid) ->
154 | gen_server:cast(ResourcePid, {attach, Resource, SocketPid}).
155 |
156 |
157 | -spec version() -> {ok, Version::version()} | {error, Reason::atom()}.
158 | version() ->
159 | case application:get_application(erlangzmq) of
160 | {ok, erlangzmq} ->
161 | {ok, return_version()};
162 | undefined -> {error, application_not_started}
163 | end.
164 |
165 | return_version() ->
166 | {ok, Version} = application:get_key(erlangzmq, vsn),
167 | [X, Y, Z] = string:tokens(Version, "."),
168 | Major = list_to_integer(X),
169 | Minor = list_to_integer(Y),
170 | Patch = list_to_integer(Z),
171 | {Major, Minor, Patch}.
172 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_req_test.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_req_test).
19 | -export([echo_rep_server/1]).
20 | -include_lib("eunit/include/eunit.hrl").
21 |
22 | req_single_test_() ->
23 | [
24 | {
25 | "Should send one message at time",
26 | {setup, fun start/0, fun stop/1, fun send_two_message_two_times/1}
27 | },
28 | {
29 | "When disconnected, should return no connected peers",
30 | {setup, fun start_without_connect/0, fun stop/1,
31 | fun send_message_without_connect/1}
32 | },
33 | {
34 | "When call send() and recv(), should recv message",
35 | {setup, fun start/0, fun stop/1,
36 | fun send_and_recv/1}
37 | },
38 | {
39 | "When call send() and more later call recv(), should recv message",
40 | {setup, fun start/0, fun stop/1,
41 | fun send_and_recv_with_delay/1}
42 | },
43 | {
44 | "When call send() and twice recv(), should deny the second recv",
45 | {setup, fun start/0, fun stop/1,
46 | fun send_and_twice_recv/1}
47 | },
48 | {
49 | "When call send() and twice recv() by distinct processes, should deny the second recv",
50 | {setup, fun start/0, fun stop/1,
51 | fun send_and_twice_recv_by_two_process/1}
52 | }
53 | ].
54 |
55 | req_with_load_balancer_test_() ->
56 | [
57 | {
58 | "Should send and receive more than one message",
59 | {setup, fun start_with_lb/0, fun stop/1, fun send_and_receive_10_messages/1}
60 | }
61 | ].
62 |
63 | start() ->
64 | application:ensure_started(erlangzmq),
65 | {ok, Socket} = erlangzmq:socket(req),
66 | ensure_echo_rep_server(rep_server1, 5555),
67 | ensure_echo_rep_server(rep_server2, 5556),
68 |
69 | {ok, _Client} = erlangzmq:connect(Socket, tcp, "localhost", 5555),
70 | Socket.
71 |
72 | ensure_echo_rep_server(Alias, Port) ->
73 | case whereis(Alias) of
74 | undefined ->
75 | {ok, Socket} = erlangzmq:socket(rep, atom_to_list(Alias)),
76 | {ok, _BindProc} = erlangzmq:bind(Socket, tcp, "127.0.0.1", Port),
77 | spawn(?MODULE, echo_rep_server, [Socket]),
78 | register(Alias, Socket),
79 | Socket;
80 | Pid ->
81 | Pid
82 | end.
83 |
84 | echo_rep_server(Socket) ->
85 | {ok, Data} = erlangzmq:recv(Socket),
86 | case Data of
87 | <<"delay">> ->
88 | timer:sleep(200);
89 | _ ->
90 | pass
91 | end,
92 |
93 | erlangzmq:send(Socket, Data),
94 | echo_rep_server(Socket).
95 |
96 | start_with_lb() ->
97 | application:ensure_started(erlangzmq),
98 | {ok, Socket} = erlangzmq:socket(req),
99 |
100 | ensure_echo_rep_server(rep_server1, 5555),
101 | ensure_echo_rep_server(rep_server2, 5556),
102 |
103 | {ok, _Client1} = erlangzmq:connect(Socket, tcp, "localhost", 5555),
104 | {ok, _Client2} = erlangzmq:connect(Socket, tcp, "localhost", 5556),
105 | Socket.
106 |
107 |
108 | start_without_connect() ->
109 | application:ensure_started(erlangzmq),
110 | {ok, Socket} = erlangzmq:socket(req),
111 | Socket.
112 |
113 | stop(Pid) ->
114 | gen_server:stop(Pid).
115 |
116 | send_two_message_two_times(SocketPid)->
117 | [
118 | ?_assertEqual(erlangzmq:send(SocketPid, <<"first message">>), ok),
119 | ?_assertEqual(erlangzmq:send(SocketPid, <<"second message">>), {error, efsm})
120 | ].
121 |
122 | send_message_without_connect(SocketPid) ->
123 | [
124 | ?_assertEqual(erlangzmq:send(SocketPid, <<"first message">>), {error,no_connected_peers})
125 | ].
126 |
127 | send_and_recv(SocketPid) ->
128 | [
129 | ?_assertEqual(erlangzmq:send(SocketPid, "message"), ok),
130 | ?_assertEqual(erlangzmq:recv(SocketPid), {ok, <<"message">>})
131 | ].
132 |
133 | send_and_recv_with_delay(SocketPid) ->
134 | ok = erlangzmq:send(SocketPid, <<"delayed message">>),
135 | ok = timer:sleep(200),
136 | [
137 | ?_assertEqual(erlangzmq:recv(SocketPid), {ok, <<"delayed message">>})
138 | ].
139 |
140 | send_and_twice_recv(SocketPid) ->
141 | [
142 | ?_assertEqual(erlangzmq:send(SocketPid, <<"twice recv">>), ok),
143 | ?_assertEqual(erlangzmq:recv(SocketPid), {ok, <<"twice recv">>}),
144 | ?_assertEqual(erlangzmq:recv(SocketPid), {error, efsm})
145 | ].
146 |
147 | send_and_twice_recv_by_two_process(SocketPid) ->
148 | ok = erlangzmq:send(SocketPid, <<"delay">>),
149 | Parent = self(),
150 |
151 | spawn_link(fun () ->
152 | Reply = erlangzmq:recv(SocketPid),
153 | timer:sleep(400),
154 | Parent ! {recv_reply, Reply}
155 | end),
156 | {ok, <<"delay">>} = erlangzmq:recv(SocketPid),
157 | AsyncReply = receive
158 | {recv_reply, X} ->
159 | X
160 | end,
161 | [
162 | ?_assertEqual(AsyncReply, {error, efsm})
163 | ].
164 |
165 | send_and_receive_10_messages(SocketPid) ->
166 | lists:map(
167 | fun (I) ->
168 | Message = io_lib:format("Message number ~p", [I]),
169 | MessageBin = list_to_binary(Message),
170 | ok = erlangzmq:send(SocketPid, MessageBin),
171 | Reply = erlangzmq:recv(SocketPid),
172 | ?_assertEqual(Reply, {ok, MessageBin})
173 | end, lists:seq(1, 10)).
174 |
--------------------------------------------------------------------------------
/src/erlangzmq_pair.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Pair Pattern for Erlang
19 | %%
20 | %% This pattern implement Pair especification
21 | %% from: http://rfc.zeromq.org/spec:31/EXPAIR#toc3
22 |
23 | -module(erlangzmq_pair).
24 | -behaviour(erlangzmq_pattern).
25 |
26 | -export([valid_peer_type/1, init/1, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, identity/1
30 | ]).
31 |
32 | -record(erlangzmq_pair, {
33 | identity :: string(),
34 | pair_pid :: nil | pid(),
35 | pending_send :: nil | {term(), binary()},
36 | pending_recv :: nil | term(),
37 | pending_recv_multipart :: nil | term(),
38 | recv_queue :: queue:queue()
39 | }).
40 |
41 | valid_peer_type(pair) -> valid;
42 | valid_peer_type(_) -> invalid.
43 |
44 | init(Identity) ->
45 | State = #erlangzmq_pair{
46 | identity=Identity,
47 | pair_pid=nil,
48 | pending_recv=nil,
49 | pending_recv_multipart=nil,
50 | pending_send=nil,
51 | recv_queue=queue:new()
52 | },
53 | {ok, State}.
54 |
55 | identity(#erlangzmq_pair{identity=Identity}) -> Identity.
56 |
57 | peer_flags(_State) ->
58 | {pair, [incomming_queue]}.
59 |
60 | accept_peer(#erlangzmq_pair{pair_pid=nil}=State, PeerPid) ->
61 | {reply, {ok, PeerPid}, State#erlangzmq_pair{pair_pid=PeerPid}};
62 |
63 | accept_peer(State, PeerPid) ->
64 | error_logger:info_msg("Deny remote peer, this peer already paired"),
65 | erlangzmq_peer:send_error(PeerPid, "This peer is already paired"),
66 | erlangzmq_peer:close(PeerPid),
67 | {reply, {error, peer_already_paired}, State}.
68 |
69 | peer_ready(#erlangzmq_pair{pending_send=PendingSend, pair_pid=PeerPid}=State, PeerPid, _Identity) ->
70 | case PendingSend of
71 | {From, Traffic} ->
72 | erlangzmq_peer:send(PeerPid, Traffic, From);
73 | nil ->
74 | pass
75 | end,
76 | {noreply, State#erlangzmq_pair{pending_send=nil}};
77 |
78 | peer_ready(State, _PeerPid, _Identity) ->
79 | {noreply, State}.
80 |
81 | send(State, Data, From) ->
82 | send_multipart(State, [Data], From).
83 |
84 | recv(#erlangzmq_pair{pending_recv=nil, pending_recv_multipart=nil}=State, From) ->
85 | case queue:out(State#erlangzmq_pair.recv_queue) of
86 | {{value, Multipart}, NewRecvQueue} ->
87 | Msg = binary:list_to_bin(Multipart),
88 | {reply, {ok, Msg}, State#erlangzmq_pair{recv_queue=NewRecvQueue}};
89 |
90 | {empty, _RecvQueue} ->
91 | {noreply, State#erlangzmq_pair{pending_recv=From}}
92 | end;
93 |
94 | recv(State, _From) ->
95 | {reply, {error, already_pending_recv}, State}.
96 |
97 | send_multipart(#erlangzmq_pair{pending_send=nil, pair_pid=nil}=State, Multipart, From) ->
98 | %% set send await
99 | Traffic = erlangzmq_protocol:encode_message_multipart(Multipart),
100 | {noreply, State#erlangzmq_pair{pending_send={From, Traffic}}};
101 |
102 | send_multipart(#erlangzmq_pair{pending_send=nil, pair_pid=PeerPid}=State, Multipart, From) ->
103 | %% send messsage now
104 | Traffic = erlangzmq_protocol:encode_message_multipart(Multipart),
105 | erlangzmq_peer:send(PeerPid, Traffic, From),
106 | {noreply, State};
107 |
108 | send_multipart(State, _Multipart, _From) ->
109 | {reply, {error, pendind_send_already_called}, State}.
110 |
111 | recv_multipart(#erlangzmq_pair{pending_recv=nil, pending_recv_multipart=nil}=State, From) ->
112 | case queue:out(State#erlangzmq_pair.recv_queue) of
113 | {{value, Multipart}, NewRecvQueue} ->
114 | {reply, {ok, Multipart}, State#erlangzmq_pair{recv_queue=NewRecvQueue}};
115 |
116 | {empty, _RecvQueue} ->
117 | {noreply, State#erlangzmq_pair{pending_recv_multipart=From}}
118 | end;
119 |
120 | recv_multipart(State, _From) ->
121 | {reply, {error, already_pending_recv}, State}.
122 |
123 | peer_recv_message(State, _Message, _From) ->
124 | %% This function will never called, because use PAIR use the incomming_queue parameter
125 | {noreply, State}.
126 |
127 | queue_ready(
128 | #erlangzmq_pair{pair_pid=PeerPid,
129 | pending_recv=PendingRecv,
130 | pending_recv_multipart=PendingRecvMultiPart,
131 | recv_queue=RecvQueue}=State, _Identity, PeerPid) ->
132 |
133 | {out, Multipart} = erlangzmq_peer:incomming_queue_out(PeerPid),
134 |
135 | NewRecvQueue = case {PendingRecv, PendingRecvMultiPart} of
136 | {nil, nil} ->
137 | queue:in(Multipart, RecvQueue);
138 |
139 | {_, nil} ->
140 | Msg = binary:list_to_bin(Multipart),
141 | gen_server:reply(PendingRecv, {ok, Msg}),
142 | RecvQueue;
143 |
144 | {nil, _}->
145 | gen_server:reply(PendingRecvMultiPart, {ok, Multipart}),
146 | RecvQueue
147 | end,
148 |
149 | {noreply, State#erlangzmq_pair{pending_recv=nil, pending_recv_multipart=nil, recv_queue=NewRecvQueue}};
150 |
151 | queue_ready(State, _Identity, _PeerPid) ->
152 | {noreply, State}.
153 |
154 | peer_disconected(State, _PeerPid) ->
155 | {noreply, State#erlangzmq_pair{pair_pid=nil}}.
156 |
--------------------------------------------------------------------------------
/src/erlangzmq_sub.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Sub Pattern for Erlang
19 | %%
20 | %% This pattern implement Sub especification
21 | %% from: http://rfc.zeromq.org/spec:29/PUBSUB#toc5
22 |
23 | -module(erlangzmq_sub).
24 | -behaviour(erlangzmq_pattern).
25 |
26 | -export([valid_peer_type/1, init/1, init/2, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, subscribe/2, cancel/2,
30 | peer_reconnected/2, identity/1
31 | ]).
32 |
33 | -record(erlangzmq_sub, {
34 | identity :: string(),
35 | topics :: list(),
36 | peers :: list(),
37 | pending_recv=nil :: nil | {from, From::term()},
38 | pending_recv_multipart :: nil | false | true,
39 | recv_queue :: queue:queue(),
40 | xsub=false :: true | false
41 | }).
42 |
43 | valid_peer_type(pub) -> valid;
44 | valid_peer_type(xpub) -> valid;
45 | valid_peer_type(_) -> invalid.
46 |
47 | init(Identity) ->
48 | init(Identity, []).
49 |
50 | init(Identity, Opts) ->
51 | State = #erlangzmq_sub{
52 | identity=Identity,
53 | topics=[],
54 | peers=[],
55 | recv_queue=queue:new(),
56 | pending_recv=nil,
57 | pending_recv_multipart=nil
58 | },
59 | {ok, apply_opts(State, Opts)}.
60 |
61 | apply_opts(State, []) ->
62 | State;
63 | apply_opts(State, [xsub | Opts]) ->
64 | apply_opts(State#erlangzmq_sub{xsub=true}, Opts).
65 |
66 | identity(#erlangzmq_sub{identity=Identity}) -> Identity.
67 |
68 | peer_flags(#erlangzmq_sub{xsub=true}) ->
69 | {xsub, [incomming_queue]};
70 | peer_flags(_State) ->
71 | {sub, [incomming_queue]}.
72 |
73 | accept_peer(#erlangzmq_sub{peers=Peers}=State, PeerPid) ->
74 | NewPeers = [PeerPid | Peers],
75 | {reply, {ok, PeerPid}, State#erlangzmq_sub{peers=NewPeers}}.
76 |
77 | peer_ready(#erlangzmq_sub{topics=Topics}=State, PeerPid, _Identity) ->
78 | send_subscriptions(Topics, PeerPid),
79 | {noreply, State}.
80 |
81 | send(State, Data, From) ->
82 | send_multipart(State, [Data], From).
83 |
84 | recv(#erlangzmq_sub{pending_recv=nil, recv_queue=RecvQueue}=State, From) ->
85 | case queue:out(RecvQueue) of
86 | {{value, Multipart}, NewRecvQueue} ->
87 | FullMsg = binary:list_to_bin(Multipart),
88 | {reply, {ok, FullMsg}, State#erlangzmq_sub{recv_queue=NewRecvQueue}};
89 |
90 | {empty, _RecvQueue} ->
91 | {noreply, State#erlangzmq_sub{pending_recv={from, From}, pending_recv_multipart=false}}
92 | end;
93 |
94 | recv(State, _From) ->
95 | {reply, {error, efsm}, State}.
96 |
97 | send_multipart(#erlangzmq_sub{xsub=true, peers=Peers}=State, Multipart, _From) ->
98 | Traffic = erlangzmq_protocol:encode_message_multipart(Multipart),
99 | lists:foreach(fun (PeerPid) ->
100 | erlangzmq_peer:send(PeerPid, Traffic)
101 | end, Peers),
102 | {reply, ok, State};
103 |
104 | send_multipart(State, _Multipart, _From) ->
105 | {reply, {error, not_use}, State}.
106 |
107 | recv_multipart(#erlangzmq_sub{pending_recv=nil, recv_queue=RecvQueue}=State, From) ->
108 | case queue:out(RecvQueue) of
109 | {{value, Multipart}, NewRecvQueue} ->
110 | {reply, {ok, Multipart}, State#erlangzmq_sub{recv_queue=NewRecvQueue}};
111 | {empty, _RecvQueue} ->
112 | {noreply, State#erlangzmq_sub{pending_recv={from, From}, pending_recv_multipart=true}}
113 | end;
114 |
115 | recv_multipart(State, _From) ->
116 | {reply, {error, efsm}, State}.
117 |
118 | peer_recv_message(State, _Message, _From) ->
119 | %% This function will never called, because use PUB not receive messages
120 | {noreply, State}.
121 |
122 | queue_ready(#erlangzmq_sub{recv_queue=RecvQueue, pending_recv=nil}=State, _Identity, PeerPid) ->
123 | {out, Messages} = erlangzmq_peer:incomming_queue_out(PeerPid),
124 | NewRecvQueue = queue:in(Messages, RecvQueue),
125 | {noreply, State#erlangzmq_sub{recv_queue=NewRecvQueue}};
126 |
127 | queue_ready(State, _Identity, PeerPid) ->
128 | #erlangzmq_sub{pending_recv={from, PendingRecv}, pending_recv_multipart=IsPendingMultipart} = State,
129 |
130 | {out, Multipart} = erlangzmq_peer:incomming_queue_out(PeerPid),
131 |
132 | case IsPendingMultipart of
133 | true ->
134 | gen_server:reply(PendingRecv, {ok, Multipart});
135 | false ->
136 | FullMsg = binary:list_to_bin(Multipart),
137 | gen_server:reply(PendingRecv, {ok, FullMsg})
138 | end,
139 | {noreply, State#erlangzmq_sub{pending_recv=nil, pending_recv_multipart=nil}}.
140 |
141 | peer_disconected(#erlangzmq_sub{peers=Peers}=State, PeerPid) ->
142 | NewPeers = lists:delete(PeerPid, Peers),
143 | {noreply, State#erlangzmq_sub{peers=NewPeers}}.
144 |
145 | subscribe(#erlangzmq_sub{topics=Topics, peers=Peers}=State, Topic) ->
146 | send_subscription_to_peers(Topic, Peers),
147 | {noreply, State#erlangzmq_sub{topics=[Topic |Topics]}}.
148 |
149 | cancel(#erlangzmq_sub{topics=Topics, peers=Peers}=State, Topic) ->
150 | send_cancel_subscription_to_peers(Topic, Peers),
151 | NewTopics = lists:delete(Topic, Topics),
152 | {noreply, State#erlangzmq_sub{topics=NewTopics}}.
153 |
154 | peer_reconnected(#erlangzmq_sub{topics=Topics}=State, PeerPid) ->
155 | send_subscriptions(Topics, PeerPid),
156 | {noreply, State}.
157 |
158 | %% PRIVATE API
159 | send_subscriptions(Topics, PeerPid) ->
160 | lists:foreach(fun (Topic) ->
161 | erlangzmq_peer:send_subscription(PeerPid, Topic)
162 | end, Topics).
163 |
164 | send_subscription_to_peers(Topic, Peers) ->
165 | lists:foreach(fun (PeerPid) ->
166 | erlangzmq_peer:send_subscription(PeerPid, Topic)
167 | end, Peers).
168 |
169 | send_cancel_subscription_to_peers(Topic, Peers) ->
170 | lists:foreach(fun (PeerPid) ->
171 | erlangzmq_peer:send_cancel_subscription(PeerPid, Topic)
172 | end, Peers).
173 |
--------------------------------------------------------------------------------
/test/erlangzmq_command_test.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_command_test).
19 |
20 | -include_lib("eunit/include/eunit.hrl").
21 |
22 | decode_ping_test() ->
23 | Frame = <<4, "PING", 1, 0>>,
24 | {ok, Command} = erlangzmq_command:decode(Frame),
25 | ?assertEqual(erlangzmq_command:command_name(Command), ping).
26 |
27 |
28 | decode_ready_test() ->
29 | BigProp = binary:copy(<<"luke">>, 100),
30 | Frame = <<
31 | 5, "READY", %% Command name "READY"
32 | 11, "Socket-Type", %% Property name "Socket-Type"
33 | 0, 0, 0, 6, "DEALER", %% Property value "DEALER"
34 | 8, "Identity", %% Property name "Identity"
35 | 0, 0, 0, 5, "HELLO", %% Property value "HELLO"
36 | 8, "resource", %% Property name "Resource"
37 | 0, 0, 0, 11, "My-Resource",%% Property value "My-Resource"
38 | 7, "X-Debug", %% Property name for application use
39 | 0, 0, 0, 8, "disabled", %% Property value for application use
40 | 5, "X-Big", %% Property name for big property test
41 | 0, 0, 1, 144, BigProp/binary %% Property value for big property test
42 | >>,
43 | {ok, Command} = erlangzmq_command:decode(Frame),
44 | ?assertEqual(erlangzmq_command:command_name(Command), ready),
45 | ?assertEqual(erlangzmq_command:ready_socket_type(Command), dealer),
46 | ?assertEqual(erlangzmq_command:ready_identity(Command), "HELLO"),
47 | ?assertEqual(erlangzmq_command:ready_resource(Command), "My-Resource"),
48 | ?assertEqual(erlangzmq_command:ready_metadata(Command), #{
49 | "x-debug" => "disabled",
50 | "x-big" => binary_to_list(BigProp)
51 | }).
52 |
53 | decode_wrong_ready_test() ->
54 | Frame = <<
55 | 5, "READY", %% Command name "READY"
56 | 110, "Wrong or corrupted" %% Wrong bytes
57 | >>,
58 | {error, wrong_ready_message} = erlangzmq_command:decode(Frame).
59 |
60 |
61 | encode_ready_command_test() ->
62 | Frame1 = erlangzmq_command:encode_ready(req, "", "", #{}),
63 | ?assertEqual(Frame1, <<
64 | 5, "READY",
65 | 11, "Socket-Type",
66 | 0, 0, 0, 3, "REQ"
67 | >>),
68 |
69 | Frame2 = erlangzmq_command:encode_ready(rep, "my-name", "my-resource", #{}),
70 | ?assertEqual(Frame2, <<
71 | 5, "READY",
72 | 11, "Socket-Type",
73 | 0, 0, 0, 3, "REP",
74 | 8, "Identity",
75 | 0, 0, 0, 7, "my-name",
76 | 8, "Resource",
77 | 0, 0, 0, 11, "my-resource"
78 | >>),
79 | Frame3 = erlangzmq_command:encode_ready(rep, "my-name", "my-resource", #{"X-Host"=>"Host1"}),
80 | ?assertEqual(Frame3, <<
81 | 5, "READY",
82 | 11, "Socket-Type",
83 | 0, 0, 0, 3, "REP",
84 | 8, "Identity",
85 | 0, 0, 0, 7, "my-name",
86 | 8, "Resource",
87 | 0, 0, 0, 11, "my-resource",
88 | 6, "X-Host",
89 | 0, 0, 0, 5, "Host1"
90 | >>),
91 | LargeProp = string:copies("large", 100),
92 | LargePropBin = list_to_binary(LargeProp),
93 | Frame4 = erlangzmq_command:encode_ready(rep, "my-name", "my-resource", #{"X-Host"=>"Host1", "X-Large"=>LargeProp}),
94 | ?assertEqual(Frame4, <<
95 | 5, "READY",
96 | 11, "Socket-Type",
97 | 0, 0, 0, 3, "REP",
98 | 8, "Identity",
99 | 0, 0, 0, 7, "my-name",
100 | 8, "Resource",
101 | 0, 0, 0, 11, "my-resource",
102 | 6, "X-Host",
103 | 0, 0, 0, 5, "Host1",
104 | 7, "X-Large",
105 | 0, 0, 1, 244,
106 | LargePropBin/binary>>).
107 |
108 | decode_error_test() ->
109 | Frame = <<5, "ERROR", 19 ,"Invalid socket type">>,
110 | {ok, Command} = erlangzmq_command:decode(Frame),
111 | ?assertEqual(erlangzmq_command:command_name(Command), error),
112 | ?assertEqual(erlangzmq_command:error_reason(Command), "Invalid socket type").
113 |
114 | decode_invalid_error_test() ->
115 | Frame = <<5, "ERROR", 10, "Broken">>,
116 | {error, wrong_error_message} = erlangzmq_command:decode(Frame).
117 |
118 | encode_error_command_test() ->
119 | Frame = erlangzmq_command:encode_error("Broken light-saber"),
120 | ?assertEqual(Frame, <<
121 | 5, "ERROR",
122 | 18, "Broken light-saber"
123 | >>).
124 |
125 | encode_subscribe_command_test() ->
126 | Frame = erlangzmq_command:encode_subscribe(<<"debug">>),
127 | ?assertEqual(Frame, <<
128 | 9, "SUBSCRIBE",
129 | "debug"
130 | >>).
131 |
132 |
133 | decode_subscribe_command_test() ->
134 | Frame = <<9, "SUBSCRIBE", "warn">>,
135 | {ok, Command} = erlangzmq_command:decode(Frame),
136 | ?assertEqual(
137 | erlangzmq_command:subscribe_subscription(Command),
138 | <<"warn">>
139 | ).
140 |
141 | encode_cancel_command_test() ->
142 | Frame = erlangzmq_command:encode_cancel(<<"error">>),
143 | ?assertEqual(Frame, <<
144 | 6, "CANCEL",
145 | "error"
146 | >>).
147 |
148 |
149 | decode_subscribe_cancel_test() ->
150 | Frame = <<6, "CANCEL", "fatal">>,
151 | {ok, Command} = erlangzmq_command:decode(Frame),
152 | ?assertEqual(
153 | erlangzmq_command:cancel_subscription(Command),
154 | <<"fatal">>
155 | ).
156 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_rep_test.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_rep_test).
19 | -export([echo_req_server/1]).
20 | -include_lib("eunit/include/eunit.hrl").
21 |
22 | rep_single_test_() ->
23 | [
24 | {
25 | "Should block recv until one peer send message",
26 | {setup, fun start_req/0, fun stop_req/1, fun rep_recv_and_send/1}
27 | },
28 | {
29 | "Should deny send without received a message",
30 | {setup, fun start_req/0, fun stop_req/1, fun rep_send_without_recv/1}
31 | },
32 | {
33 | "Should deny twice recvs",
34 | {setup, fun start_req/0, fun stop_req/1, fun rep_twice_recv/1}
35 | },
36 | {
37 | "Should bufferize while sent is not called yet",
38 | {setup, fun start_req/0, fun stop_req/1, fun rep_bufferize/1}
39 | }
40 | ].
41 |
42 | rep_reverse_test_() ->
43 | [
44 | {
45 | "Should connect to req peer",
46 | {setup, fun start_req_reverse/0, fun stop_req/1, fun recv_reverse/1}
47 | }
48 | ].
49 |
50 | rep_with_dealer_test_() ->
51 | [
52 | {
53 | "Should connect to dealer peer",
54 | {setup, fun start_req/0, fun stop_req/1, fun req_with_dealers/1}
55 | }
56 | ].
57 |
58 | start_req() ->
59 | application:ensure_started(erlangzmq),
60 | {ok, Socket} = erlangzmq:socket(rep),
61 | {ok, _BindProc} = erlangzmq:bind(Socket, tcp, "127.0.0.1", 5655),
62 | Socket.
63 |
64 | start_req_reverse() ->
65 | application:ensure_started(erlangzmq),
66 | {ok, Socket} = erlangzmq:socket(rep),
67 | req_server(5755),
68 | timer:sleep(50), %% wait the connection
69 | {ok, _BindProc} = erlangzmq:connect(Socket, tcp, "127.0.0.1", 5755),
70 | Socket.
71 |
72 | req_server(Port) ->
73 | {ok, Socket} = erlangzmq:socket(req),
74 | {ok, _BindProc} = erlangzmq:bind(Socket, tcp, "127.0.0.1", Port),
75 | spawn(?MODULE, echo_req_server, [Socket]).
76 |
77 | echo_req_server(Socket) ->
78 | timer:sleep(100), %% wait for a peer connect
79 | ok = erlangzmq:send(Socket, <<"Reverse Hello">>),
80 | {ok, Message} = erlangzmq:recv(Socket),
81 |
82 | case Message of
83 | <<"quit">> ->
84 | quit;
85 | _ ->
86 | echo_req_server(Socket)
87 | end.
88 |
89 | stop_req(Pid) ->
90 | gen_server:stop(Pid).
91 |
92 | rep_recv_and_send(SocketPid)->
93 | spy_client(),
94 | {ok, <<"message from client 1">>} = erlangzmq:recv(SocketPid),
95 | ok = erlangzmq:send(SocketPid, <<"reply from client 1">>),
96 | ReceivedData = receive
97 | {peer_recv, Data} -> Data
98 | end,
99 | [
100 | ?_assertEqual(ReceivedData, <<"reply from client 1">>)
101 | ].
102 |
103 | recv_reverse(SocketPid) ->
104 | {ok, Message1} = erlangzmq:recv(SocketPid),
105 | ok = erlangzmq:send(SocketPid, <<"continue">>),
106 | {ok, Message2} = erlangzmq:recv(SocketPid),
107 | ok = erlangzmq:send(SocketPid, <<"quit">>),
108 | [
109 | ?_assertEqual(Message1, <<"Reverse Hello">>),
110 | ?_assertEqual(Message2, <<"Reverse Hello">>)
111 | ].
112 |
113 | rep_send_without_recv(SocketPid) ->
114 | [
115 | ?_assertEqual(erlangzmq:send(SocketPid, <<"ok">>), {error, efsm})
116 | ].
117 |
118 | rep_twice_recv(SocketPid) ->
119 | spy_client(),
120 | Parent = self(),
121 | spawn_link(fun () ->
122 | timer:sleep(100),
123 | Reply = erlangzmq:recv(SocketPid),
124 | Parent ! {recv_reply, Reply}
125 | end),
126 | {ok, ReceivedData} = erlangzmq:recv(SocketPid),
127 | AsyncReply = receive
128 | {recv_reply, X} ->
129 | X
130 | end,
131 | [
132 | ?_assertEqual(ReceivedData, <<"message from client 1">>),
133 | ?_assertEqual(AsyncReply, {error, efsm})
134 | ].
135 |
136 | rep_bufferize(SocketPid) ->
137 | spy_client(<<"message 1">>),
138 | spy_client(<<"message 2">>),
139 | timer:sleep(250),
140 | {ok, ReceivedData1} = erlangzmq:recv(SocketPid),
141 | erlangzmq:send(SocketPid, <<"ok">>),
142 |
143 | {ok, ReceivedData2} = erlangzmq:recv(SocketPid),
144 | erlangzmq:send(SocketPid, <<"ok">>),
145 | [
146 | ?_assertEqual([<<"message 1">>, <<"message 2">>], lists:sort([ReceivedData1, ReceivedData2]))
147 | ].
148 |
149 | req_with_dealers(SocketPid) ->
150 | dealer_client(),
151 |
152 | {ok, ReceivedData1} = erlangzmq:recv(SocketPid),
153 | erlangzmq:send(SocketPid, <<"ok 1">>),
154 |
155 | {ok, ReceivedData2} = erlangzmq:recv(SocketPid),
156 | erlangzmq:send(SocketPid, <<"ok 2">>),
157 |
158 | RecvMsgs = receive
159 | {recv_msgs, Msgs} ->
160 | Msgs
161 | end,
162 |
163 | [
164 | ?_assertEqual(<<"packet 1">>, ReceivedData1),
165 | ?_assertEqual(<<"packet 2">>, ReceivedData2),
166 | ?_assertEqual([[<<>>, <<"ok 1">>], [<<>>, <<"ok 2">>]], RecvMsgs)
167 | ].
168 |
169 | spy_client() ->
170 | spy_client(<<"message from client 1">>).
171 |
172 | spy_client(Msg) ->
173 | Parent = self(),
174 | spawn_link(fun () ->
175 | timer:sleep(100), %% wait socket to be acceptable
176 | {ok, ClientSocket} = erlangzmq:socket(req),
177 | {ok, _ClientPid} = erlangzmq:connect(ClientSocket, tcp, "127.0.0.1", 5655),
178 | ok = erlangzmq:send(ClientSocket, Msg),
179 | {ok, Data} = erlangzmq:recv(ClientSocket),
180 | Parent ! {peer_recv, Data}
181 | end).
182 |
183 | dealer_client() ->
184 | Parent = self(),
185 | spawn_link(fun () ->
186 | timer:sleep(100), %% wait socket to be acceptable
187 | {ok, ClientSocket} = erlangzmq:socket(dealer),
188 | {ok, _ClientPid} = erlangzmq:connect(ClientSocket, tcp, "127.0.0.1", 5655),
189 | ok = erlangzmq:send_multipart(ClientSocket, [<<>>, <<"packet 1">>]),
190 | ok = erlangzmq:send_multipart(ClientSocket, [<<>>, <<"packet 2">>]),
191 |
192 | {ok, Message1} = erlangzmq:recv_multipart(ClientSocket),
193 | {ok, Message2} = erlangzmq:recv_multipart(ClientSocket),
194 |
195 | Parent ! {recv_msgs, [Message1, Message2]}
196 |
197 |
198 | end).
199 |
--------------------------------------------------------------------------------
/src/erlangzmq_req.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Req Pattern for Erlang
19 | %%
20 | %% This pattern implement REQ especification
21 | %% from: http://rfc.zeromq.org/spec:28/REQREP#toc3
22 |
23 | -module(erlangzmq_req).
24 | -behaviour(erlangzmq_pattern).
25 |
26 | -export([valid_peer_type/1, init/1, peer_flags/1, accept_peer/2, peer_ready/3,
27 | send/3, recv/2,
28 | send_multipart/3, recv_multipart/2, peer_recv_message/3,
29 | queue_ready/3, peer_disconected/2, identity/1]).
30 |
31 | %% The state of REQ pattern, lock-step fsm.
32 | %% +----------+ +---------------+ +------------------+ +-----------------------+ +----------+
33 | %% | s1-ready | => | s2-wait_reply | => | s3-wait_more_msg | => | s4-wait_recv/s1-ready | => | s1-ready |
34 | %% +----------+ e1 +---------------+ e2 +------------------+ e3 +-----------------------+ e4 +----------+
35 | %%
36 | %% Flow of states and events
37 | %% state 1 - ready, the socket not received any send command yet
38 | %% event 1 - when call send
39 | %% state 2 - waiting a reply, now is allowed to call recv command
40 | %% event 2 - then socket receive the delimiter message from the last peer that have sent the message.
41 | %% state 3 - waiting more messages
42 | %% event 3 - receive the last message, if recv is called return to state 1 or else goint to state 4
43 | %% state 4 - the socket is waiting client to call recv method
44 | %% event 4 - recv method called and the socket will return to state 1.
45 | -type req_state() :: ready | wait_reply | wait_more_msg | wait_recv.
46 |
47 | %% state for a pattern always to be module name.
48 | -record(erlangzmq_req, {
49 | identity :: string(),
50 | lb :: list(),
51 | state=ready :: req_state(),
52 | last_peer_sent=nil :: nil | pid(),
53 | pending_recv=nil :: term(),
54 | msg_buf=[] :: list()
55 | }).
56 |
57 | valid_peer_type(rep) -> valid;
58 | valid_peer_type(router) -> valid;
59 | valid_peer_type(_) -> invalid.
60 |
61 | init(Identity) ->
62 | State = #erlangzmq_req{
63 | identity=Identity,
64 | lb=erlangzmq_lb:new(),
65 | msg_buf=[]
66 | },
67 | {ok, State}.
68 |
69 | identity(#erlangzmq_req{identity=I}) -> I.
70 |
71 | peer_flags(_State) ->
72 | {req, []}.
73 |
74 | accept_peer(State, PeerPid) ->
75 | NewLb = erlangzmq_lb:put(State#erlangzmq_req.lb, PeerPid),
76 | {reply, {ok, PeerPid}, State#erlangzmq_req{lb=NewLb}}.
77 |
78 | peer_ready(State, _PeerPid, _Identity) ->
79 | {noreply, State}.
80 |
81 | send(#erlangzmq_req{lb=LB, state=ready}=State, Data, From) ->
82 | Traffic = erlangzmq_protocol:encode_message_multipart([<<>>, Data]),
83 |
84 | case erlangzmq_lb:get(LB) of
85 | none ->
86 | {reply, {error, no_connected_peers}, State};
87 | {NewLB, PeerPid} ->
88 | erlangzmq_peer:send(PeerPid, Traffic, From),
89 | {noreply, State#erlangzmq_req{
90 | lb=NewLB,
91 | state=wait_reply,
92 | last_peer_sent=PeerPid
93 | }}
94 | end;
95 |
96 | send(State, _Data, _From) ->
97 | {reply, {error, efsm}, State}.
98 |
99 | %% send recv when is already waiting recv command
100 | recv(#erlangzmq_req{state=wait_recv, msg_buf=Buffer}=State, _From) ->
101 | FullMsg = binary:list_to_bin(Buffer),
102 | {reply, {ok, FullMsg}, State#erlangzmq_req{state=ready, msg_buf=[]}};
103 |
104 | %% not allow recv in 'ready' state
105 | recv(#erlangzmq_req{state=ready}=State, _From) ->
106 | {reply, {error, efsm}, State};
107 |
108 | %% when in other state wait_reply and wait_more_msg
109 | recv(#erlangzmq_req{state=SocketState, pending_recv=PendingRecv}=State, From) ->
110 | case {SocketState, PendingRecv} of
111 | {wait_reply, nil} ->
112 | {noreply, State#erlangzmq_req{pending_recv=From}};
113 |
114 | {wait_more_msg, nil} ->
115 | {noreply, State#erlangzmq_req{pending_recv=From}};
116 |
117 | _ ->
118 | {reply, {error, efsm}, State}
119 | end.
120 |
121 | send_multipart(State, _Multipart, _From) ->
122 | {reply, {error, not_implemented_yet}, State}.
123 |
124 | recv_multipart(State, _From) ->
125 | {reply, {error, not_implemented_yet}, State}.
126 |
127 |
128 | peer_recv_message(#erlangzmq_req{state=wait_reply, last_peer_sent=From}=State, Message, From) ->
129 | case erlangzmq_protocol:message_data(Message) of
130 | <<>> ->
131 | {noreply, State#erlangzmq_req{state=wait_more_msg}};
132 | Frame ->
133 | error_logger:warning_report({
134 | invalid_delimiter_frame,
135 | {pattern, req},
136 | {obtained_frame, Frame},
137 | {expected_frame, <<>>}
138 | }),
139 | {noreply, State}
140 | end;
141 |
142 | peer_recv_message(#erlangzmq_req{state=wait_more_msg, last_peer_sent=From}=State, Message, From) ->
143 | #erlangzmq_req{msg_buf=Buffer, pending_recv=PendingRecv} = State,
144 | NewBuffer = Buffer ++ [erlangzmq_protocol:message_data(Message)],
145 |
146 | case {erlangzmq_protocol:message_has_more(Message), PendingRecv} of
147 | %% if need to accumulate more message
148 | {true, _} ->
149 | {noreply, State#erlangzmq_req{state=wait_more_msg, msg_buf=NewBuffer}};
150 |
151 | %% the last message was received, but the client not called recv yet
152 | {false, nil} ->
153 | {noreply, State#erlangzmq_req{state=wait_recv, msg_buf=NewBuffer}};
154 |
155 | %% client already called recv
156 | {false, PendingRecv} ->
157 | FullMsg = binary:list_to_bin(NewBuffer),
158 | gen_server:reply(PendingRecv, {ok, FullMsg}),
159 | {noreply, State#erlangzmq_req{state=ready, msg_buf=[], pending_recv=nil}}
160 | end;
161 |
162 | peer_recv_message(#erlangzmq_req{last_peer_sent=LastPeer, state=S}=State, Message, From) ->
163 | error_logger:info_report({
164 | discard_message,
165 | {last_peer_sent, LastPeer},
166 | {peer_sent, From},
167 | {state, S},
168 | {message, Message}
169 | }),
170 | {noreply, State}.
171 |
172 | queue_ready(State, _Identity, _From) ->
173 | %% Not used in REQ Pattern
174 | {noreply, State}.
175 |
176 | peer_disconected(#erlangzmq_req{lb=LB}=State, PeerPid) ->
177 | NewLB = erlangzmq_lb:delete(LB, PeerPid),
178 | {noreply, State#erlangzmq_req{lb=NewLB}}.
179 |
--------------------------------------------------------------------------------
/src/erlangzmq_socket.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc ZeroMQ Socket Type implementation for Erlang
19 | %% @hidden
20 |
21 | -module(erlangzmq_socket).
22 | -behaviour(gen_server).
23 |
24 | -record(state, {socket, socket_state}).
25 | %% api behaviour
26 | -export([start_link/2]).
27 |
28 | %% gen_server behaviors
29 | -export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, init/1, terminate/2]).
30 |
31 | %% public API implementation
32 | -spec start_link(Type::term(),
33 | Identity::string()) ->
34 | {ok, Pid::pid()} | {error, Reason::term()}.
35 | start_link(Type, Identity)
36 | when is_atom(Type),
37 | is_list(Identity) ->
38 | gen_server:start_link(?MODULE, {Type, Identity}, []).
39 |
40 |
41 |
42 | %% gen_server implementation
43 | init({Type, Identity}) ->
44 | process_flag(trap_exit, true),
45 | case erlangzmq_pattern:module(Type) of
46 | {error, Reason} ->
47 | {stop, Reason};
48 | ModuleName ->
49 | {ok, S} = ModuleName:init(Identity),
50 | {ok, #state{socket=ModuleName, socket_state=S}}
51 | end.
52 |
53 | code_change(_OldVsn, State, _Extra) ->
54 | {ok, State}.
55 |
56 | handle_call({connect, Protocol, Host, Port, Resource}, _From, State) ->
57 | connect(Protocol, Host, Port, Resource, State);
58 |
59 | handle_call({accept, SocketPid}, _From, State) ->
60 | accept(SocketPid, State);
61 |
62 | handle_call({send, Data}, From, State) ->
63 | send(Data, From, State);
64 |
65 | handle_call(recv, From, State) ->
66 | recv(From, State);
67 |
68 | handle_call({send_multipart, Multipart}, From, State) ->
69 | send_multipart(Multipart, From, State);
70 |
71 | handle_call(recv_multipart, From, State) ->
72 | recv_multipart(From, State);
73 |
74 | handle_call({bind, tcp, Host, Port}, _From, State) ->
75 | Reply = erlangzmq_bind:start_link(Host, Port),
76 | {reply, Reply, State};
77 |
78 | handle_call(get_flags, _From, State) ->
79 | get_flags(State);
80 |
81 | handle_call({bind, Protocol, _Host, _Port}, _From, State) ->
82 | {reply, {error, {unsupported_protocol, Protocol}}, State}.
83 |
84 | handle_cast({peer_ready, From, Identity}, State) ->
85 | peer_ready(From, Identity, State);
86 |
87 | handle_cast({subscribe, Topic}, State) ->
88 | pattern_support(State, subscribe, [Topic]);
89 |
90 | handle_cast({cancel, Topic}, State) ->
91 | pattern_support(State, cancel, [Topic]);
92 |
93 | handle_cast({peer_subscribe, From, Subscription}, State) ->
94 | pattern_support(State, peer_subscribe, [From, Subscription]);
95 |
96 | handle_cast({peer_cancel_subscribe, From, Subscription}, State) ->
97 | pattern_support(State, peer_cancel_subscribe, [From, Subscription]);
98 |
99 | handle_cast({peer_reconnected, From}, State) ->
100 | pattern_support(State, peer_reconnected, [From], nowarn);
101 |
102 | handle_cast(CastMsg, State) ->
103 | error_logger:info_report([
104 | unhandled_handle_cast,
105 | {module, ?MODULE},
106 | {msg, CastMsg}
107 | ]),
108 | {noreply, State}.
109 |
110 | handle_info({peer_recv_message, Message, From}, State) ->
111 | peer_recv_message(Message, From, State);
112 |
113 | handle_info({queue_ready, Identity, From}, State) ->
114 | queue_ready(Identity, From, State);
115 |
116 | handle_info({'EXIT', PeerPid, {shutdown, _Reason}}, State) ->
117 | exit_peer(PeerPid, State);
118 |
119 | handle_info(InfoMsg, State) ->
120 | error_logger:info_report([
121 | unhandled_handle_info,
122 | {module, ?MODULE},
123 | {msg, InfoMsg}
124 | ]),
125 | {noreply, State}.
126 |
127 | terminate(_Reason, _State) ->
128 | %% TODO: close all sockets
129 | ok.
130 |
131 | %% private api
132 |
133 | %% Take the sockets reply, and apply it to our state, while returning the reply.
134 | store({reply, M, S}, State) -> {reply, M, State#state{socket_state=S}};
135 | store({noreply, S}, State) -> {noreply, State#state{socket_state=S}}.
136 |
137 | connect(Protocol, Host, Port, Resource, #state{socket=S, socket_state=T}=State) ->
138 | {SocketType, PeerOpts} = peer_flags(S, T),
139 |
140 | case erlangzmq_peer:connect(SocketType, Protocol, Host, Port, Resource, PeerOpts) of
141 | {ok, Pid} ->
142 | Reply = S:accept_peer(T, Pid),
143 | store(Reply, State);
144 | {error, Reason} ->
145 | {reply, {error, Reason}, State}
146 | end.
147 |
148 | accept(SocketPid, #state{socket=S, socket_state=T}=State) ->
149 | {SocketType, PeerOpts} = peer_flags(S, T),
150 |
151 | case erlangzmq_peer:accept(SocketType, SocketPid, PeerOpts) of
152 | {ok, Pid} ->
153 | Reply = S:accept_peer(T, Pid),
154 | store(Reply, State);
155 | {error, Reason} ->
156 | {reply, {error, Reason}, State}
157 | end.
158 |
159 | send(Data, From, #state{socket=S, socket_state=T}=State) ->
160 | Reply = S:send(T, Data, From),
161 | store(Reply, State).
162 |
163 | recv(From, #state{socket=S, socket_state=T}=State) ->
164 | Reply = S:recv(T, From),
165 | store(Reply, State).
166 |
167 | send_multipart(Multipart, From, #state{socket=S, socket_state=T}=State) ->
168 | Reply = S:send_multipart(T, Multipart, From),
169 | store(Reply, State).
170 |
171 | recv_multipart(From, #state{socket=S, socket_state=T}=State) ->
172 | Reply = S:recv_multipart(T, From),
173 | store(Reply, State).
174 |
175 | get_flags(#state{socket=S, socket_state=T}=State) ->
176 | {reply, peer_flags(S, T), State}.
177 |
178 | peer_ready(From, Identity, #state{socket=S, socket_state=T}=State) ->
179 | Reply = S:peer_ready(T, From, Identity),
180 | store(Reply, State).
181 |
182 | pattern_support(State, Function, Args) ->
183 | pattern_support(State, Function, Args, warn).
184 |
185 | pattern_support(#state{socket=S, socket_state=T}=State, Function, Args, Alert) ->
186 | IsExported = erlang:function_exported(S, Function, length(Args) + 1),
187 |
188 | case {IsExported, Alert} of
189 | {true, _} ->
190 | store(apply(S, Function, [T] ++ Args), State);
191 |
192 | {false, warn} ->
193 | error_logger:warning_report([
194 | pattern_not_supported,
195 | {module, S},
196 | {method, Function},
197 | {args, Args}
198 | ]),
199 | {noreply, State};
200 |
201 | {false, _} ->
202 | {noreply, State}
203 | end.
204 |
205 | peer_recv_message(Message, From, #state{socket=S, socket_state=T}=State) ->
206 | Reply = S:peer_recv_message(T, Message, From),
207 | store(Reply, State).
208 |
209 | queue_ready(Identity, From, #state{socket=S, socket_state=T}=State) ->
210 | Reply = S:queue_ready(T, Identity, From),
211 | store(Reply, State).
212 |
213 | exit_peer(PeerPid, #state{socket=S, socket_state=T}=State) ->
214 | Reply = S:peer_disconected(T, PeerPid),
215 | store(Reply, State).
216 |
217 | peer_flags(Socket, SocketState) ->
218 | {SocketType, PeerOpts} = Socket:peer_flags(SocketState),
219 | Identity = Socket:identity(SocketState),
220 | {SocketType, [{identity, Identity} | PeerOpts]}.
221 |
222 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_xpub_with_xsub.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_xpub_with_xsub).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | -define(PORT, 5587).
22 |
23 | normal_test_() ->
24 | [
25 | {
26 | "Should deliver message for all subscribers",
27 | {setup, fun start/0, fun stop/1, fun negotiate_subcriptions_without_multipart/1}
28 | }
29 | , {
30 | "Should deliver message for all subscribers using multipart",
31 | {setup, fun start/0, fun stop/1, fun negotiate_subcriptions_with_multipart/1}
32 | }
33 | , {
34 | "Should deliver message for all subscribers that matching with pattern",
35 | {setup, fun start/0, fun stop/1, fun negotiate_subcriptions_with_matching/1}
36 | }
37 | , {
38 | "Should resend subscriptions when reconnection ocurred",
39 | {setup, fun start/0, fun stop/1, fun negotiate_subcriptions_with_reconnect/1}
40 | }
41 | , {
42 | "Should allow to cancel and make other subscriptions",
43 | {setup, fun start/0, fun stop/1, fun cancel_and_remake_subscriptions/1}
44 | }
45 | , {
46 | "Should allow to XPUB recv messages and XSUB send messages",
47 | {setup, fun start/0, fun stop/1, fun negociate_reverse_messages/1}
48 | }
49 | ].
50 |
51 | start() ->
52 | application:ensure_started(erlangzmq),
53 | {ok, Socket} = erlangzmq:socket(xpub),
54 | {ok, _BindPid} = erlangzmq:bind(Socket, tcp, "localhost", ?PORT),
55 | Socket.
56 |
57 | start_worker(Identity, SubscribeTopic, Func) ->
58 | Parent = self(),
59 | spawn_link(
60 | fun () ->
61 | {ok, Socket} = erlangzmq:socket(xsub, Identity),
62 | erlangzmq:subscribe(Socket, SubscribeTopic),
63 | {ok, PeerPid} = erlangzmq:connect(Socket, tcp, "localhost", ?PORT),
64 |
65 | case erlang:fun_info(Func, arity) of
66 | {arity, 3} ->
67 | Func(Socket, Identity, Parent);
68 | {arity, 4} ->
69 | Func(Socket, PeerPid, Identity, Parent)
70 | end
71 | end
72 | ).
73 |
74 | stop(Pid) ->
75 | gen_server:stop(Pid).
76 |
77 | negotiate_subcriptions_without_multipart(Socket) ->
78 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
79 | {ok, Message} = erlangzmq:recv(ClientSocket),
80 | Parent ! {recv, Identity, Message}
81 | end,
82 | start_worker("XSUB-A", <<>>, NegociateFunc),
83 | start_worker("XSUB-B", <<>>, NegociateFunc),
84 | timer:sleep(200),
85 | ok = erlangzmq:send(Socket, <<"Ready">>),
86 |
87 | MessageA = receive
88 | {recv, "XSUB-A", MultipartA} ->
89 | MultipartA
90 | end,
91 |
92 | MessageB = receive
93 | {recv, "XSUB-B", MultipartB} ->
94 | MultipartB
95 | end,
96 |
97 | [
98 | ?_assertEqual(MessageA, <<"Ready">>),
99 | ?_assertEqual(MessageB, <<"Ready">>)
100 | ].
101 |
102 | negotiate_subcriptions_with_multipart(Socket) ->
103 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
104 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
105 | Parent ! {recv, Identity, Message}
106 | end,
107 | start_worker("XSUB-C", <<>>, NegociateFunc),
108 | start_worker("XSUB-D", <<>>, NegociateFunc),
109 | timer:sleep(200),
110 | ok = erlangzmq:send_multipart(Socket, [<<"Ready">>, <<"OK">>]),
111 |
112 | MessageA = receive
113 | {recv, "XSUB-C", MultipartA} ->
114 | MultipartA
115 | end,
116 |
117 | MessageB = receive
118 | {recv, "XSUB-D", MultipartB} ->
119 | MultipartB
120 | end,
121 |
122 | [
123 | ?_assertEqual(MessageA, [<<"Ready">>, <<"OK">>]),
124 | ?_assertEqual(MessageB, [<<"Ready">>, <<"OK">>])
125 | ].
126 |
127 | negotiate_subcriptions_with_matching(Socket) ->
128 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
129 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
130 | Parent ! {recv, Identity, Message}
131 | end,
132 | start_worker("XSUB-E", <<"debug">>, NegociateFunc),
133 | start_worker("XSUB-F", <<"info">>, NegociateFunc),
134 | timer:sleep(200),
135 | ok = erlangzmq:send_multipart(Socket, [<<"debug">>,<<"DebugReady">>]),
136 | ok = erlangzmq:send_multipart(Socket, [<<"info">>,<<"InfoReady">>]),
137 |
138 | MessageA = receive
139 | {recv, "XSUB-E", MultipartA} ->
140 | MultipartA
141 | end,
142 |
143 | MessageB = receive
144 | {recv, "XSUB-F", MultipartB} ->
145 | MultipartB
146 | end,
147 |
148 | [
149 | ?_assertEqual([<<"debug">>, <<"DebugReady">>], MessageA),
150 | ?_assertEqual([<<"info">>, <<"InfoReady">>], MessageB)
151 | ].
152 |
153 | negotiate_subcriptions_with_reconnect(Socket) ->
154 | NegociateFunc = fun (ClientSocket, PeerPid, Identity, Parent) ->
155 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
156 | Parent ! {recv, Identity, Message},
157 | erlangzmq_peer:reconnect(PeerPid),
158 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
159 | Parent ! {recv, Identity, Message}
160 | end,
161 | start_worker("XSUB-G", <<"A">>, NegociateFunc),
162 | timer:sleep(200),
163 | ok = erlangzmq:send_multipart(Socket, [<<"A">>, <<"Message A">>]),
164 | ok = erlangzmq:send_multipart(Socket, [<<"B">>, <<"Message B">>]),
165 |
166 | Message1 = receive
167 | {recv, "XSUB-G", MultipartA} ->
168 | MultipartA
169 | end,
170 | timer:sleep(300), %% waits for reconnection
171 | ok = erlangzmq:send_multipart(Socket, [<<"A">>, <<"Message A">>]),
172 | ok = erlangzmq:send_multipart(Socket, [<<"B">>, <<"Message B">>]),
173 |
174 | Message2 = receive
175 | {recv, "XSUB-G", MultipartB} ->
176 | MultipartB
177 | end,
178 |
179 | [
180 | ?_assertEqual([<<"A">>, <<"Message A">>], Message1),
181 | ?_assertEqual([<<"A">>, <<"Message A">>], Message2)
182 | ].
183 |
184 | cancel_and_remake_subscriptions(Socket) ->
185 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
186 | erlangzmq:cancel(ClientSocket, <<"Z">>),
187 | erlangzmq:subscribe(ClientSocket, <<"W">>),
188 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
189 | Parent ! {recv, Identity, Message}
190 | end,
191 | start_worker("XSUB-H", <<"Z">>, NegociateFunc),
192 |
193 | %% waits the negotiation
194 | timer:sleep(400),
195 | ok = erlangzmq:send_multipart(Socket, [<<"Z">>, <<"Message Z">>]),
196 | ok = erlangzmq:send_multipart(Socket, [<<"W">>, <<"Message W">>]),
197 |
198 | Message = receive
199 | {recv, "XSUB-H", MultipartA} ->
200 | MultipartA
201 | end,
202 | [
203 | ?_assertEqual([<<"W">>, <<"Message W">>], Message)
204 | ].
205 |
206 | negociate_reverse_messages(Socket) ->
207 | NegociateFunc = fun (ClientSocket, _Identity, _Parent) ->
208 | ok = erlangzmq:send_multipart(ClientSocket, [<<"hey girl <3">>])
209 | end,
210 | start_worker("XSUB-I", <<"A">>, NegociateFunc),
211 | {ok, [Data]} = erlangzmq:recv_multipart(Socket),
212 |
213 | [
214 | ?_assertEqual(<<"hey girl <3">>, Data)
215 | ].
216 |
--------------------------------------------------------------------------------
/src/erlangzmq_command.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | %% @doc Module responsible to decode and encode ZeroMQ commands
19 | -module(erlangzmq_command).
20 | -include("erlangzmq.hrl").
21 |
22 | -export_type([command/0]).
23 | -export([decode/1, command_name/1,
24 | encode_ready/4, ready_socket_type/1, ready_identity/1, ready_resource/1, ready_metadata/1,
25 | encode_error/1, error_reason/1,
26 | encode_subscribe/1, subscribe_subscription/1,
27 | encode_cancel/1, cancel_subscription/1
28 | ]).
29 |
30 | -record(ready, {
31 | socket_type=nil :: nil | socket_type(),
32 | identity="" :: string(),
33 | resource="" :: string(),
34 | metadata=#{} :: map()
35 | }).
36 |
37 | -record(ping, {}).
38 | -record(error, {reason :: string()}).
39 | -record(subscribe, {subscription :: binary()}).
40 | -record(cancel, {subscription :: binary()}).
41 |
42 | -type ready() :: ready(). %% ready command
43 | -type ping() :: ping(). %% ping command
44 | -type error() :: error(). %% error command
45 | -type subscribe():: subscribe(). %% subscribe command
46 | -type cancel() :: cancel(). %% cancel command
47 |
48 | %% commands available
49 | -type command() :: ready() | ping() | error() | subscribe() | cancel ().
50 |
51 | -type ready_decode_error() :: wrong_ready_message.
52 | -type decode_error() :: ready_decode_error().
53 |
54 | %%
55 | %% Public API
56 | %%
57 |
58 | %% @doc decode reads incoming command and generate an command 'object'
59 | -spec decode(Frame::erlangzmq_protocol:frame()) -> Command::command() | {error, decode_error()}.
60 | decode(Frame) ->
61 | <> = Frame,
62 | CommandNameBitSize = 8 * CommandNameByteSize,
63 | <> = Frame2,
64 | decode_message(binary_to_atom(CommandName, utf8), CommandBody).
65 |
66 |
67 | %% @doc returns the name of a command
68 | -spec command_name(Command::command()) -> Name::atom().
69 | command_name(Command) ->
70 | element(1, Command).
71 |
72 | %%
73 | %% Ready command handler functions
74 | %%
75 |
76 | %% @doc return the socket type of a ready command
77 | -spec ready_socket_type(Command::ready()) -> SocketType::atom().
78 | ready_socket_type(#ready{socket_type=SocketType}) ->
79 | SocketType.
80 |
81 |
82 | %% @doc return the identity of a ready command
83 | -spec ready_identity(Command::ready()) -> Identity::string().
84 | ready_identity(#ready{identity=Identity}) ->
85 | Identity.
86 |
87 |
88 | %% @doc return the resource of a ready command
89 | -spec ready_resource(Command::ready()) -> Resource::string().
90 | ready_resource(#ready{resource=Resource}) ->
91 | Resource.
92 |
93 |
94 | %% @doc return the metadata of a ready command
95 | -spec ready_metadata(Command::ready()) -> Metadata::map().
96 | ready_metadata(#ready{metadata=Metadata}) ->
97 | Metadata.
98 |
99 |
100 | %% @doc encode a ready command
101 | -spec encode_ready(SocketType::atom(), Identity::string(), Resource::string(), Metadata::map()) -> Data::binary().
102 | encode_ready(SocketType, Identity, Resource, Metadata) when is_atom(SocketType) ->
103 | SocketTypeBin = string:to_upper(atom_to_list(SocketType)),
104 | Properties = lists:flatten([
105 | {"Socket-Type", SocketTypeBin},
106 | {"Identity", Identity},
107 | {"Resource", Resource},
108 | maps:to_list(Metadata)
109 | ]),
110 | PropertiesFrame = encode_ready_properties(Properties),
111 | <<5, "READY", PropertiesFrame/binary>>.
112 |
113 |
114 | %%
115 | %% Error command functions
116 | %%
117 |
118 | %% @doc returns the reason of error
119 | -spec error_reason(Command::error()) -> Reason::string().
120 | error_reason(#error{reason=Reason}) ->
121 | Reason.
122 |
123 | %% @doc returns an encoded errorr command
124 | -spec encode_error(Reason::string()) -> Data::binary().
125 | encode_error(Reason) when is_list(Reason) ->
126 | ReasonBin = list_to_binary(Reason),
127 | ReasonSize = byte_size(ReasonBin),
128 | <<5, "ERROR", ReasonSize, ReasonBin/binary>>.
129 |
130 | %%
131 | %% SUBSCRIBE functions
132 | %%
133 |
134 | %% @doc encode a subscribe command
135 | -spec encode_subscribe(Subscription::binary()) -> Command::binary().
136 | encode_subscribe(Subscription) when is_binary(Subscription) ->
137 | <<9, "SUBSCRIBE", Subscription/binary>>.
138 |
139 | %% @doc return subscription of subscribe command
140 | -spec subscribe_subscription(Command::subscribe()) -> Subscription::binary().
141 | subscribe_subscription(#subscribe{subscription=Subscription}) ->
142 | Subscription.
143 |
144 | %% @doc encode a cancel command
145 | -spec encode_cancel(Subscription::binary()) -> Command::binary().
146 | encode_cancel(Subscription) when is_binary(Subscription) ->
147 | <<6, "CANCEL", Subscription/binary>>.
148 |
149 | %% @doc return subscription of cancel command
150 | -spec cancel_subscription(Command::cancel()) -> Subscription::binary().
151 | cancel_subscription(#cancel{subscription=Subscription}) ->
152 | Subscription.
153 |
154 | %% Private API
155 | decode_message('READY', Body) ->
156 | try decode_ready_message(#ready{}, Body) of
157 | Message -> {ok, Message}
158 | catch
159 | error:{badmatch,_} ->
160 | {error, wrong_ready_message}
161 | end;
162 |
163 | decode_message('PING', _Body) ->
164 | {ok, #ping{}};
165 |
166 | decode_message('ERROR', Body) ->
167 | try decode_error_message(Body) of
168 | Message -> {ok, Message}
169 | catch
170 | error:{badmatch,_} ->
171 | {error, wrong_error_message}
172 | end;
173 |
174 | decode_message('SUBSCRIBE', Body) ->
175 | {ok, #subscribe{subscription=Body}};
176 |
177 | decode_message('CANCEL', Body) ->
178 | {ok, #cancel{subscription=Body}}.
179 |
180 | %% Decode Ready message utils
181 | decode_ready_message(Command, <<"">>) ->
182 | Command;
183 |
184 | decode_ready_message(Command, <>) ->
185 | PropertyNameBitLen = PropertyNameLen * 8,
186 | <> = Part1,
187 |
188 | <> = Part2,
189 | PropertyValueBitLen = PropertyValueLen * 8,
190 | <> = Part3,
191 |
192 | Name = string:to_lower(binary_to_list(PropertyName)),
193 | Value = binary_to_list(PropertyValue),
194 | UpdatedCommand = append_ready_property(Command, Name, Value),
195 | decode_ready_message(UpdatedCommand, Part4).
196 |
197 | append_ready_property(ReadyCommand, "socket-type", SocketType) ->
198 | ReadyCommand#ready{
199 | socket_type=list_to_atom(string:to_lower(SocketType))
200 | };
201 | append_ready_property(ReadyCommand, "identity", Identity) ->
202 | ReadyCommand#ready{identity=Identity};
203 | append_ready_property(ReadyCommand, "resource", Resource) ->
204 | ReadyCommand#ready{resource=Resource};
205 | append_ready_property(#ready{metadata=MetaData}=ReadyCommand, Name, Value) ->
206 | ReadyCommand#ready{metadata=MetaData#{Name=>Value}}.
207 |
208 | encode_ready_properties([]) ->
209 | <<"">>;
210 | encode_ready_properties([{_Name, ""}|Properties]) ->
211 | encode_ready_properties(Properties);
212 | encode_ready_properties([{Name, Value}|Properties]) ->
213 | NameBin = list_to_binary(Name),
214 | ValueBin = list_to_binary(Value),
215 | NameLen = byte_size(NameBin),
216 | ValueLen = byte_size(ValueBin),
217 | Tail = encode_ready_properties(Properties),
218 | <>.
219 |
220 | decode_error_message(Body) ->
221 | <> = Body,
222 | <> = RemaingBody,
223 | #error{reason=binary_to_list(Reason)}.
224 |
--------------------------------------------------------------------------------
/test/erlangzmq_acceptance_pub_with_sub.erl:
--------------------------------------------------------------------------------
1 | %% @copyright 2016 Choven Corp.
2 | %%
3 | %% This file is part of erlangzmq.
4 | %%
5 | %% erlangzmq is free software: you can redistribute it and/or modify
6 | %% it under the terms of the GNU Affero General Public License as published by
7 | %% the Free Software Foundation, either version 3 of the License, or
8 | %% (at your option) any later version.
9 | %%
10 | %% erlangzmq is distributed in the hope that it will be useful,
11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | %% GNU Affero General Public License for more details.
14 | %%
15 | %% You should have received a copy of the GNU Affero General Public License
16 | %% along with erlangzmq. If not, see
17 |
18 | -module(erlangzmq_acceptance_pub_with_sub).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | -define(PORT, 5586).
22 |
23 | normal_test_() ->
24 | [
25 | {
26 | "Should deny PUB to recv message",
27 | {setup, fun start/0, fun stop/1, fun deny_pub_to_receive/1}
28 | }
29 | , {
30 | "Should deny SUB to send message",
31 | {setup, fun start/0, fun stop/1, fun deny_sub_to_send/1}
32 | }
33 | , {
34 | "Should deliver message for all subscribers",
35 | {setup, fun start/0, fun stop/1, fun negotiate_subcriptions_without_multipart/1}
36 | }
37 | , {
38 | "Should deliver message for all subscribers using multipart",
39 | {setup, fun start/0, fun stop/1, fun negotiate_subcriptions_with_multipart/1}
40 | }
41 | , {
42 | "Should deliver message for all subscribers that matching with pattern",
43 | {setup, fun start/0, fun stop/1, fun negotiate_subcriptions_with_matching/1}
44 | }
45 | , {
46 | "Should resend subscriptions when reconnection ocurred",
47 | {setup, fun start/0, fun stop/1, fun negotiate_subcriptions_with_reconnect/1}
48 | }
49 | , {
50 | "Should allow to cancel and make other subscriptions",
51 | {setup, fun start/0, fun stop/1, fun cancel_and_remake_subscriptions/1}
52 | }
53 | ].
54 |
55 | start() ->
56 | application:ensure_started(erlangzmq),
57 | {ok, Socket} = erlangzmq:socket(pub),
58 | {ok, _BindPid} = erlangzmq:bind(Socket, tcp, "localhost", ?PORT),
59 | Socket.
60 |
61 | start_worker(Identity, SubscribeTopic, Func) ->
62 | Parent = self(),
63 | spawn_link(
64 | fun () ->
65 | {ok, Socket} = erlangzmq:socket(sub, Identity),
66 | erlangzmq:subscribe(Socket, SubscribeTopic),
67 | {ok, PeerPid} = erlangzmq:connect(Socket, tcp, "localhost", ?PORT),
68 |
69 | case erlang:fun_info(Func, arity) of
70 | {arity, 3} ->
71 | Func(Socket, Identity, Parent);
72 | {arity, 4} ->
73 | Func(Socket, PeerPid, Identity, Parent)
74 | end
75 | end
76 | ).
77 |
78 | stop(Pid) ->
79 | gen_server:stop(Pid).
80 |
81 | deny_pub_to_receive(Socket) ->
82 | R1 = erlangzmq:recv(Socket),
83 | R2 = erlangzmq:recv_multipart(Socket),
84 |
85 | [
86 | ?_assertEqual({error, not_use}, R1),
87 | ?_assertEqual({error, not_use}, R2)
88 | ].
89 |
90 | deny_sub_to_send(_Socket) ->
91 | {ok, ClientSocket} = erlangzmq:socket(sub),
92 | {ok, _PeerPid} = erlangzmq:connect(ClientSocket, tcp, "localhost", ?PORT),
93 |
94 | R1 = erlangzmq:send(ClientSocket, <<"oi">>),
95 | R2 = erlangzmq:send(ClientSocket, <<"oi">>),
96 | [
97 | ?_assertEqual({error, not_use}, R1),
98 | ?_assertEqual({error, not_use}, R2)
99 | ].
100 |
101 | negotiate_subcriptions_without_multipart(Socket) ->
102 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
103 | {ok, Message} = erlangzmq:recv(ClientSocket),
104 | Parent ! {recv, Identity, Message}
105 | end,
106 | start_worker("SUB-A", <<>>, NegociateFunc),
107 | start_worker("SUB-B", <<>>, NegociateFunc),
108 | timer:sleep(200),
109 | ok = erlangzmq:send(Socket, <<"Ready">>),
110 |
111 | MessageA = receive
112 | {recv, "SUB-A", MultipartA} ->
113 | MultipartA
114 | end,
115 |
116 | MessageB = receive
117 | {recv, "SUB-B", MultipartB} ->
118 | MultipartB
119 | end,
120 |
121 | [
122 | ?_assertEqual(MessageA, <<"Ready">>),
123 | ?_assertEqual(MessageB, <<"Ready">>)
124 | ].
125 |
126 | negotiate_subcriptions_with_multipart(Socket) ->
127 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
128 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
129 | Parent ! {recv, Identity, Message}
130 | end,
131 | start_worker("SUB-C", <<>>, NegociateFunc),
132 | start_worker("SUB-D", <<>>, NegociateFunc),
133 | timer:sleep(200),
134 | ok = erlangzmq:send_multipart(Socket, [<<"Ready">>, <<"OK">>]),
135 |
136 | MessageA = receive
137 | {recv, "SUB-C", MultipartA} ->
138 | MultipartA
139 | end,
140 |
141 | MessageB = receive
142 | {recv, "SUB-D", MultipartB} ->
143 | MultipartB
144 | end,
145 |
146 | [
147 | ?_assertEqual(MessageA, [<<"Ready">>, <<"OK">>]),
148 | ?_assertEqual(MessageB, [<<"Ready">>, <<"OK">>])
149 | ].
150 |
151 | negotiate_subcriptions_with_matching(Socket) ->
152 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
153 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
154 | Parent ! {recv, Identity, Message}
155 | end,
156 | start_worker("SUB-E", <<"debug">>, NegociateFunc),
157 | start_worker("SUB-F", <<"info">>, NegociateFunc),
158 | timer:sleep(200),
159 | ok = erlangzmq:send_multipart(Socket, [<<"debug">>,<<"DebugReady">>]),
160 | ok = erlangzmq:send_multipart(Socket, [<<"info">>,<<"InfoReady">>]),
161 |
162 | MessageA = receive
163 | {recv, "SUB-E", MultipartA} ->
164 | MultipartA
165 | end,
166 |
167 | MessageB = receive
168 | {recv, "SUB-F", MultipartB} ->
169 | MultipartB
170 | end,
171 |
172 | [
173 | ?_assertEqual([<<"debug">>, <<"DebugReady">>], MessageA),
174 | ?_assertEqual([<<"info">>, <<"InfoReady">>], MessageB)
175 | ].
176 |
177 | negotiate_subcriptions_with_reconnect(Socket) ->
178 | NegociateFunc = fun (ClientSocket, PeerPid, Identity, Parent) ->
179 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
180 | Parent ! {recv, Identity, Message},
181 | erlangzmq_peer:reconnect(PeerPid),
182 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
183 | Parent ! {recv, Identity, Message}
184 | end,
185 | start_worker("SUB-G", <<"A">>, NegociateFunc),
186 | timer:sleep(200),
187 | ok = erlangzmq:send_multipart(Socket, [<<"A">>, <<"Message A">>]),
188 | ok = erlangzmq:send_multipart(Socket, [<<"B">>, <<"Message B">>]),
189 |
190 | Message1 = receive
191 | {recv, "SUB-G", MultipartA} ->
192 | MultipartA
193 | end,
194 | timer:sleep(300), %% waits for reconnection
195 | ok = erlangzmq:send_multipart(Socket, [<<"A">>, <<"Message A">>]),
196 | ok = erlangzmq:send_multipart(Socket, [<<"B">>, <<"Message B">>]),
197 |
198 | Message2 = receive
199 | {recv, "SUB-G", MultipartB} ->
200 | MultipartB
201 | end,
202 |
203 | [
204 | ?_assertEqual([<<"A">>, <<"Message A">>], Message1),
205 | ?_assertEqual([<<"A">>, <<"Message A">>], Message2)
206 | ].
207 |
208 | cancel_and_remake_subscriptions(Socket) ->
209 | NegociateFunc = fun (ClientSocket, Identity, Parent) ->
210 | erlangzmq:cancel(ClientSocket, <<"Z">>),
211 | erlangzmq:subscribe(ClientSocket, <<"W">>),
212 | {ok, Message} = erlangzmq:recv_multipart(ClientSocket),
213 | Parent ! {recv, Identity, Message}
214 | end,
215 | start_worker("SUB-H", <<"Z">>, NegociateFunc),
216 |
217 | %% waits the negotiation
218 | timer:sleep(400),
219 | ok = erlangzmq:send_multipart(Socket, [<<"Z">>, <<"Message Z">>]),
220 | ok = erlangzmq:send_multipart(Socket, [<<"W">>, <<"Message W">>]),
221 |
222 | Message = receive
223 | {recv, "SUB-H", MultipartA} ->
224 | MultipartA
225 | end,
226 | [
227 | ?_assertEqual([<<"W">>, <<"Message W">>], Message)
228 | ].
229 |
--------------------------------------------------------------------------------