├── 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 | ![System Map](images/erlangzmq_system_map.png) 13 | 14 | Sockets 15 | ------- 16 | 17 | Each socket creates a peer process for each remote peer it communicates with. 18 | 19 | ![Socket Composition Diagram](images/erlangzmq_socket.png) 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 | ![Entity Relationship Diagram](images/erlangzmq_entities.png) 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 | ![Contributing flow](docs/images/contributing.png) 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 | --------------------------------------------------------------------------------