├── rebar ├── .gitignore ├── .travis.yml ├── src ├── amqp_client.app.src ├── amqp_client.erl ├── amqp_sup.erl ├── amqp_channel_sup_sup.erl ├── amqp_auth_mechanisms.erl ├── amqp_connection_sup.erl ├── amqp_channel_sup.erl ├── amqp_direct_consumer.erl ├── amqp_connection_type_sup.erl ├── uri_parser.erl ├── amqp_rpc_server.erl ├── amqp_main_reader.erl ├── amqp_rpc_client.erl ├── rabbit_ct_client_helpers.erl ├── amqp_direct_connection.erl ├── rabbit_routing_util.erl ├── amqp_channels_manager.erl ├── amqp_selective_consumer.erl ├── amqp_uri.erl ├── amqp_gen_consumer.erl ├── amqp_gen_connection.erl ├── amqp_network_connection.erl └── amqp_connection.erl ├── rebar.config ├── Makefile ├── include ├── rabbit_routing_prefixes.hrl ├── amqp_client_internal.hrl ├── amqp_gen_consumer_spec.hrl └── amqp_client.hrl ├── README.md └── erlang.mk /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbrisbin/amqp_client/HEAD/rebar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | ebin 3 | deps 4 | .rebar 5 | .erlang.* 6 | .idea 7 | *.iml 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | 3 | sudo: false 4 | 5 | otp_release: 6 | - 17.5 7 | - 18.3 8 | - 19.0 9 | 10 | script: "make" 11 | 12 | notifications: 13 | recipients: 14 | - jon@jbrisbin.com 15 | -------------------------------------------------------------------------------- /src/amqp_client.app.src: -------------------------------------------------------------------------------- 1 | {application, amqp_client, 2 | [{description, "RabbitMQ AMQP Client"}, 3 | {vsn, "3.6.2"}, 4 | {modules, []}, 5 | {registered, [amqp_sup]}, 6 | {env, [{prefer_ipv6, false}, 7 | {ssl_options, []}]}, 8 | {mod, {amqp_client, []}}, 9 | {applications, [kernel, stdlib, xmerl, rabbit_common]}]}. 10 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {require_otp_vsn, "17|18|19"}. 2 | 3 | {deps, [ 4 | {rabbit_common, ".*", {git, "https://github.com/jbrisbin/rabbit_common.git", ""}} 5 | ]}. 6 | 7 | {erl_opts, [ 8 | debug_info, 9 | compressed, 10 | report, 11 | warn_export_all, 12 | warn_export_vars, 13 | warn_shadow_vars, 14 | warn_unused_function, 15 | warn_deprecated_function, 16 | warn_obsolete_guard, 17 | warn_unused_import 18 | ]}. 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = amqp_client 2 | 3 | DEPS = rabbit_common 4 | 5 | ERLC_OPTS = +debug_info \ 6 | +compressed \ 7 | +report \ 8 | +warn_export_all \ 9 | +warn_export_vars \ 10 | +warn_shadow_vars \ 11 | +warn_unused_function \ 12 | +warn_deprecated_function \ 13 | +warn_obsolete_guard \ 14 | +warn_unused_import \ 15 | +nowarn_export_vars 16 | 17 | 18 | COMPILE_FIRST = amqp_gen_consumer \ 19 | amqp_gen_connection 20 | 21 | dep_rabbit_common = git git://github.com/jbrisbin/rabbit_common.git rabbitmq-3.5.6 22 | 23 | include erlang.mk 24 | -------------------------------------------------------------------------------- /include/rabbit_routing_prefixes.hrl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License 4 | %% at http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %% the License for the specific language governing rights and 9 | %% limitations under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2011-2015 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | -define(QUEUE_PREFIX, "/queue"). 18 | -define(TOPIC_PREFIX, "/topic"). 19 | -define(EXCHANGE_PREFIX, "/exchange"). 20 | -define(AMQQUEUE_PREFIX, "/amq/queue"). 21 | -define(TEMP_QUEUE_PREFIX, "/temp-queue"). 22 | %% reply queues names can have slashes in the content so no further 23 | %% parsing happens. 24 | -define(REPLY_QUEUE_PREFIX, "/reply-queue/"). 25 | -------------------------------------------------------------------------------- /src/amqp_client.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_client). 19 | 20 | -behaviour(application). 21 | 22 | -export([start/0]). 23 | -export([start/2, stop/1]). 24 | 25 | %%--------------------------------------------------------------------------- 26 | %% Interface 27 | %%--------------------------------------------------------------------------- 28 | 29 | start() -> 30 | application:start(rabbit_common), 31 | application:start(amqp_client). 32 | 33 | %%--------------------------------------------------------------------------- 34 | %% application callbacks 35 | %%--------------------------------------------------------------------------- 36 | 37 | start(_StartType, _StartArgs) -> 38 | amqp_sup:start_link(). 39 | 40 | stop(_State) -> 41 | ok. 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rebar-friendly fork of Erlang AMQP client [![Build Status](https://travis-ci.org/jbrisbin/amqp_client.svg?branch=master)](https://travis-ci.org/jbrisbin/amqp_client) 2 | 3 | This is a fork of the [official RabbitMQ/AMQP Erlang client](https://github.com/rabbitmq/rabbitmq-erlang-client). 4 | 5 | It's meant to be included in your rebar projects in your rebar.config file: 6 | 7 | {deps, [ 8 | {amqp_client, ".*", {git, "git://github.com/jbrisbin/amqp_client.git", {tag, "rabbitmq-3.6.2"}}} 9 | ]}. 10 | 11 | The "master" branch of this port is a simple re-packaging of the rabbit_common AMQP client dependency. 12 | 13 | The "community" branch, however, is a port of the RabbitMQ source code with additional strict compilation checking turned on and the source code edited to eliminate warnings. It should be 100% compatible with the unaltered source code. The community branch is simply a tweak to allow projects that depend on rabbit_common to not have to deal with the warnings issued by the compiler in the unaltered RabbitMQ code. 14 | 15 | To use the "community" branch in your project, which includes stricter compilation settings, add "-community" 16 | to the version tag: 17 | 18 | {deps, [ 19 | {amqp_client, ".*", {git, "git://github.com/jbrisbin/amqp_client.git", {tag, "rabbitmq-3.5.6-community"}}} 20 | ]}. 21 | 22 | ### License 23 | 24 | This package, just like the the RabbitMQ server, is licensed under the MPL. For the MPL, please see LICENSE-MPL-RabbitMQ. 25 | -------------------------------------------------------------------------------- /include/amqp_client_internal.hrl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | -include("amqp_client.hrl"). 18 | 19 | -define(PROTOCOL_VERSION_MAJOR, 0). 20 | -define(PROTOCOL_VERSION_MINOR, 9). 21 | -define(PROTOCOL_HEADER, <<"AMQP", 0, 0, 9, 1>>). 22 | -define(PROTOCOL, rabbit_framing_amqp_0_9_1). 23 | 24 | -define(MAX_CHANNEL_NUMBER, 65535). 25 | 26 | -define(LOG_DEBUG(Format), error_logger:info_msg(Format)). 27 | -define(LOG_INFO(Format, Args), error_logger:info_msg(Format, Args)). 28 | -define(LOG_WARN(Format, Args), error_logger:warning_msg(Format, Args)). 29 | -define(LOG_ERR(Format, Args), error_logger:error_msg(Format, Args)). 30 | 31 | -define(CLIENT_CAPABILITIES, 32 | [{<<"publisher_confirms">>, bool, true}, 33 | {<<"exchange_exchange_bindings">>, bool, true}, 34 | {<<"basic.nack">>, bool, true}, 35 | {<<"consumer_cancel_notify">>, bool, true}, 36 | {<<"connection.blocked">>, bool, true}, 37 | {<<"authentication_failure_close">>, bool, true}]). 38 | -------------------------------------------------------------------------------- /src/amqp_sup.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_sup). 19 | 20 | -include("amqp_client.hrl"). 21 | 22 | -behaviour(supervisor2). 23 | 24 | -export([start_link/0, is_ready/0, start_connection_sup/1]). 25 | -export([init/1]). 26 | 27 | %%--------------------------------------------------------------------------- 28 | %% Interface 29 | %%--------------------------------------------------------------------------- 30 | 31 | start_link() -> 32 | supervisor2:start_link({local, amqp_sup}, ?MODULE, []). 33 | 34 | is_ready() -> 35 | whereis(amqp_sup) =/= undefined. 36 | 37 | start_connection_sup(AmqpParams) -> 38 | supervisor2:start_child(amqp_sup, [AmqpParams]). 39 | 40 | %%--------------------------------------------------------------------------- 41 | %% supervisor2 callbacks 42 | %%--------------------------------------------------------------------------- 43 | 44 | init([]) -> 45 | {ok, {{simple_one_for_one, 0, 1}, 46 | [{connection_sup, {amqp_connection_sup, start_link, []}, 47 | temporary, infinity, supervisor, [amqp_connection_sup]}]}}. 48 | -------------------------------------------------------------------------------- /src/amqp_channel_sup_sup.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_channel_sup_sup). 19 | 20 | -include("amqp_client.hrl"). 21 | 22 | -behaviour(supervisor2). 23 | 24 | -export([start_link/3, start_channel_sup/4]). 25 | -export([init/1]). 26 | 27 | %%--------------------------------------------------------------------------- 28 | %% Interface 29 | %%--------------------------------------------------------------------------- 30 | 31 | start_link(Type, Connection, ConnName) -> 32 | supervisor2:start_link(?MODULE, [Type, Connection, ConnName]). 33 | 34 | start_channel_sup(Sup, InfraArgs, ChannelNumber, Consumer) -> 35 | supervisor2:start_child(Sup, [InfraArgs, ChannelNumber, Consumer]). 36 | 37 | %%--------------------------------------------------------------------------- 38 | %% supervisor2 callbacks 39 | %%--------------------------------------------------------------------------- 40 | 41 | init([Type, Connection, ConnName]) -> 42 | {ok, {{simple_one_for_one, 0, 1}, 43 | [{channel_sup, 44 | {amqp_channel_sup, start_link, [Type, Connection, ConnName]}, 45 | temporary, brutal_kill, supervisor, [amqp_channel_sup]}]}}. 46 | -------------------------------------------------------------------------------- /include/amqp_gen_consumer_spec.hrl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2011-2015 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | -include("amqp_client.hrl"). 18 | 19 | -ifdef(use_specs). 20 | -type(state() :: any()). 21 | -type(consume() :: #'basic.consume'{}). 22 | -type(consume_ok() :: #'basic.consume_ok'{}). 23 | -type(cancel() :: #'basic.cancel'{}). 24 | -type(cancel_ok() :: #'basic.cancel_ok'{}). 25 | -type(deliver() :: #'basic.deliver'{}). 26 | -type(from() :: any()). 27 | -type(reason() :: any()). 28 | -type(ok_error() :: {ok, state()} | {error, reason(), state()}). 29 | 30 | -spec(init([any()]) -> {ok, state()}). 31 | -spec(handle_consume(consume(), pid(), state()) -> ok_error()). 32 | -spec(handle_consume_ok(consume_ok(), consume(), state()) -> 33 | ok_error()). 34 | -spec(handle_cancel(cancel(), state()) -> ok_error()). 35 | -spec(handle_server_cancel(cancel(), state()) -> ok_error()). 36 | -spec(handle_cancel_ok(cancel_ok(), cancel(), state()) -> ok_error()). 37 | -spec(handle_deliver(deliver(), #amqp_msg{}, state()) -> ok_error()). 38 | -spec(handle_info(any(), state()) -> ok_error()). 39 | -spec(handle_call(any(), from(), state()) -> 40 | {reply, any(), state()} | {noreply, state()} | 41 | {error, reason(), state()}). 42 | -spec(terminate(any(), state()) -> state()). 43 | -endif. 44 | -------------------------------------------------------------------------------- /src/amqp_auth_mechanisms.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_auth_mechanisms). 19 | 20 | -include("amqp_client.hrl"). 21 | 22 | -export([plain/3, amqplain/3, external/3, crdemo/3]). 23 | 24 | %%--------------------------------------------------------------------------- 25 | 26 | plain(none, _, init) -> 27 | {<<"PLAIN">>, []}; 28 | plain(none, #amqp_params_network{username = Username, 29 | password = Password}, _State) -> 30 | {<<0, Username/binary, 0, Password/binary>>, _State}. 31 | 32 | amqplain(none, _, init) -> 33 | {<<"AMQPLAIN">>, []}; 34 | amqplain(none, #amqp_params_network{username = Username, 35 | password = Password}, _State) -> 36 | LoginTable = [{<<"LOGIN">>, longstr, Username}, 37 | {<<"PASSWORD">>, longstr, Password}], 38 | {rabbit_binary_generator:generate_table(LoginTable), _State}. 39 | 40 | external(none, _, init) -> 41 | {<<"EXTERNAL">>, []}; 42 | external(none, _, _State) -> 43 | {<<"">>, _State}. 44 | 45 | crdemo(none, _, init) -> 46 | {<<"RABBIT-CR-DEMO">>, 0}; 47 | crdemo(none, #amqp_params_network{username = Username}, 0) -> 48 | {Username, 1}; 49 | crdemo(<<"Please tell me your password">>, 50 | #amqp_params_network{password = Password}, 1) -> 51 | {<<"My password is ", Password/binary>>, 2}. 52 | -------------------------------------------------------------------------------- /src/amqp_connection_sup.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_connection_sup). 19 | 20 | -include("amqp_client.hrl"). 21 | 22 | -behaviour(supervisor2). 23 | 24 | -export([start_link/1]). 25 | -export([init/1]). 26 | 27 | %%--------------------------------------------------------------------------- 28 | %% Interface 29 | %%--------------------------------------------------------------------------- 30 | 31 | start_link(AMQPParams) -> 32 | {ok, Sup} = supervisor2:start_link(?MODULE, []), 33 | {ok, TypeSup} = supervisor2:start_child( 34 | Sup, {connection_type_sup, 35 | {amqp_connection_type_sup, start_link, []}, 36 | transient, infinity, supervisor, 37 | [amqp_connection_type_sup]}), 38 | {ok, Connection} = supervisor2:start_child( 39 | Sup, {connection, {amqp_gen_connection, start_link, 40 | [TypeSup, AMQPParams]}, 41 | intrinsic, brutal_kill, worker, 42 | [amqp_gen_connection]}), 43 | {ok, Sup, Connection}. 44 | 45 | %%--------------------------------------------------------------------------- 46 | %% supervisor2 callbacks 47 | %%--------------------------------------------------------------------------- 48 | 49 | init([]) -> 50 | {ok, {{one_for_all, 0, 1}, []}}. 51 | -------------------------------------------------------------------------------- /include/amqp_client.hrl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | -ifndef(AMQP_CLIENT_HRL). 18 | -define(AMQP_CLIENT_HRL, true). 19 | 20 | -include_lib("rabbit_common/include/rabbit.hrl"). 21 | -include_lib("rabbit_common/include/rabbit_framing.hrl"). 22 | 23 | -record(amqp_msg, {props = #'P_basic'{}, payload = <<>>}). 24 | 25 | -record(amqp_params_network, {username = <<"guest">>, 26 | password = <<"guest">>, 27 | virtual_host = <<"/">>, 28 | host = "localhost", 29 | port = undefined, 30 | channel_max = 0, 31 | frame_max = 0, 32 | heartbeat = 10, 33 | connection_timeout = infinity, 34 | ssl_options = none, 35 | auth_mechanisms = 36 | [fun amqp_auth_mechanisms:plain/3, 37 | fun amqp_auth_mechanisms:amqplain/3], 38 | client_properties = [], 39 | socket_options = []}). 40 | 41 | -record(amqp_params_direct, {username = none, 42 | password = none, 43 | virtual_host = <<"/">>, 44 | node = node(), 45 | adapter_info = none, 46 | client_properties = []}). 47 | 48 | -record(amqp_adapter_info, {host = unknown, 49 | port = unknown, 50 | peer_host = unknown, 51 | peer_port = unknown, 52 | name = unknown, 53 | protocol = unknown, 54 | additional_info = []}). 55 | 56 | -endif. 57 | -------------------------------------------------------------------------------- /src/amqp_channel_sup.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_channel_sup). 19 | 20 | -include("amqp_client_internal.hrl"). 21 | 22 | -behaviour(supervisor2). 23 | 24 | -export([start_link/6]). 25 | -export([init/1]). 26 | 27 | %%--------------------------------------------------------------------------- 28 | %% Interface 29 | %%--------------------------------------------------------------------------- 30 | 31 | start_link(Type, Connection, ConnName, InfraArgs, ChNumber, 32 | Consumer = {_, _}) -> 33 | Identity = {ConnName, ChNumber}, 34 | {ok, Sup} = supervisor2:start_link(?MODULE, [Consumer, Identity]), 35 | [{gen_consumer, ConsumerPid, _, _}] = supervisor2:which_children(Sup), 36 | {ok, ChPid} = supervisor2:start_child( 37 | Sup, {channel, 38 | {amqp_channel, start_link, 39 | [Type, Connection, ChNumber, ConsumerPid, Identity]}, 40 | intrinsic, ?MAX_WAIT, worker, [amqp_channel]}), 41 | Writer = start_writer(Sup, Type, InfraArgs, ConnName, ChNumber, ChPid), 42 | amqp_channel:set_writer(ChPid, Writer), 43 | {ok, AState} = init_command_assembler(Type), 44 | {ok, Sup, {ChPid, AState}}. 45 | 46 | %%--------------------------------------------------------------------------- 47 | %% Internal plumbing 48 | %%--------------------------------------------------------------------------- 49 | 50 | start_writer(_Sup, direct, [ConnPid, Node, User, VHost, Collector], 51 | ConnName, ChNumber, ChPid) -> 52 | {ok, RabbitCh} = 53 | rpc:call(Node, rabbit_direct, start_channel, 54 | [ChNumber, ChPid, ConnPid, ConnName, ?PROTOCOL, User, 55 | VHost, ?CLIENT_CAPABILITIES, Collector]), 56 | RabbitCh; 57 | start_writer(Sup, network, [Sock, FrameMax], ConnName, ChNumber, ChPid) -> 58 | {ok, Writer} = supervisor2:start_child( 59 | Sup, 60 | {writer, {rabbit_writer, start_link, 61 | [Sock, ChNumber, FrameMax, ?PROTOCOL, ChPid, 62 | {ConnName, ChNumber}]}, 63 | transient, ?MAX_WAIT, worker, [rabbit_writer]}), 64 | Writer. 65 | 66 | init_command_assembler(direct) -> {ok, none}; 67 | init_command_assembler(network) -> rabbit_command_assembler:init(?PROTOCOL). 68 | 69 | %%--------------------------------------------------------------------------- 70 | %% supervisor2 callbacks 71 | %%--------------------------------------------------------------------------- 72 | 73 | init([{ConsumerModule, ConsumerArgs}, Identity]) -> 74 | {ok, {{one_for_all, 0, 1}, 75 | [{gen_consumer, {amqp_gen_consumer, start_link, 76 | [ConsumerModule, ConsumerArgs, Identity]}, 77 | intrinsic, ?MAX_WAIT, worker, [amqp_gen_consumer]}]}}. 78 | -------------------------------------------------------------------------------- /src/amqp_direct_consumer.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2011-2015 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @doc This module is an implementation of the amqp_gen_consumer 18 | %% behaviour and can be used as part of the Consumer parameter when 19 | %% opening AMQP channels. 20 | %%
21 | %%
22 | %% The Consumer parameter for this implementation is {{@module}, 23 | %% [ConsumerPid]@}, where ConsumerPid is a process that will receive 24 | %% queue subscription-related messages.
25 | %%
26 | %% This consumer implementation causes the channel to send to the 27 | %% ConsumerPid all basic.consume, basic.consume_ok, basic.cancel, 28 | %% basic.cancel_ok and basic.deliver messages received from the 29 | %% server. 30 | %%
31 | %%
32 | %% In addition, this consumer implementation monitors the ConsumerPid 33 | %% and exits with the same shutdown reason when it dies. 'DOWN' 34 | %% messages from other sources are passed to ConsumerPid. 35 | %%
36 | %% Warning! It is not recommended to rely on a consumer on killing off the 37 | %% channel (through the exit signal). That may cause messages to get lost. 38 | %% Always use amqp_channel:close/{1,3} for a clean shut down.
39 | %%
40 | %% This module has no public functions. 41 | -module(amqp_direct_consumer). 42 | 43 | -include("amqp_gen_consumer_spec.hrl"). 44 | 45 | -behaviour(amqp_gen_consumer). 46 | 47 | -export([init/1, handle_consume_ok/3, handle_consume/3, handle_cancel_ok/3, 48 | handle_cancel/2, handle_server_cancel/2, 49 | handle_deliver/3, handle_deliver/4, 50 | handle_info/2, handle_call/3, terminate/2]). 51 | 52 | %%--------------------------------------------------------------------------- 53 | %% amqp_gen_consumer callbacks 54 | %%--------------------------------------------------------------------------- 55 | 56 | %% @private 57 | init([ConsumerPid]) -> 58 | erlang:monitor(process, ConsumerPid), 59 | {ok, ConsumerPid}. 60 | 61 | %% @private 62 | handle_consume(M, A, C) -> 63 | C ! {M, A}, 64 | {ok, C}. 65 | 66 | %% @private 67 | handle_consume_ok(M, _, C) -> 68 | C ! M, 69 | {ok, C}. 70 | 71 | %% @private 72 | handle_cancel(M, C) -> 73 | C ! M, 74 | {ok, C}. 75 | 76 | %% @private 77 | handle_cancel_ok(M, _, C) -> 78 | C ! M, 79 | {ok, C}. 80 | 81 | %% @private 82 | handle_server_cancel(M, C) -> 83 | C ! {server_cancel, M}, 84 | {ok, C}. 85 | 86 | %% @private 87 | handle_deliver(M, A, C) -> 88 | C ! {M, A}, 89 | {ok, C}. 90 | handle_deliver(M, A, DeliveryCtx, C) -> 91 | C ! {M, A, DeliveryCtx}, 92 | {ok, C}. 93 | 94 | 95 | %% @private 96 | handle_info({'DOWN', _MRef, process, C, normal}, C) -> 97 | %% The channel was closed. 98 | {ok, C}; 99 | handle_info({'DOWN', _MRef, process, C, Info}, C) -> 100 | {error, {consumer_died, Info}, C}; 101 | handle_info({'DOWN', MRef, process, Pid, Info}, C) -> 102 | C ! {'DOWN', MRef, process, Pid, Info}, 103 | {ok, C}. 104 | 105 | %% @private 106 | handle_call(M, A, C) -> 107 | C ! {M, A}, 108 | {reply, ok, C}. 109 | 110 | %% @private 111 | terminate(_Reason, C) -> 112 | C. 113 | -------------------------------------------------------------------------------- /src/amqp_connection_type_sup.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_connection_type_sup). 19 | 20 | -include("amqp_client_internal.hrl"). 21 | 22 | -behaviour(supervisor2). 23 | 24 | -export([start_link/0, start_infrastructure_fun/3, type_module/1]). 25 | 26 | -export([init/1]). 27 | 28 | %%--------------------------------------------------------------------------- 29 | %% Interface 30 | %%--------------------------------------------------------------------------- 31 | 32 | start_link() -> 33 | supervisor2:start_link(?MODULE, []). 34 | 35 | type_module(#amqp_params_direct{}) -> {direct, amqp_direct_connection}; 36 | type_module(#amqp_params_network{}) -> {network, amqp_network_connection}. 37 | 38 | %%--------------------------------------------------------------------------- 39 | 40 | start_channels_manager(Sup, Conn, ConnName, Type) -> 41 | {ok, ChSupSup} = supervisor2:start_child( 42 | Sup, 43 | {channel_sup_sup, {amqp_channel_sup_sup, start_link, 44 | [Type, Conn, ConnName]}, 45 | intrinsic, infinity, supervisor, 46 | [amqp_channel_sup_sup]}), 47 | {ok, _} = supervisor2:start_child( 48 | Sup, 49 | {channels_manager, {amqp_channels_manager, start_link, 50 | [Conn, ConnName, ChSupSup]}, 51 | transient, ?MAX_WAIT, worker, [amqp_channels_manager]}). 52 | 53 | start_infrastructure_fun(Sup, Conn, network) -> 54 | fun (Sock, ConnName) -> 55 | {ok, ChMgr} = start_channels_manager(Sup, Conn, ConnName, network), 56 | {ok, AState} = rabbit_command_assembler:init(?PROTOCOL), 57 | {ok, Writer} = 58 | supervisor2:start_child( 59 | Sup, 60 | {writer, 61 | {rabbit_writer, start_link, 62 | [Sock, 0, ?FRAME_MIN_SIZE, ?PROTOCOL, Conn, ConnName]}, 63 | transient, ?MAX_WAIT, worker, [rabbit_writer]}), 64 | {ok, _Reader} = 65 | supervisor2:start_child( 66 | Sup, 67 | {main_reader, {amqp_main_reader, start_link, 68 | [Sock, Conn, ChMgr, AState, ConnName]}, 69 | transient, ?MAX_WAIT, worker, [amqp_main_reader]}), 70 | {ok, ChMgr, Writer} 71 | end; 72 | start_infrastructure_fun(Sup, Conn, direct) -> 73 | fun (ConnName) -> 74 | {ok, ChMgr} = start_channels_manager(Sup, Conn, ConnName, direct), 75 | {ok, Collector} = 76 | supervisor2:start_child( 77 | Sup, 78 | {collector, {rabbit_queue_collector, start_link, [ConnName]}, 79 | transient, ?MAX_WAIT, worker, [rabbit_queue_collector]}), 80 | {ok, ChMgr, Collector} 81 | end. 82 | 83 | %%--------------------------------------------------------------------------- 84 | %% supervisor2 callbacks 85 | %%--------------------------------------------------------------------------- 86 | 87 | init([]) -> 88 | {ok, {{one_for_all, 0, 1}, []}}. 89 | -------------------------------------------------------------------------------- /src/uri_parser.erl: -------------------------------------------------------------------------------- 1 | %% This file is a copy of http_uri.erl from the R13B-1 Erlang/OTP 2 | %% distribution with several modifications. 3 | 4 | %% All modifications are Copyright (c) 2009-2014 GoPivotal, Ltd. 5 | 6 | %% ``The contents of this file are subject to the Erlang Public License, 7 | %% Version 1.1, (the "License"); you may not use this file except in 8 | %% compliance with the License. You should have received a copy of the 9 | %% Erlang Public License along with this software. If not, it can be 10 | %% retrieved via the world wide web at http://www.erlang.org/. 11 | %% 12 | %% Software distributed under the License is distributed on an "AS IS" 13 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 14 | %% the License for the specific language governing rights and limitations 15 | %% under the License. 16 | %% 17 | %% The Initial Developer of the Original Code is Ericsson Utvecklings AB. 18 | %% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings 19 | %% AB. All Rights Reserved.'' 20 | 21 | %% See http://tools.ietf.org/html/rfc3986 22 | 23 | -module(uri_parser). 24 | 25 | -export([parse/2]). 26 | 27 | %%%========================================================================= 28 | %%% API 29 | %%%========================================================================= 30 | 31 | %% Returns a key list of elements extracted from the URI. Note that 32 | %% only 'scheme' is guaranteed to exist. Key-Value pairs from the 33 | %% Defaults list will be used absence of a non-empty value extracted 34 | %% from the URI. The values extracted are strings, except for 'port' 35 | %% which is an integer, 'userinfo' which is a list of strings (split 36 | %% on $:), and 'query' which is a list of strings where no $= char 37 | %% found, or a {key,value} pair where a $= char is found (initial 38 | %% split on $& and subsequent optional split on $=). Possible keys 39 | %% are: 'scheme', 'userinfo', 'host', 'port', 'path', 'query', 40 | %% 'fragment'. 41 | 42 | parse(AbsURI, Defaults) -> 43 | case parse_scheme(AbsURI) of 44 | {error, Reason} -> 45 | {error, Reason}; 46 | {Scheme, Rest} -> 47 | case (catch parse_uri_rest(Rest, true)) of 48 | [_|_] = List -> 49 | merge_keylists([{scheme, Scheme} | List], Defaults); 50 | E -> 51 | {error, {malformed_uri, AbsURI, E}} 52 | end 53 | end. 54 | 55 | %%%======================================================================== 56 | %%% Internal functions 57 | %%%======================================================================== 58 | parse_scheme(AbsURI) -> 59 | split_uri(AbsURI, ":", {error, no_scheme}). 60 | 61 | parse_uri_rest("//" ++ URIPart, true) -> 62 | %% we have an authority 63 | {Authority, PathQueryFrag} = 64 | split_uri(URIPart, "/|\\?|#", {URIPart, ""}, 1, 0), 65 | AuthorityParts = parse_authority(Authority), 66 | parse_uri_rest(PathQueryFrag, false) ++ AuthorityParts; 67 | parse_uri_rest(PathQueryFrag, _Bool) -> 68 | %% no authority, just a path and maybe query 69 | {PathQuery, Frag} = split_uri(PathQueryFrag, "#", {PathQueryFrag, ""}), 70 | {Path, QueryString} = split_uri(PathQuery, "\\?", {PathQuery, ""}), 71 | QueryPropList = split_query(QueryString), 72 | [{path, Path}, {'query', QueryPropList}, {fragment, Frag}]. 73 | 74 | parse_authority(Authority) -> 75 | {UserInfo, HostPort} = split_uri(Authority, "@", {"", Authority}), 76 | UserInfoSplit = case re:split(UserInfo, ":", [{return, list}]) of 77 | [""] -> []; 78 | UIS -> UIS 79 | end, 80 | [{userinfo, UserInfoSplit} | parse_host_port(HostPort)]. 81 | 82 | parse_host_port("[" ++ HostPort) -> %ipv6 83 | {Host, ColonPort} = split_uri(HostPort, "\\]", {HostPort, ""}), 84 | [{host, Host} | case split_uri(ColonPort, ":", not_found, 0, 1) of 85 | not_found -> case ColonPort of 86 | [] -> []; 87 | _ -> throw({invalid_port, ColonPort}) 88 | end; 89 | {_, Port} -> [{port, list_to_integer(Port)}] 90 | end]; 91 | 92 | parse_host_port(HostPort) -> 93 | {Host, Port} = split_uri(HostPort, ":", {HostPort, not_found}), 94 | [{host, Host} | case Port of 95 | not_found -> []; 96 | _ -> [{port, list_to_integer(Port)}] 97 | end]. 98 | 99 | split_query(Query) -> 100 | case re:split(Query, "&", [{return, list}]) of 101 | [""] -> []; 102 | QParams -> [split_uri(Param, "=", Param) || Param <- QParams] 103 | end. 104 | 105 | split_uri(UriPart, SplitChar, NoMatchResult) -> 106 | split_uri(UriPart, SplitChar, NoMatchResult, 1, 1). 107 | 108 | split_uri(UriPart, SplitChar, NoMatchResult, SkipLeft, SkipRight) -> 109 | case re:run(UriPart, SplitChar) of 110 | {match, [{Match, _}]} -> 111 | {string:substr(UriPart, 1, Match + 1 - SkipLeft), 112 | string:substr(UriPart, Match + 1 + SkipRight, length(UriPart))}; 113 | nomatch -> 114 | NoMatchResult 115 | end. 116 | 117 | merge_keylists(A, B) -> 118 | {AEmpty, ANonEmpty} = lists:partition(fun ({_Key, V}) -> V =:= [] end, A), 119 | [AEmptyS, ANonEmptyS, BS] = 120 | [lists:ukeysort(1, X) || X <- [AEmpty, ANonEmpty, B]], 121 | lists:ukeymerge(1, lists:ukeymerge(1, ANonEmptyS, BS), AEmptyS). 122 | -------------------------------------------------------------------------------- /src/amqp_rpc_server.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @doc This is a utility module that is used to expose an arbitrary function 18 | %% via an asynchronous RPC over AMQP mechanism. It frees the implementor of 19 | %% a simple function from having to plumb this into AMQP. Note that the 20 | %% RPC server does not handle any data encoding, so it is up to the callback 21 | %% function to marshall and unmarshall message payloads accordingly. 22 | -module(amqp_rpc_server). 23 | 24 | -behaviour(gen_server). 25 | 26 | -include("amqp_client.hrl"). 27 | 28 | -export([init/1, terminate/2, code_change/3, handle_call/3, 29 | handle_cast/2, handle_info/2]). 30 | -export([start/3, start_link/3]). 31 | -export([stop/1]). 32 | 33 | -record(state, {channel, 34 | handler}). 35 | 36 | %%-------------------------------------------------------------------------- 37 | %% API 38 | %%-------------------------------------------------------------------------- 39 | 40 | %% @spec (Connection, Queue, RpcHandler) -> RpcServer 41 | %% where 42 | %% Connection = pid() 43 | %% Queue = binary() 44 | %% RpcHandler = function() 45 | %% RpcServer = pid() 46 | %% @doc Starts a new RPC server instance that receives requests via a 47 | %% specified queue and dispatches them to a specified handler function. This 48 | %% function returns the pid of the RPC server that can be used to stop the 49 | %% server. 50 | start(Connection, Queue, Fun) -> 51 | {ok, Pid} = gen_server:start(?MODULE, [Connection, Queue, Fun], []), 52 | Pid. 53 | 54 | %% @spec (Connection, Queue, RpcHandler) -> RpcServer 55 | %% where 56 | %% Connection = pid() 57 | %% Queue = binary() 58 | %% RpcHandler = function() 59 | %% RpcServer = pid() 60 | %% @doc Starts, and links to, a new RPC server instance that receives 61 | %% requests via a specified queue and dispatches them to a specified 62 | %% handler function. This function returns the pid of the RPC server that 63 | %% can be used to stop the server. 64 | start_link(Connection, Queue, Fun) -> 65 | {ok, Pid} = gen_server:start_link(?MODULE, [Connection, Queue, Fun], []), 66 | Pid. 67 | 68 | %% @spec (RpcServer) -> ok 69 | %% where 70 | %% RpcServer = pid() 71 | %% @doc Stops an exisiting RPC server. 72 | stop(Pid) -> 73 | gen_server:call(Pid, stop, infinity). 74 | 75 | %%-------------------------------------------------------------------------- 76 | %% gen_server callbacks 77 | %%-------------------------------------------------------------------------- 78 | 79 | %% @private 80 | init([Connection, Q, Fun]) -> 81 | {ok, Channel} = amqp_connection:open_channel( 82 | Connection, {amqp_direct_consumer, [self()]}), 83 | amqp_channel:call(Channel, #'queue.declare'{queue = Q}), 84 | amqp_channel:call(Channel, #'basic.consume'{queue = Q}), 85 | {ok, #state{channel = Channel, handler = Fun} }. 86 | 87 | %% @private 88 | handle_info(shutdown, State) -> 89 | {stop, normal, State}; 90 | 91 | %% @private 92 | handle_info({#'basic.consume'{}, _}, State) -> 93 | {noreply, State}; 94 | 95 | %% @private 96 | handle_info(#'basic.consume_ok'{}, State) -> 97 | {noreply, State}; 98 | 99 | %% @private 100 | handle_info(#'basic.cancel'{}, State) -> 101 | {noreply, State}; 102 | 103 | %% @private 104 | handle_info(#'basic.cancel_ok'{}, State) -> 105 | {stop, normal, State}; 106 | 107 | %% @private 108 | handle_info({#'basic.deliver'{delivery_tag = DeliveryTag}, 109 | #amqp_msg{props = Props, payload = Payload}}, 110 | State = #state{handler = Fun, channel = Channel}) -> 111 | #'P_basic'{correlation_id = CorrelationId, 112 | reply_to = Q} = Props, 113 | Response = Fun(Payload), 114 | Properties = #'P_basic'{correlation_id = CorrelationId}, 115 | Publish = #'basic.publish'{exchange = <<>>, 116 | routing_key = Q, 117 | mandatory = true}, 118 | amqp_channel:call(Channel, Publish, #amqp_msg{props = Properties, 119 | payload = Response}), 120 | amqp_channel:call(Channel, #'basic.ack'{delivery_tag = DeliveryTag}), 121 | {noreply, State}; 122 | 123 | %% @private 124 | handle_info({'DOWN', _MRef, process, _Pid, _Info}, State) -> 125 | {noreply, State}. 126 | 127 | %% @private 128 | handle_call(stop, _From, State) -> 129 | {stop, normal, ok, State}. 130 | 131 | %%-------------------------------------------------------------------------- 132 | %% Rest of the gen_server callbacks 133 | %%-------------------------------------------------------------------------- 134 | 135 | %% @private 136 | handle_cast(_Message, State) -> 137 | {noreply, State}. 138 | 139 | %% Closes the channel this gen_server instance started 140 | %% @private 141 | terminate(_Reason, #state{channel = Channel}) -> 142 | amqp_channel:close(Channel), 143 | ok. 144 | 145 | %% @private 146 | code_change(_OldVsn, State, _Extra) -> 147 | {ok, State}. 148 | -------------------------------------------------------------------------------- /src/amqp_main_reader.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_main_reader). 19 | 20 | -include("amqp_client_internal.hrl"). 21 | 22 | -behaviour(gen_server). 23 | 24 | -export([start_link/5]). 25 | -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, 26 | handle_info/2]). 27 | 28 | -record(state, {sock, 29 | connection, 30 | channels_manager, 31 | astate, 32 | message = none %% none | {Type, Channel, Length} 33 | }). 34 | 35 | %%--------------------------------------------------------------------------- 36 | %% Interface 37 | %%--------------------------------------------------------------------------- 38 | 39 | start_link(Sock, Connection, ChMgr, AState, ConnName) -> 40 | gen_server:start_link( 41 | ?MODULE, [Sock, Connection, ConnName, ChMgr, AState], []). 42 | 43 | %%--------------------------------------------------------------------------- 44 | %% gen_server callbacks 45 | %%--------------------------------------------------------------------------- 46 | 47 | init([Sock, Connection, ConnName, ChMgr, AState]) -> 48 | ?store_proc_name(ConnName), 49 | State = #state{sock = Sock, 50 | connection = Connection, 51 | channels_manager = ChMgr, 52 | astate = AState, 53 | message = none}, 54 | case rabbit_net:async_recv(Sock, 0, infinity) of 55 | {ok, _} -> {ok, State}; 56 | {error, Reason} -> {stop, Reason, _} = handle_error(Reason, State), 57 | {stop, Reason} 58 | end. 59 | 60 | terminate(_Reason, _State) -> 61 | ok. 62 | 63 | code_change(_OldVsn, State, _Extra) -> 64 | {ok, State}. 65 | 66 | handle_call(Call, From, State) -> 67 | {stop, {unexpected_call, Call, From}, State}. 68 | 69 | handle_cast(Cast, State) -> 70 | {stop, {unexpected_cast, Cast}, State}. 71 | 72 | handle_info({inet_async, Sock, _, {ok, Data}}, 73 | State = #state {sock = Sock}) -> 74 | %% Latency hiding: Request next packet first, then process data 75 | case rabbit_net:async_recv(Sock, 0, infinity) of 76 | {ok, _} -> handle_data(Data, State); 77 | {error, Reason} -> handle_error(Reason, State) 78 | end; 79 | handle_info({inet_async, Sock, _, {error, Reason}}, 80 | State = #state{sock = Sock}) -> 81 | handle_error(Reason, State). 82 | 83 | handle_data(<>, 85 | #state{message = none} = State) when 86 | Type =:= ?FRAME_METHOD; Type =:= ?FRAME_HEADER; 87 | Type =:= ?FRAME_BODY; Type =:= ?FRAME_HEARTBEAT -> 88 | %% Optimisation for the direct match 89 | handle_data( 90 | More, process_frame(Type, Channel, Payload, State#state{message = none})); 91 | handle_data(<>, 92 | #state{message = none} = State) when 93 | Type =:= ?FRAME_METHOD; Type =:= ?FRAME_HEADER; 94 | Type =:= ?FRAME_BODY; Type =:= ?FRAME_HEARTBEAT -> 95 | {noreply, State#state{message = {Type, Channel, Length, Data}}}; 96 | handle_data(<<"AMQP", A, B, C>>, #state{sock = Sock, message = none} = State) -> 97 | {ok, <>} = rabbit_net:sync_recv(Sock, 1), 98 | handle_error({refused, {A, B, C, D}}, State); 99 | handle_data(<>, 100 | #state{message = none} = State) -> 101 | handle_error({malformed_header, Malformed}, State); 102 | handle_data(<>, #state{message = none} = State) -> 103 | {noreply, State#state{message = {expecting_header, Data}}}; 104 | handle_data(Data, #state{message = {Type, Channel, L, OldData}} = State) -> 105 | case <> of 106 | <> -> 107 | handle_data(More, 108 | process_frame(Type, Channel, Payload, 109 | State#state{message = none})); 110 | NotEnough -> 111 | %% Read in more data from the socket 112 | {noreply, State#state{message = {Type, Channel, L, NotEnough}}} 113 | end; 114 | handle_data(Data, 115 | #state{message = {expecting_header, Old}} = State) -> 116 | handle_data(<>, State#state{message = none}); 117 | handle_data(<<>>, State) -> 118 | {noreply, State}. 119 | 120 | %%--------------------------------------------------------------------------- 121 | %% Internal plumbing 122 | %%--------------------------------------------------------------------------- 123 | 124 | process_frame(Type, ChNumber, Payload, 125 | State = #state{connection = Connection, 126 | channels_manager = ChMgr, 127 | astate = AState}) -> 128 | case rabbit_command_assembler:analyze_frame(Type, Payload, ?PROTOCOL) of 129 | heartbeat when ChNumber /= 0 -> 130 | amqp_gen_connection:server_misbehaved( 131 | Connection, 132 | #amqp_error{name = command_invalid, 133 | explanation = "heartbeat on non-zero channel"}), 134 | State; 135 | %% Match heartbeats but don't do anything with them 136 | heartbeat -> 137 | State; 138 | AnalyzedFrame when ChNumber /= 0 -> 139 | amqp_channels_manager:pass_frame(ChMgr, ChNumber, AnalyzedFrame), 140 | State; 141 | AnalyzedFrame -> 142 | State#state{astate = amqp_channels_manager:process_channel_frame( 143 | AnalyzedFrame, 0, Connection, AState)} 144 | end. 145 | 146 | handle_error(closed, State = #state{connection = Conn}) -> 147 | Conn ! socket_closed, 148 | {noreply, State}; 149 | handle_error({refused, Version}, State = #state{connection = Conn}) -> 150 | Conn ! {refused, Version}, 151 | {noreply, State}; 152 | handle_error({malformed_header, Version}, State = #state{connection = Conn}) -> 153 | Conn ! {malformed_header, Version}, 154 | {noreply, State}; 155 | handle_error(Reason, State = #state{connection = Conn}) -> 156 | Conn ! {socket_error, Reason}, 157 | {stop, {socket_error, Reason}, State}. 158 | -------------------------------------------------------------------------------- /src/amqp_rpc_client.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @doc This module allows the simple execution of an asynchronous RPC over 18 | %% AMQP. It frees a client programmer of the necessary having to AMQP 19 | %% plumbing. Note that the this module does not handle any data encoding, 20 | %% so it is up to the caller to marshall and unmarshall message payloads 21 | %% accordingly. 22 | -module(amqp_rpc_client). 23 | 24 | -include("amqp_client.hrl"). 25 | 26 | -behaviour(gen_server). 27 | 28 | -export([start/2, start_link/2, stop/1]). 29 | -export([call/2]). 30 | -export([init/1, terminate/2, code_change/3, handle_call/3, 31 | handle_cast/2, handle_info/2]). 32 | 33 | -record(state, {channel, 34 | reply_queue, 35 | exchange, 36 | routing_key, 37 | continuations = dict:new(), 38 | correlation_id = 0}). 39 | 40 | %%-------------------------------------------------------------------------- 41 | %% API 42 | %%-------------------------------------------------------------------------- 43 | 44 | %% @spec (Connection, Queue) -> RpcClient 45 | %% where 46 | %% Connection = pid() 47 | %% Queue = binary() 48 | %% RpcClient = pid() 49 | %% @doc Starts a new RPC client instance that sends requests to a 50 | %% specified queue. This function returns the pid of the RPC client process 51 | %% that can be used to invoke RPCs and stop the client. 52 | start(Connection, Queue) -> 53 | {ok, Pid} = gen_server:start(?MODULE, [Connection, Queue], []), 54 | Pid. 55 | 56 | %% @spec (Connection, Queue) -> RpcClient 57 | %% where 58 | %% Connection = pid() 59 | %% Queue = binary() 60 | %% RpcClient = pid() 61 | %% @doc Starts, and links to, a new RPC client instance that sends requests 62 | %% to a specified queue. This function returns the pid of the RPC client 63 | %% process that can be used to invoke RPCs and stop the client. 64 | start_link(Connection, Queue) -> 65 | {ok, Pid} = gen_server:start_link(?MODULE, [Connection, Queue], []), 66 | Pid. 67 | 68 | %% @spec (RpcClient) -> ok 69 | %% where 70 | %% RpcClient = pid() 71 | %% @doc Stops an exisiting RPC client. 72 | stop(Pid) -> 73 | gen_server:call(Pid, stop, infinity). 74 | 75 | %% @spec (RpcClient, Payload) -> ok 76 | %% where 77 | %% RpcClient = pid() 78 | %% Payload = binary() 79 | %% @doc Invokes an RPC. Note the caller of this function is responsible for 80 | %% encoding the request and decoding the response. 81 | call(RpcClient, Payload) -> 82 | gen_server:call(RpcClient, {call, Payload}, infinity). 83 | 84 | %%-------------------------------------------------------------------------- 85 | %% Plumbing 86 | %%-------------------------------------------------------------------------- 87 | 88 | %% Sets up a reply queue for this client to listen on 89 | setup_reply_queue(State = #state{channel = Channel}) -> 90 | #'queue.declare_ok'{queue = Q} = 91 | amqp_channel:call(Channel, #'queue.declare'{exclusive = true, 92 | auto_delete = true}), 93 | State#state{reply_queue = Q}. 94 | 95 | %% Registers this RPC client instance as a consumer to handle rpc responses 96 | setup_consumer(#state{channel = Channel, reply_queue = Q}) -> 97 | amqp_channel:call(Channel, #'basic.consume'{queue = Q}). 98 | 99 | %% Publishes to the broker, stores the From address against 100 | %% the correlation id and increments the correlationid for 101 | %% the next request 102 | publish(Payload, From, 103 | State = #state{channel = Channel, 104 | reply_queue = Q, 105 | exchange = X, 106 | routing_key = RoutingKey, 107 | correlation_id = CorrelationId, 108 | continuations = Continuations}) -> 109 | EncodedCorrelationId = base64:encode(<>), 110 | Props = #'P_basic'{correlation_id = EncodedCorrelationId, 111 | content_type = <<"application/octet-stream">>, 112 | reply_to = Q}, 113 | Publish = #'basic.publish'{exchange = X, 114 | routing_key = RoutingKey, 115 | mandatory = true}, 116 | amqp_channel:call(Channel, Publish, #amqp_msg{props = Props, 117 | payload = Payload}), 118 | State#state{correlation_id = CorrelationId + 1, 119 | continuations = dict:store(EncodedCorrelationId, From, Continuations)}. 120 | 121 | %%-------------------------------------------------------------------------- 122 | %% gen_server callbacks 123 | %%-------------------------------------------------------------------------- 124 | 125 | %% Sets up a reply queue and consumer within an existing channel 126 | %% @private 127 | init([Connection, RoutingKey]) -> 128 | {ok, Channel} = amqp_connection:open_channel( 129 | Connection, {amqp_direct_consumer, [self()]}), 130 | InitialState = #state{channel = Channel, 131 | exchange = <<>>, 132 | routing_key = RoutingKey}, 133 | State = setup_reply_queue(InitialState), 134 | setup_consumer(State), 135 | {ok, State}. 136 | 137 | %% Closes the channel this gen_server instance started 138 | %% @private 139 | terminate(_Reason, #state{channel = Channel}) -> 140 | amqp_channel:close(Channel), 141 | ok. 142 | 143 | %% Handle the application initiated stop by just stopping this gen server 144 | %% @private 145 | handle_call(stop, _From, State) -> 146 | {stop, normal, ok, State}; 147 | 148 | %% @private 149 | handle_call({call, Payload}, From, State) -> 150 | NewState = publish(Payload, From, State), 151 | {noreply, NewState}. 152 | 153 | %% @private 154 | handle_cast(_Msg, State) -> 155 | {noreply, State}. 156 | 157 | %% @private 158 | handle_info({#'basic.consume'{}, _Pid}, State) -> 159 | {noreply, State}; 160 | 161 | %% @private 162 | handle_info(#'basic.consume_ok'{}, State) -> 163 | {noreply, State}; 164 | 165 | %% @private 166 | handle_info(#'basic.cancel'{}, State) -> 167 | {noreply, State}; 168 | 169 | %% @private 170 | handle_info(#'basic.cancel_ok'{}, State) -> 171 | {stop, normal, State}; 172 | 173 | %% @private 174 | handle_info({#'basic.deliver'{delivery_tag = DeliveryTag}, 175 | #amqp_msg{props = #'P_basic'{correlation_id = Id}, 176 | payload = Payload}}, 177 | State = #state{continuations = Conts, channel = Channel}) -> 178 | From = dict:fetch(Id, Conts), 179 | gen_server:reply(From, Payload), 180 | amqp_channel:call(Channel, #'basic.ack'{delivery_tag = DeliveryTag}), 181 | {noreply, State#state{continuations = dict:erase(Id, Conts) }}. 182 | 183 | %% @private 184 | code_change(_OldVsn, State, _Extra) -> 185 | {ok, State}. 186 | -------------------------------------------------------------------------------- /src/rabbit_ct_client_helpers.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License 4 | %% at http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %% the License for the specific language governing rights and 9 | %% limitations under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2015 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | -module(rabbit_ct_client_helpers). 18 | 19 | -include_lib("common_test/include/ct.hrl"). 20 | -include("include/amqp_client.hrl"). 21 | 22 | -export([ 23 | setup_steps/0, 24 | teardown_steps/0, 25 | start_channels_managers/1, 26 | stop_channels_managers/1, 27 | 28 | open_connection/2, close_connection/1, 29 | open_channel/2, close_channel/1, 30 | close_channels_and_connection/2, 31 | 32 | publish/3, consume/3, fetch/3 33 | ]). 34 | 35 | %% ------------------------------------------------------------------- 36 | %% Client setup/teardown steps. 37 | %% ------------------------------------------------------------------- 38 | 39 | setup_steps() -> 40 | [ 41 | fun start_channels_managers/1 42 | ]. 43 | 44 | teardown_steps() -> 45 | [ 46 | fun stop_channels_managers/1 47 | ]. 48 | 49 | start_channels_managers(Config) -> 50 | NodeConfigs = rabbit_ct_broker_helpers:get_node_configs(Config), 51 | NodeConfigs1 = [start_channels_manager(NC) || NC <- NodeConfigs], 52 | rabbit_ct_helpers:set_config(Config, {rmq_nodes, NodeConfigs1}). 53 | 54 | start_channels_manager(NodeConfig) -> 55 | Pid = erlang:spawn( 56 | fun() -> channels_manager(NodeConfig, undefined, []) end), 57 | rabbit_ct_helpers:set_config(NodeConfig, {channels_manager, Pid}). 58 | 59 | stop_channels_managers(Config) -> 60 | NodeConfigs = rabbit_ct_broker_helpers:get_node_configs(Config), 61 | NodeConfigs1 = [stop_channels_manager(NC) || NC <- NodeConfigs], 62 | rabbit_ct_helpers:set_config(Config, {rmq_nodes, NodeConfigs1}). 63 | 64 | stop_channels_manager(NodeConfig) -> 65 | Pid = ?config(channels_manager, NodeConfig), 66 | Pid ! stop, 67 | proplists:delete(channels_manager, NodeConfig). 68 | 69 | channels_manager(NodeConfig, ConnTuple, Channels) -> 70 | receive 71 | {open_connection, From} -> 72 | {Conn1, _} = ConnTuple1 = open_conn(NodeConfig, ConnTuple), 73 | From ! Conn1, 74 | channels_manager(NodeConfig, ConnTuple1, Channels); 75 | {open_channel, From} -> 76 | {Conn1, _} = ConnTuple1 = open_conn(NodeConfig, ConnTuple), 77 | {ok, Ch} = amqp_connection:open_channel(Conn1), 78 | ChMRef = erlang:monitor(process, Ch), 79 | From ! Ch, 80 | channels_manager(NodeConfig, ConnTuple1, 81 | [{Ch, ChMRef} | Channels]); 82 | {close_everything, From} -> 83 | close_everything(ConnTuple, Channels), 84 | From ! ok, 85 | channels_manager(NodeConfig, undefined, []); 86 | {'DOWN', ConnMRef, process, Conn, _} 87 | when {Conn, ConnMRef} =:= ConnTuple -> 88 | channels_manager(NodeConfig, undefined, Channels); 89 | {'DOWN', ChMRef, process, Ch, _} -> 90 | Channels1 = Channels -- [{Ch, ChMRef}], 91 | channels_manager(NodeConfig, ConnTuple, Channels1); 92 | stop -> 93 | close_everything(ConnTuple, Channels); 94 | Unhandled -> 95 | ct:pal("Channels manager ~p: unhandled message: ~p", 96 | [self(), Unhandled]), 97 | channels_manager(NodeConfig, ConnTuple, Channels) 98 | end. 99 | 100 | open_conn(NodeConfig, undefined) -> 101 | Port = ?config(tcp_port_amqp, NodeConfig), 102 | Params = #amqp_params_network{port = Port}, 103 | {ok, Conn} = amqp_connection:start(Params), 104 | MRef = erlang:monitor(process, Conn), 105 | {Conn, MRef}; 106 | open_conn(NodeConfig, {Conn, _} = ConnTuple) -> 107 | case erlang:is_process_alive(Conn) of 108 | true -> ConnTuple; 109 | false -> open_conn(NodeConfig, undefined) 110 | end. 111 | 112 | close_everything(Conn, [{Ch, MRef} | Rest]) -> 113 | case erlang:is_process_alive(Ch) of 114 | true -> 115 | erlang:demonitor(MRef, [flush]), 116 | amqp_channel:close(Ch); 117 | false -> 118 | ok 119 | end, 120 | close_everything(Conn, Rest); 121 | close_everything({Conn, MRef}, []) -> 122 | case erlang:is_process_alive(Conn) of 123 | true -> 124 | erlang:demonitor(MRef), 125 | amqp_connection:close(Conn); 126 | false -> 127 | ok 128 | end; 129 | close_everything(undefined, []) -> 130 | ok. 131 | 132 | %% ------------------------------------------------------------------- 133 | %% Public API. 134 | %% ------------------------------------------------------------------- 135 | 136 | open_connection(Config, Node) -> 137 | Pid = rabbit_ct_broker_helpers:get_node_config(Config, Node, 138 | channels_manager), 139 | Pid ! {open_connection, self()}, 140 | receive 141 | Conn when is_pid(Conn) -> Conn 142 | end. 143 | 144 | open_channel(Config, Node) -> 145 | Pid = rabbit_ct_broker_helpers:get_node_config(Config, Node, 146 | channels_manager), 147 | Pid ! {open_channel, self()}, 148 | receive 149 | Ch when is_pid(Ch) -> Ch 150 | end. 151 | 152 | close_channel(Ch) -> 153 | case is_process_alive(Ch) of 154 | true -> amqp_channel:close(Ch); 155 | false -> ok 156 | end. 157 | 158 | close_connection(Conn) -> 159 | case is_process_alive(Conn) of 160 | true -> amqp_connection:close(Conn); 161 | false -> ok 162 | end. 163 | 164 | close_channels_and_connection(Config, Node) -> 165 | Pid = rabbit_ct_broker_helpers:get_node_config(Config, Node, 166 | channels_manager), 167 | Pid ! {close_everything, self()}, 168 | receive 169 | ok -> ok 170 | end. 171 | 172 | publish(Ch, QName, Count) -> 173 | amqp_channel:call(Ch, #'confirm.select'{}), 174 | [amqp_channel:call(Ch, 175 | #'basic.publish'{routing_key = QName}, 176 | #amqp_msg{props = #'P_basic'{delivery_mode = 2}, 177 | payload = list_to_binary(integer_to_list(I))}) 178 | || I <- lists:seq(1, Count)], 179 | amqp_channel:wait_for_confirms(Ch). 180 | 181 | consume(Ch, QName, Count) -> 182 | amqp_channel:subscribe(Ch, #'basic.consume'{queue = QName, no_ack = true}, 183 | self()), 184 | CTag = receive #'basic.consume_ok'{consumer_tag = C} -> C end, 185 | [begin 186 | Exp = list_to_binary(integer_to_list(I)), 187 | receive {#'basic.deliver'{consumer_tag = CTag}, 188 | #amqp_msg{payload = Exp}} -> 189 | ok 190 | after 500 -> 191 | exit(timeout) 192 | end 193 | end|| I <- lists:seq(1, Count)], 194 | #'queue.declare_ok'{message_count = 0} 195 | = amqp_channel:call(Ch, #'queue.declare'{queue = QName, 196 | durable = true}), 197 | amqp_channel:call(Ch, #'basic.cancel'{consumer_tag = CTag}), 198 | ok. 199 | 200 | fetch(Ch, QName, Count) -> 201 | [{#'basic.get_ok'{}, _} = 202 | amqp_channel:call(Ch, #'basic.get'{queue = QName}) || 203 | _ <- lists:seq(1, Count)], 204 | ok. 205 | -------------------------------------------------------------------------------- /src/amqp_direct_connection.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_direct_connection). 19 | 20 | -include("amqp_client_internal.hrl"). 21 | 22 | -behaviour(amqp_gen_connection). 23 | 24 | -export([server_close/3]). 25 | 26 | -export([init/0, terminate/2, connect/4, do/2, open_channel_args/1, i/2, 27 | info_keys/0, handle_message/2, closing/3, channels_terminated/1]). 28 | 29 | -export([socket_adapter_info/2]). 30 | 31 | -record(state, {node, 32 | user, 33 | vhost, 34 | params, 35 | adapter_info, 36 | collector, 37 | closing_reason, %% undefined | Reason 38 | connected_at 39 | }). 40 | 41 | -define(INFO_KEYS, [type]). 42 | 43 | -define(CREATION_EVENT_KEYS, [pid, protocol, host, port, name, 44 | peer_host, peer_port, 45 | user, vhost, client_properties, type, 46 | connected_at]). 47 | 48 | %%--------------------------------------------------------------------------- 49 | 50 | %% amqp_connection:close() logically closes from the client end. We may 51 | %% want to close from the server end. 52 | server_close(ConnectionPid, Code, Text) -> 53 | Close = #'connection.close'{reply_text = Text, 54 | reply_code = Code, 55 | class_id = 0, 56 | method_id = 0}, 57 | amqp_gen_connection:server_close(ConnectionPid, Close). 58 | 59 | init() -> 60 | {ok, #state{}}. 61 | 62 | open_channel_args(#state{node = Node, 63 | user = User, 64 | vhost = VHost, 65 | collector = Collector}) -> 66 | [self(), Node, User, VHost, Collector]. 67 | 68 | do(_Method, _State) -> 69 | ok. 70 | 71 | handle_message({force_event_refresh, Ref}, State = #state{node = Node}) -> 72 | rpc:call(Node, rabbit_event, notify, 73 | [connection_created, connection_info(State), Ref]), 74 | {ok, State}; 75 | handle_message(closing_timeout, State = #state{closing_reason = Reason}) -> 76 | {stop, {closing_timeout, Reason}, State}; 77 | handle_message({'DOWN', _MRef, process, _ConnSup, shutdown}, State) -> 78 | {stop, {shutdown, node_down}, State}; 79 | handle_message({'DOWN', _MRef, process, _ConnSup, Reason}, State) -> 80 | {stop, {remote_node_down, Reason}, State}; 81 | handle_message(Msg, State) -> 82 | {stop, {unexpected_msg, Msg}, State}. 83 | 84 | closing(_ChannelCloseType, Reason, State) -> 85 | {ok, State#state{closing_reason = Reason}}. 86 | 87 | channels_terminated(State = #state{closing_reason = Reason, 88 | collector = Collector}) -> 89 | rabbit_queue_collector:delete_all(Collector), 90 | {stop, {shutdown, Reason}, State}. 91 | 92 | terminate(_Reason, #state{node = Node}) -> 93 | rpc:call(Node, rabbit_direct, disconnect, [self(), [{pid, self()}]]), 94 | ok. 95 | 96 | i(type, _State) -> direct; 97 | i(pid, _State) -> self(); 98 | %% AMQP Params 99 | i(user, #state{params = P}) -> P#amqp_params_direct.username; 100 | i(vhost, #state{params = P}) -> P#amqp_params_direct.virtual_host; 101 | i(client_properties, #state{params = P}) -> 102 | P#amqp_params_direct.client_properties; 103 | i(connected_at, #state{connected_at = T}) -> T; 104 | %% Optional adapter info 105 | i(protocol, #state{adapter_info = I}) -> I#amqp_adapter_info.protocol; 106 | i(host, #state{adapter_info = I}) -> I#amqp_adapter_info.host; 107 | i(port, #state{adapter_info = I}) -> I#amqp_adapter_info.port; 108 | i(peer_host, #state{adapter_info = I}) -> I#amqp_adapter_info.peer_host; 109 | i(peer_port, #state{adapter_info = I}) -> I#amqp_adapter_info.peer_port; 110 | i(name, #state{adapter_info = I}) -> I#amqp_adapter_info.name; 111 | 112 | i(Item, _State) -> throw({bad_argument, Item}). 113 | 114 | info_keys() -> 115 | ?INFO_KEYS. 116 | 117 | infos(Items, State) -> 118 | [{Item, i(Item, State)} || Item <- Items]. 119 | 120 | connection_info(State = #state{adapter_info = I}) -> 121 | infos(?CREATION_EVENT_KEYS, State) ++ I#amqp_adapter_info.additional_info. 122 | 123 | connect(Params = #amqp_params_direct{username = Username, 124 | password = Password, 125 | node = Node, 126 | adapter_info = Info, 127 | virtual_host = VHost}, 128 | SIF, _TypeSup, State) -> 129 | State1 = State#state{node = Node, 130 | vhost = VHost, 131 | params = Params, 132 | adapter_info = ensure_adapter_info(Info), 133 | connected_at = 134 | time_compat:os_system_time(milli_seconds)}, 135 | case rpc:call(Node, rabbit_direct, connect, 136 | [{Username, Password}, VHost, ?PROTOCOL, self(), 137 | connection_info(State1)]) of 138 | {ok, {User, ServerProperties}} -> 139 | {ok, ChMgr, Collector} = SIF(i(name, State1)), 140 | State2 = State1#state{user = User, 141 | collector = Collector}, 142 | %% There's no real connection-level process on the remote 143 | %% node for us to monitor or link to, but we want to 144 | %% detect connection death if the remote node goes down 145 | %% when there are no channels. So we monitor the 146 | %% supervisor; that way we find out if the node goes down 147 | %% or the rabbit app stops. 148 | erlang:monitor(process, {rabbit_direct_client_sup, Node}), 149 | {ok, {ServerProperties, 0, ChMgr, State2}}; 150 | {error, _} = E -> 151 | E; 152 | {badrpc, nodedown} -> 153 | {error, {nodedown, Node}} 154 | end. 155 | 156 | ensure_adapter_info(none) -> 157 | ensure_adapter_info(#amqp_adapter_info{}); 158 | 159 | ensure_adapter_info(A = #amqp_adapter_info{protocol = unknown}) -> 160 | ensure_adapter_info(A#amqp_adapter_info{ 161 | protocol = {'Direct', ?PROTOCOL:version()}}); 162 | 163 | ensure_adapter_info(A = #amqp_adapter_info{name = unknown}) -> 164 | Name = list_to_binary(rabbit_misc:pid_to_string(self())), 165 | ensure_adapter_info(A#amqp_adapter_info{name = Name}); 166 | 167 | ensure_adapter_info(Info) -> Info. 168 | 169 | socket_adapter_info(Sock, Protocol) -> 170 | {PeerHost, PeerPort, Host, Port} = 171 | case rabbit_net:socket_ends(Sock, inbound) of 172 | {ok, Res} -> Res; 173 | _ -> {unknown, unknown, unknown, unknown} 174 | end, 175 | Name = case rabbit_net:connection_string(Sock, inbound) of 176 | {ok, Res1} -> Res1; 177 | _Error -> "(unknown)" 178 | end, 179 | #amqp_adapter_info{protocol = Protocol, 180 | name = list_to_binary(Name), 181 | host = Host, 182 | port = Port, 183 | peer_host = PeerHost, 184 | peer_port = PeerPort, 185 | additional_info = maybe_ssl_info(Sock)}. 186 | 187 | maybe_ssl_info(Sock) -> 188 | case rabbit_net:is_ssl(Sock) of 189 | true -> [{ssl, true}] ++ ssl_info(Sock) ++ ssl_cert_info(Sock); 190 | false -> [{ssl, false}] 191 | end. 192 | 193 | ssl_info(Sock) -> 194 | {Protocol, KeyExchange, Cipher, Hash} = 195 | case rabbit_net:ssl_info(Sock) of 196 | {ok, Infos} -> 197 | {_, P} = lists:keyfind(protocol, 1, Infos), 198 | case lists:keyfind(cipher_suite, 1, Infos) of 199 | {_,{K, C, H}} -> {P, K, C, H}; 200 | {_,{K, C, H, _}} -> {P, K, C, H} 201 | end; 202 | _ -> 203 | {unknown, unknown, unknown, unknown} 204 | end, 205 | [{ssl_protocol, Protocol}, 206 | {ssl_key_exchange, KeyExchange}, 207 | {ssl_cipher, Cipher}, 208 | {ssl_hash, Hash}]. 209 | 210 | ssl_cert_info(Sock) -> 211 | case rabbit_net:peercert(Sock) of 212 | {ok, Cert} -> 213 | [{peer_cert_issuer, list_to_binary( 214 | rabbit_ssl:peer_cert_issuer(Cert))}, 215 | {peer_cert_subject, list_to_binary( 216 | rabbit_ssl:peer_cert_subject(Cert))}, 217 | {peer_cert_validity, list_to_binary( 218 | rabbit_ssl:peer_cert_validity(Cert))}]; 219 | _ -> 220 | [] 221 | end. 222 | -------------------------------------------------------------------------------- /src/rabbit_routing_util.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2013-2015 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | -module(rabbit_routing_util). 18 | 19 | -export([init_state/0, dest_prefixes/0, all_dest_prefixes/0]). 20 | -export([ensure_endpoint/4, ensure_endpoint/5, ensure_binding/3]). 21 | -export([parse_endpoint/1, parse_endpoint/2]). 22 | -export([parse_routing/1, dest_temp_queue/1]). 23 | 24 | -include("amqp_client.hrl"). 25 | -include("rabbit_routing_prefixes.hrl"). 26 | 27 | %%---------------------------------------------------------------------------- 28 | 29 | init_state() -> sets:new(). 30 | 31 | dest_prefixes() -> [?EXCHANGE_PREFIX, ?TOPIC_PREFIX, ?QUEUE_PREFIX, 32 | ?AMQQUEUE_PREFIX, ?REPLY_QUEUE_PREFIX]. 33 | 34 | all_dest_prefixes() -> [?TEMP_QUEUE_PREFIX | dest_prefixes()]. 35 | 36 | %% -------------------------------------------------------------------------- 37 | 38 | parse_endpoint(Destination) -> 39 | parse_endpoint(Destination, false). 40 | 41 | parse_endpoint(undefined, AllowAnonymousQueue) -> 42 | parse_endpoint("/queue", AllowAnonymousQueue); 43 | 44 | parse_endpoint(Destination, AllowAnonymousQueue) when is_binary(Destination) -> 45 | parse_endpoint(unicode:characters_to_list(Destination), 46 | AllowAnonymousQueue); 47 | parse_endpoint(Destination, AllowAnonymousQueue) when is_list(Destination) -> 48 | case re:split(Destination, "/", [{return, list}]) of 49 | [Name] -> 50 | {ok, {queue, unescape(Name)}}; 51 | ["", Type | Rest] 52 | when Type =:= "exchange" orelse Type =:= "queue" orelse 53 | Type =:= "topic" orelse Type =:= "temp-queue" -> 54 | parse_endpoint0(atomise(Type), Rest, AllowAnonymousQueue); 55 | ["", "amq", "queue" | Rest] -> 56 | parse_endpoint0(amqqueue, Rest, AllowAnonymousQueue); 57 | ["", "reply-queue" = Prefix | [_|_]] -> 58 | parse_endpoint0(reply_queue, 59 | [lists:nthtail(2 + length(Prefix), Destination)], 60 | AllowAnonymousQueue); 61 | _ -> 62 | {error, {unknown_destination, Destination}} 63 | end. 64 | 65 | parse_endpoint0(exchange, ["" | _] = Rest, _) -> 66 | {error, {invalid_destination, exchange, to_url(Rest)}}; 67 | parse_endpoint0(exchange, [Name], _) -> 68 | {ok, {exchange, {unescape(Name), undefined}}}; 69 | parse_endpoint0(exchange, [Name, Pattern], _) -> 70 | {ok, {exchange, {unescape(Name), unescape(Pattern)}}}; 71 | parse_endpoint0(queue, [], false) -> 72 | {error, {invalid_destination, queue, []}}; 73 | parse_endpoint0(queue, [], true) -> 74 | {ok, {queue, undefined}}; 75 | parse_endpoint0(Type, [[_|_]] = [Name], _) -> 76 | {ok, {Type, unescape(Name)}}; 77 | parse_endpoint0(Type, Rest, _) -> 78 | {error, {invalid_destination, Type, to_url(Rest)}}. 79 | 80 | %% -------------------------------------------------------------------------- 81 | 82 | ensure_endpoint(Dir, Channel, Endpoint, State) -> 83 | ensure_endpoint(Dir, Channel, Endpoint, [], State). 84 | 85 | ensure_endpoint(source, Channel, {exchange, {Name, _}}, Params, State) -> 86 | check_exchange(Name, Channel, 87 | proplists:get_value(check_exchange, Params, false)), 88 | Method = queue_declare_method(#'queue.declare'{}, exchange, Params), 89 | #'queue.declare_ok'{queue = Queue} = amqp_channel:call(Channel, Method), 90 | {ok, Queue, State}; 91 | 92 | ensure_endpoint(source, Channel, {topic, _}, Params, State) -> 93 | Method = queue_declare_method(#'queue.declare'{}, topic, Params), 94 | #'queue.declare_ok'{queue = Queue} = amqp_channel:call(Channel, Method), 95 | {ok, Queue, State}; 96 | 97 | ensure_endpoint(_Dir, _Channel, {queue, undefined}, _Params, State) -> 98 | {ok, undefined, State}; 99 | 100 | ensure_endpoint(_, Channel, {queue, Name}, Params, State) -> 101 | Params1 = rabbit_misc:pmerge(durable, true, Params), 102 | Queue = list_to_binary(Name), 103 | State1 = case sets:is_element(Queue, State) of 104 | true -> State; 105 | _ -> Method = queue_declare_method( 106 | #'queue.declare'{queue = Queue, 107 | nowait = true}, 108 | queue, Params1), 109 | case Method#'queue.declare'.nowait of 110 | true -> amqp_channel:cast(Channel, Method); 111 | false -> amqp_channel:call(Channel, Method) 112 | end, 113 | sets:add_element(Queue, State) 114 | end, 115 | {ok, Queue, State1}; 116 | 117 | ensure_endpoint(dest, Channel, {exchange, {Name, _}}, Params, State) -> 118 | check_exchange(Name, Channel, 119 | proplists:get_value(check_exchange, Params, false)), 120 | {ok, undefined, State}; 121 | 122 | ensure_endpoint(dest, _Ch, {topic, _}, _Params, State) -> 123 | {ok, undefined, State}; 124 | 125 | ensure_endpoint(_, _Ch, {amqqueue, Name}, _Params, State) -> 126 | {ok, list_to_binary(Name), State}; 127 | 128 | ensure_endpoint(_, _Ch, {reply_queue, Name}, _Params, State) -> 129 | {ok, list_to_binary(Name), State}; 130 | 131 | ensure_endpoint(_Direction, _Ch, _Endpoint, _Params, _State) -> 132 | {error, invalid_endpoint}. 133 | 134 | %% -------------------------------------------------------------------------- 135 | 136 | ensure_binding(QueueBin, {"", Queue}, _Channel) -> 137 | %% i.e., we should only be asked to bind to the default exchange a 138 | %% queue with its own name 139 | QueueBin = list_to_binary(Queue), 140 | ok; 141 | ensure_binding(Queue, {Exchange, RoutingKey}, Channel) -> 142 | #'queue.bind_ok'{} = 143 | amqp_channel:call(Channel, 144 | #'queue.bind'{ 145 | queue = Queue, 146 | exchange = list_to_binary(Exchange), 147 | routing_key = list_to_binary(RoutingKey)}), 148 | ok. 149 | 150 | %% -------------------------------------------------------------------------- 151 | 152 | parse_routing({exchange, {Name, undefined}}) -> 153 | {Name, ""}; 154 | parse_routing({exchange, {Name, Pattern}}) -> 155 | {Name, Pattern}; 156 | parse_routing({topic, Name}) -> 157 | {"amq.topic", Name}; 158 | parse_routing({Type, Name}) 159 | when Type =:= queue orelse Type =:= reply_queue orelse Type =:= amqqueue -> 160 | {"", Name}. 161 | 162 | dest_temp_queue({temp_queue, Name}) -> Name; 163 | dest_temp_queue(_) -> none. 164 | 165 | %% -------------------------------------------------------------------------- 166 | 167 | check_exchange(_, _, false) -> 168 | ok; 169 | check_exchange(ExchangeName, Channel, true) -> 170 | XDecl = #'exchange.declare'{ exchange = list_to_binary(ExchangeName), 171 | passive = true }, 172 | #'exchange.declare_ok'{} = amqp_channel:call(Channel, XDecl), 173 | ok. 174 | 175 | update_queue_declare_arguments(Method, Params) -> 176 | Method#'queue.declare'{arguments = 177 | proplists:get_value(arguments, Params, [])}. 178 | 179 | update_queue_declare_exclusive(Method, Params) -> 180 | case proplists:get_value(exclusive, Params) of 181 | undefined -> Method; 182 | Val -> Method#'queue.declare'{exclusive = Val} 183 | end. 184 | 185 | update_queue_declare_auto_delete(Method, Params) -> 186 | case proplists:get_value(auto_delete, Params) of 187 | undefined -> Method; 188 | Val -> Method#'queue.declare'{auto_delete = Val} 189 | end. 190 | 191 | update_queue_declare_nowait(Method, Params) -> 192 | case proplists:get_value(nowait, Params) of 193 | undefined -> Method; 194 | Val -> Method#'queue.declare'{nowait = Val} 195 | end. 196 | 197 | queue_declare_method(#'queue.declare'{} = Method, Type, Params) -> 198 | %% defaults 199 | Method1 = case proplists:get_value(durable, Params, false) of 200 | true -> Method#'queue.declare'{durable = true}; 201 | false -> Method#'queue.declare'{auto_delete = true, 202 | exclusive = true} 203 | end, 204 | %% set the rest of queue.declare fields from Params 205 | Method2 = lists:foldl(fun (F, Acc) -> F(Acc, Params) end, 206 | Method1, [fun update_queue_declare_arguments/2, 207 | fun update_queue_declare_exclusive/2, 208 | fun update_queue_declare_auto_delete/2, 209 | fun update_queue_declare_nowait/2]), 210 | case {Type, proplists:get_value(subscription_queue_name_gen, Params)} of 211 | {topic, SQNG} when is_function(SQNG) -> 212 | Method2#'queue.declare'{queue = SQNG()}; 213 | {exchange, SQNG} when is_function(SQNG) -> 214 | Method2#'queue.declare'{queue = SQNG()}; 215 | {'reply-queue', SQNG} when is_function(SQNG) -> 216 | Method2#'queue.declare'{queue = SQNG()}; 217 | _ -> 218 | Method2 219 | end. 220 | 221 | %% -------------------------------------------------------------------------- 222 | 223 | to_url([]) -> []; 224 | to_url(Lol) -> "/" ++ string:join(Lol, "/"). 225 | 226 | atomise(Name) when is_list(Name) -> 227 | list_to_atom(re:replace(Name, "-", "_", [{return,list}, global])). 228 | 229 | unescape(Str) -> unescape(Str, []). 230 | 231 | unescape("%2F" ++ Str, Acc) -> unescape(Str, [$/ | Acc]); 232 | unescape([C | Str], Acc) -> unescape(Str, [C | Acc]); 233 | unescape([], Acc) -> lists:reverse(Acc). 234 | -------------------------------------------------------------------------------- /src/amqp_channels_manager.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_channels_manager). 19 | 20 | -include("amqp_client_internal.hrl"). 21 | 22 | -behaviour(gen_server). 23 | 24 | -export([start_link/3, open_channel/4, set_channel_max/2, is_empty/1, 25 | num_channels/1, pass_frame/3, signal_connection_closing/3, 26 | process_channel_frame/4]). 27 | -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, 28 | handle_info/2]). 29 | 30 | -record(state, {connection, 31 | channel_sup_sup, 32 | map_num_pa = gb_trees:empty(), %% Number -> {Pid, AState} 33 | map_pid_num = dict:new(), %% Pid -> Number 34 | channel_max = ?MAX_CHANNEL_NUMBER, 35 | closing = false}). 36 | 37 | %%--------------------------------------------------------------------------- 38 | %% Interface 39 | %%--------------------------------------------------------------------------- 40 | 41 | start_link(Connection, ConnName, ChSupSup) -> 42 | gen_server:start_link(?MODULE, [Connection, ConnName, ChSupSup], []). 43 | 44 | open_channel(ChMgr, ProposedNumber, Consumer, InfraArgs) -> 45 | gen_server:call(ChMgr, {open_channel, ProposedNumber, Consumer, InfraArgs}, 46 | infinity). 47 | 48 | set_channel_max(ChMgr, ChannelMax) -> 49 | gen_server:cast(ChMgr, {set_channel_max, ChannelMax}). 50 | 51 | is_empty(ChMgr) -> 52 | gen_server:call(ChMgr, is_empty, infinity). 53 | 54 | num_channels(ChMgr) -> 55 | gen_server:call(ChMgr, num_channels, infinity). 56 | 57 | pass_frame(ChMgr, ChNumber, Frame) -> 58 | gen_server:cast(ChMgr, {pass_frame, ChNumber, Frame}). 59 | 60 | signal_connection_closing(ChMgr, ChannelCloseType, Reason) -> 61 | gen_server:cast(ChMgr, {connection_closing, ChannelCloseType, Reason}). 62 | 63 | process_channel_frame(Frame, Channel, ChPid, AState) -> 64 | case rabbit_command_assembler:process(Frame, AState) of 65 | {ok, NewAState} -> NewAState; 66 | {ok, Method, NewAState} -> rabbit_channel:do(ChPid, Method), 67 | NewAState; 68 | {ok, Method, Content, NewAState} -> rabbit_channel:do(ChPid, Method, 69 | Content), 70 | NewAState; 71 | {error, Reason} -> ChPid ! {channel_exit, Channel, 72 | Reason}, 73 | AState 74 | end. 75 | 76 | %%--------------------------------------------------------------------------- 77 | %% gen_server callbacks 78 | %%--------------------------------------------------------------------------- 79 | 80 | init([Connection, ConnName, ChSupSup]) -> 81 | ?store_proc_name(ConnName), 82 | {ok, #state{connection = Connection, channel_sup_sup = ChSupSup}}. 83 | 84 | terminate(_Reason, _State) -> 85 | ok. 86 | 87 | code_change(_OldVsn, State, _Extra) -> 88 | {ok, State}. 89 | 90 | handle_call({open_channel, ProposedNumber, Consumer, InfraArgs}, _, 91 | State = #state{closing = false}) -> 92 | handle_open_channel(ProposedNumber, Consumer, InfraArgs, State); 93 | handle_call(is_empty, _, State) -> 94 | {reply, internal_is_empty(State), State}; 95 | handle_call(num_channels, _, State) -> 96 | {reply, internal_num_channels(State), State}. 97 | 98 | handle_cast({set_channel_max, ChannelMax}, State) -> 99 | {noreply, State#state{channel_max = ChannelMax}}; 100 | handle_cast({pass_frame, ChNumber, Frame}, State) -> 101 | {noreply, internal_pass_frame(ChNumber, Frame, State)}; 102 | handle_cast({connection_closing, ChannelCloseType, Reason}, State) -> 103 | handle_connection_closing(ChannelCloseType, Reason, State). 104 | 105 | handle_info({'DOWN', _, process, Pid, Reason}, State) -> 106 | handle_down(Pid, Reason, State). 107 | 108 | %%--------------------------------------------------------------------------- 109 | %% Internal plumbing 110 | %%--------------------------------------------------------------------------- 111 | 112 | handle_open_channel(ProposedNumber, Consumer, InfraArgs, 113 | State = #state{channel_sup_sup = ChSupSup}) -> 114 | case new_number(ProposedNumber, State) of 115 | {ok, Number} -> 116 | {ok, _ChSup, {Ch, AState}} = 117 | amqp_channel_sup_sup:start_channel_sup(ChSupSup, InfraArgs, 118 | Number, Consumer), 119 | NewState = internal_register(Number, Ch, AState, State), 120 | erlang:monitor(process, Ch), 121 | {reply, {ok, Ch}, NewState}; 122 | {error, _} = Error -> 123 | {reply, Error, State} 124 | end. 125 | 126 | new_number(none, #state{channel_max = ChannelMax, map_num_pa = MapNPA}) -> 127 | case gb_trees:is_empty(MapNPA) of 128 | true -> {ok, 1}; 129 | false -> {Smallest, _} = gb_trees:smallest(MapNPA), 130 | if Smallest > 1 -> 131 | {ok, Smallest - 1}; 132 | true -> 133 | {Largest, _} = gb_trees:largest(MapNPA), 134 | if Largest < ChannelMax -> {ok, Largest + 1}; 135 | true -> find_free(MapNPA) 136 | end 137 | end 138 | end; 139 | new_number(Proposed, State = #state{channel_max = ChannelMax, 140 | map_num_pa = MapNPA}) -> 141 | IsValid = Proposed > 0 andalso Proposed =< ChannelMax andalso 142 | not gb_trees:is_defined(Proposed, MapNPA), 143 | case IsValid of true -> {ok, Proposed}; 144 | false -> new_number(none, State) 145 | end. 146 | 147 | find_free(MapNPA) -> 148 | find_free(gb_trees:iterator(MapNPA), 1). 149 | 150 | find_free(It, Candidate) -> 151 | case gb_trees:next(It) of 152 | {Number, _, It1} -> if Number > Candidate -> 153 | {ok, Number - 1}; 154 | Number =:= Candidate -> 155 | find_free(It1, Candidate + 1) 156 | end; 157 | none -> {error, out_of_channel_numbers} 158 | end. 159 | 160 | handle_down(Pid, Reason, State) -> 161 | case internal_lookup_pn(Pid, State) of 162 | undefined -> {stop, {error, unexpected_down}, State}; 163 | Number -> handle_channel_down(Pid, Number, Reason, State) 164 | end. 165 | 166 | handle_channel_down(Pid, Number, Reason, State) -> 167 | maybe_report_down(Pid, case Reason of {shutdown, R} -> R; 168 | _ -> Reason 169 | end, 170 | State), 171 | NewState = internal_unregister(Number, Pid, State), 172 | check_all_channels_terminated(NewState), 173 | {noreply, NewState}. 174 | 175 | maybe_report_down(_Pid, normal, _State) -> 176 | ok; 177 | maybe_report_down(_Pid, shutdown, _State) -> 178 | ok; 179 | maybe_report_down(_Pid, {app_initiated_close, _, _}, _State) -> 180 | ok; 181 | maybe_report_down(_Pid, {server_initiated_close, _, _}, _State) -> 182 | ok; 183 | maybe_report_down(_Pid, {connection_closing, _}, _State) -> 184 | ok; 185 | maybe_report_down(_Pid, {server_misbehaved, AmqpError}, 186 | #state{connection = Connection}) -> 187 | amqp_gen_connection:server_misbehaved(Connection, AmqpError); 188 | maybe_report_down(Pid, Other, #state{connection = Connection}) -> 189 | amqp_gen_connection:channel_internal_error(Connection, Pid, Other). 190 | 191 | check_all_channels_terminated(#state{closing = false}) -> 192 | ok; 193 | check_all_channels_terminated(State = #state{closing = true, 194 | connection = Connection}) -> 195 | case internal_is_empty(State) of 196 | true -> amqp_gen_connection:channels_terminated(Connection); 197 | false -> ok 198 | end. 199 | 200 | handle_connection_closing(ChannelCloseType, Reason, 201 | State = #state{connection = Connection}) -> 202 | case internal_is_empty(State) of 203 | true -> amqp_gen_connection:channels_terminated(Connection); 204 | false -> signal_channels_connection_closing(ChannelCloseType, Reason, 205 | State) 206 | end, 207 | {noreply, State#state{closing = true}}. 208 | 209 | %%--------------------------------------------------------------------------- 210 | 211 | internal_pass_frame(Number, Frame, State) -> 212 | case internal_lookup_npa(Number, State) of 213 | undefined -> 214 | ?LOG_INFO("Dropping frame ~p for invalid or closed " 215 | "channel number ~p~n", [Frame, Number]), 216 | State; 217 | {ChPid, AState} -> 218 | NewAState = process_channel_frame(Frame, Number, ChPid, AState), 219 | internal_update_npa(Number, ChPid, NewAState, State) 220 | end. 221 | 222 | internal_register(Number, Pid, AState, 223 | State = #state{map_num_pa = MapNPA, map_pid_num = MapPN}) -> 224 | MapNPA1 = gb_trees:enter(Number, {Pid, AState}, MapNPA), 225 | MapPN1 = dict:store(Pid, Number, MapPN), 226 | State#state{map_num_pa = MapNPA1, 227 | map_pid_num = MapPN1}. 228 | 229 | internal_unregister(Number, Pid, 230 | State = #state{map_num_pa = MapNPA, map_pid_num = MapPN}) -> 231 | MapNPA1 = gb_trees:delete(Number, MapNPA), 232 | MapPN1 = dict:erase(Pid, MapPN), 233 | State#state{map_num_pa = MapNPA1, 234 | map_pid_num = MapPN1}. 235 | 236 | internal_is_empty(#state{map_num_pa = MapNPA}) -> 237 | gb_trees:is_empty(MapNPA). 238 | 239 | internal_num_channels(#state{map_num_pa = MapNPA}) -> 240 | gb_trees:size(MapNPA). 241 | 242 | internal_lookup_npa(Number, #state{map_num_pa = MapNPA}) -> 243 | case gb_trees:lookup(Number, MapNPA) of {value, PA} -> PA; 244 | none -> undefined 245 | end. 246 | 247 | internal_lookup_pn(Pid, #state{map_pid_num = MapPN}) -> 248 | case dict:find(Pid, MapPN) of {ok, Number} -> Number; 249 | error -> undefined 250 | end. 251 | 252 | internal_update_npa(Number, Pid, AState, State = #state{map_num_pa = MapNPA}) -> 253 | State#state{map_num_pa = gb_trees:update(Number, {Pid, AState}, MapNPA)}. 254 | 255 | signal_channels_connection_closing(ChannelCloseType, Reason, 256 | #state{map_pid_num = MapPN}) -> 257 | [amqp_channel:connection_closing(Pid, ChannelCloseType, Reason) 258 | || Pid <- dict:fetch_keys(MapPN)]. 259 | -------------------------------------------------------------------------------- /src/amqp_selective_consumer.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public Licensbe 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2011-2015 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @doc This module is an implementation of the amqp_gen_consumer 18 | %% behaviour and can be used as part of the Consumer parameter when 19 | %% opening AMQP channels. This is the default implementation selected 20 | %% by channel.
21 | %%
22 | %% The Consumer parameter for this implementation is {{@module}, []@}
23 | %% This consumer implementation keeps track of consumer tags and sends 24 | %% the subscription-relevant messages to the registered consumers, according 25 | %% to an internal tag dictionary.
26 | %%
27 | %% Send a #basic.consume{} message to the channel to subscribe a 28 | %% consumer to a queue and send a #basic.cancel{} message to cancel a 29 | %% subscription.
30 | %%
31 | %% The channel will send to the relevant registered consumers the 32 | %% basic.consume_ok, basic.cancel_ok, basic.cancel and basic.deliver messages 33 | %% received from the server.
34 | %%
35 | %% If a consumer is not registered for a given consumer tag, the message 36 | %% is sent to the default consumer registered with 37 | %% {@module}:register_default_consumer. If there is no default consumer 38 | %% registered in this case, an exception occurs and the channel is abruptly 39 | %% terminated.
40 | -module(amqp_selective_consumer). 41 | 42 | -include("amqp_gen_consumer_spec.hrl"). 43 | 44 | -behaviour(amqp_gen_consumer). 45 | 46 | -export([register_default_consumer/2]). 47 | -export([init/1, handle_consume_ok/3, handle_consume/3, handle_cancel_ok/3, 48 | handle_cancel/2, handle_server_cancel/2, 49 | handle_deliver/3, handle_deliver/4, 50 | handle_info/2, handle_call/3, terminate/2]). 51 | 52 | -record(state, {consumers = dict:new(), %% Tag -> ConsumerPid 53 | unassigned = undefined, %% Pid 54 | monitors = dict:new(), %% Pid -> {Count, MRef} 55 | default_consumer = none}). 56 | 57 | %%--------------------------------------------------------------------------- 58 | %% Interface 59 | %%--------------------------------------------------------------------------- 60 | 61 | %% @spec (ChannelPid, ConsumerPid) -> ok 62 | %% where 63 | %% ChannelPid = pid() 64 | %% ConsumerPid = pid() 65 | %% @doc This function registers a default consumer with the channel. A 66 | %% default consumer is used when a subscription is made via 67 | %% amqp_channel:call(ChannelPid, #'basic.consume'{}) (rather than 68 | %% {@module}:subscribe/3) and hence there is no consumer pid 69 | %% registered with the consumer tag. In this case, the relevant 70 | %% deliveries will be sent to the default consumer. 71 | register_default_consumer(ChannelPid, ConsumerPid) -> 72 | amqp_channel:call_consumer(ChannelPid, 73 | {register_default_consumer, ConsumerPid}). 74 | 75 | %%--------------------------------------------------------------------------- 76 | %% amqp_gen_consumer callbacks 77 | %%--------------------------------------------------------------------------- 78 | 79 | %% @private 80 | init([]) -> 81 | {ok, #state{}}. 82 | 83 | %% @private 84 | handle_consume(#'basic.consume'{consumer_tag = Tag, 85 | nowait = NoWait}, 86 | Pid, State = #state{consumers = Consumers, 87 | monitors = Monitors}) -> 88 | Result = case NoWait of 89 | true when Tag =:= undefined orelse size(Tag) == 0 -> 90 | no_consumer_tag_specified; 91 | _ when is_binary(Tag) andalso size(Tag) >= 0 -> 92 | case resolve_consumer(Tag, State) of 93 | {consumer, _} -> consumer_tag_in_use; 94 | _ -> ok 95 | end; 96 | _ -> 97 | ok 98 | end, 99 | case {Result, NoWait} of 100 | {ok, true} -> 101 | {ok, State#state 102 | {consumers = dict:store(Tag, Pid, Consumers), 103 | monitors = add_to_monitor_dict(Pid, Monitors)}}; 104 | {ok, false} -> 105 | {ok, State#state{unassigned = Pid}}; 106 | {Err, true} -> 107 | {error, Err, State}; 108 | {_Err, false} -> 109 | %% Don't do anything (don't override existing 110 | %% consumers), the server will close the channel with an error. 111 | {ok, State} 112 | end. 113 | 114 | %% @private 115 | handle_consume_ok(BasicConsumeOk, _BasicConsume, 116 | State = #state{unassigned = Pid, 117 | consumers = Consumers, 118 | monitors = Monitors}) 119 | when is_pid(Pid) -> 120 | State1 = 121 | State#state{ 122 | consumers = dict:store(tag(BasicConsumeOk), Pid, Consumers), 123 | monitors = add_to_monitor_dict(Pid, Monitors), 124 | unassigned = undefined}, 125 | deliver(BasicConsumeOk, State1), 126 | {ok, State1}. 127 | 128 | %% @private 129 | %% We sent a basic.cancel. 130 | handle_cancel(#'basic.cancel'{nowait = true}, 131 | #state{default_consumer = none}) -> 132 | exit(cancel_nowait_requires_default_consumer); 133 | 134 | handle_cancel(Cancel = #'basic.cancel'{nowait = NoWait}, State) -> 135 | State1 = case NoWait of 136 | true -> do_cancel(Cancel, State); 137 | false -> State 138 | end, 139 | {ok, State1}. 140 | 141 | %% @private 142 | %% We sent a basic.cancel and now receive the ok. 143 | handle_cancel_ok(CancelOk, _Cancel, State) -> 144 | State1 = do_cancel(CancelOk, State), 145 | %% Use old state 146 | deliver(CancelOk, State), 147 | {ok, State1}. 148 | 149 | %% @private 150 | %% The server sent a basic.cancel. 151 | handle_server_cancel(Cancel = #'basic.cancel'{nowait = true}, State) -> 152 | State1 = do_cancel(Cancel, State), 153 | %% Use old state 154 | deliver(Cancel, State), 155 | {ok, State1}. 156 | 157 | %% @private 158 | handle_deliver(Method, Message, State) -> 159 | deliver(Method, Message, State), 160 | {ok, State}. 161 | 162 | %% @private 163 | handle_deliver(Method, Message, DeliveryCtx, State) -> 164 | deliver(Method, Message, DeliveryCtx, State), 165 | {ok, State}. 166 | 167 | %% @private 168 | handle_info({'DOWN', _MRef, process, Pid, _Info}, 169 | State = #state{monitors = Monitors, 170 | consumers = Consumers, 171 | default_consumer = DConsumer }) -> 172 | case dict:find(Pid, Monitors) of 173 | {ok, _CountMRef} -> 174 | {ok, State#state{monitors = dict:erase(Pid, Monitors), 175 | consumers = 176 | dict:filter( 177 | fun (_, Pid1) when Pid1 =:= Pid -> false; 178 | (_, _) -> true 179 | end, Consumers)}}; 180 | error -> 181 | case Pid of 182 | DConsumer -> {ok, State#state{ 183 | monitors = dict:erase(Pid, Monitors), 184 | default_consumer = none}}; 185 | _ -> {ok, State} %% unnamed consumer went down 186 | %% before receiving consume_ok 187 | end 188 | end. 189 | 190 | %% @private 191 | handle_call({register_default_consumer, Pid}, _From, 192 | State = #state{default_consumer = PrevPid, 193 | monitors = Monitors}) -> 194 | Monitors1 = case PrevPid of 195 | none -> Monitors; 196 | _ -> remove_from_monitor_dict(PrevPid, Monitors) 197 | end, 198 | {reply, ok, 199 | State#state{default_consumer = Pid, 200 | monitors = add_to_monitor_dict(Pid, Monitors1)}}. 201 | 202 | %% @private 203 | terminate(_Reason, State) -> 204 | State. 205 | 206 | %%--------------------------------------------------------------------------- 207 | %% Internal plumbing 208 | %%--------------------------------------------------------------------------- 209 | 210 | deliver_to_consumer_or_die(Method, Msg, State) -> 211 | case resolve_consumer(tag(Method), State) of 212 | {consumer, Pid} -> Pid ! Msg; 213 | {default, Pid} -> Pid ! Msg; 214 | error -> exit(unexpected_delivery_and_no_default_consumer) 215 | end. 216 | 217 | deliver(Method, State) -> 218 | deliver(Method, undefined, State). 219 | deliver(Method, Message, State) -> 220 | Combined = if Message =:= undefined -> Method; 221 | true -> {Method, Message} 222 | end, 223 | deliver_to_consumer_or_die(Method, Combined, State). 224 | deliver(Method, Message, DeliveryCtx, State) -> 225 | Combined = if Message =:= undefined -> Method; 226 | true -> {Method, Message, DeliveryCtx} 227 | end, 228 | deliver_to_consumer_or_die(Method, Combined, State). 229 | 230 | do_cancel(Cancel, State = #state{consumers = Consumers, 231 | monitors = Monitors}) -> 232 | Tag = tag(Cancel), 233 | case dict:find(Tag, Consumers) of 234 | {ok, Pid} -> State#state{ 235 | consumers = dict:erase(Tag, Consumers), 236 | monitors = remove_from_monitor_dict(Pid, Monitors)}; 237 | error -> %% Untracked consumer. Do nothing. 238 | State 239 | end. 240 | 241 | resolve_consumer(Tag, #state{consumers = Consumers, 242 | default_consumer = DefaultConsumer}) -> 243 | case dict:find(Tag, Consumers) of 244 | {ok, ConsumerPid} -> {consumer, ConsumerPid}; 245 | error -> case DefaultConsumer of 246 | none -> error; 247 | _ -> {default, DefaultConsumer} 248 | end 249 | end. 250 | 251 | tag(#'basic.consume'{consumer_tag = Tag}) -> Tag; 252 | tag(#'basic.consume_ok'{consumer_tag = Tag}) -> Tag; 253 | tag(#'basic.cancel'{consumer_tag = Tag}) -> Tag; 254 | tag(#'basic.cancel_ok'{consumer_tag = Tag}) -> Tag; 255 | tag(#'basic.deliver'{consumer_tag = Tag}) -> Tag. 256 | 257 | add_to_monitor_dict(Pid, Monitors) -> 258 | case dict:find(Pid, Monitors) of 259 | error -> dict:store(Pid, 260 | {1, erlang:monitor(process, Pid)}, 261 | Monitors); 262 | {ok, {Count, MRef}} -> dict:store(Pid, {Count + 1, MRef}, Monitors) 263 | end. 264 | 265 | remove_from_monitor_dict(Pid, Monitors) -> 266 | case dict:fetch(Pid, Monitors) of 267 | {1, MRef} -> erlang:demonitor(MRef), 268 | dict:erase(Pid, Monitors); 269 | {Count, MRef} -> dict:store(Pid, {Count - 1, MRef}, Monitors) 270 | end. 271 | -------------------------------------------------------------------------------- /src/amqp_uri.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | -module(amqp_uri). 18 | 19 | -include("amqp_client.hrl"). 20 | 21 | -export([parse/1, parse/2, remove_credentials/1]). 22 | 23 | %%--------------------------------------------------------------------------- 24 | %% AMQP URI Parsing 25 | %%--------------------------------------------------------------------------- 26 | 27 | %% Reformat a URI to remove authentication secrets from it (before we 28 | %% log it or display it anywhere). 29 | remove_credentials(URI) -> 30 | Props = uri_parser:parse(URI, 31 | [{host, undefined}, {path, undefined}, 32 | {port, undefined}, {'query', []}]), 33 | PortPart = case proplists:get_value(port, Props) of 34 | undefined -> ""; 35 | Port -> rabbit_misc:format(":~B", [Port]) 36 | end, 37 | PGet = fun(K, P) -> case proplists:get_value(K, P) of 38 | undefined -> ""; 39 | R -> R 40 | end 41 | end, 42 | rabbit_misc:format( 43 | "~s://~s~s~s", [proplists:get_value(scheme, Props), PGet(host, Props), 44 | PortPart, PGet(path, Props)]). 45 | 46 | %% @spec (Uri) -> {ok, #amqp_params_network{} | #amqp_params_direct{}} | 47 | %% {error, {Info, Uri}} 48 | %% where 49 | %% Uri = string() 50 | %% Info = any() 51 | %% 52 | %% @doc Parses an AMQP URI. If any of the URI parts are missing, the 53 | %% default values are used. If the hostname is zero-length, an 54 | %% #amqp_params_direct{} record is returned; otherwise, an 55 | %% #amqp_params_network{} record is returned. Extra parameters may be 56 | %% specified via the query string 57 | %% (e.g. "?heartbeat=5&auth_mechanism=external"). In case of failure, 58 | %% an {error, {Info, Uri}} tuple is returned. 59 | %% 60 | %% The extra parameters that may be specified are channel_max, 61 | %% frame_max, heartbeat and auth_mechanism (the latter can appear more 62 | %% than once). The extra parameters that may be specified for an SSL 63 | %% connection are cacertfile, certfile, keyfile, verify, 64 | %% fail_if_no_peer_cert, password, and depth. 65 | parse(Uri) -> parse(Uri, <<"/">>). 66 | 67 | parse(Uri, DefaultVHost) -> 68 | try return(parse1(Uri, DefaultVHost)) 69 | catch throw:Err -> {error, {Err, Uri}}; 70 | error:Err -> {error, {Err, Uri}} 71 | end. 72 | 73 | parse1(Uri, DefaultVHost) when is_list(Uri) -> 74 | case uri_parser:parse(Uri, [{host, undefined}, {path, undefined}, 75 | {port, undefined}, {'query', []}]) of 76 | {error, Err} -> 77 | throw({unable_to_parse_uri, Err}); 78 | Parsed -> 79 | Endpoint = 80 | case string:to_lower(proplists:get_value(scheme, Parsed)) of 81 | "amqp" -> build_broker(Parsed, DefaultVHost); 82 | "amqps" -> build_ssl_broker(Parsed, DefaultVHost); 83 | Scheme -> fail({unexpected_uri_scheme, Scheme}) 84 | end, 85 | return({ok, broker_add_query(Endpoint, Parsed)}) 86 | end; 87 | parse1(_, _DefaultVHost) -> 88 | fail(expected_string_uri). 89 | 90 | unescape_string(Atom) when is_atom(Atom) -> 91 | Atom; 92 | unescape_string(Integer) when is_integer(Integer) -> 93 | Integer; 94 | unescape_string([]) -> 95 | []; 96 | unescape_string([$%, N1, N2 | Rest]) -> 97 | try 98 | [erlang:list_to_integer([N1, N2], 16) | unescape_string(Rest)] 99 | catch 100 | error:badarg -> throw({invalid_entitiy, ['%', N1, N2]}) 101 | end; 102 | unescape_string([$% | Rest]) -> 103 | fail({unterminated_entity, ['%' | Rest]}); 104 | unescape_string([C | Rest]) -> 105 | [C | unescape_string(Rest)]. 106 | 107 | build_broker(ParsedUri, DefaultVHost) -> 108 | [Host, Port, Path] = 109 | [proplists:get_value(F, ParsedUri) || F <- [host, port, path]], 110 | case Port =:= undefined orelse (0 < Port andalso Port =< 65535) of 111 | true -> ok; 112 | false -> fail({port_out_of_range, Port}) 113 | end, 114 | VHost = case Path of 115 | undefined -> DefaultVHost; 116 | [$/|Rest] -> case string:chr(Rest, $/) of 117 | 0 -> list_to_binary(unescape_string(Rest)); 118 | _ -> fail({invalid_vhost, Rest}) 119 | end 120 | end, 121 | UserInfo = proplists:get_value(userinfo, ParsedUri), 122 | set_user_info(case unescape_string(Host) of 123 | undefined -> #amqp_params_direct{virtual_host = VHost}; 124 | Host1 -> Mech = mechanisms(ParsedUri), 125 | #amqp_params_network{host = Host1, 126 | port = Port, 127 | virtual_host = VHost, 128 | auth_mechanisms = Mech} 129 | end, UserInfo). 130 | 131 | set_user_info(Ps, UserInfo) -> 132 | case UserInfo of 133 | [U, P | _] -> set([{username, list_to_binary(unescape_string(U))}, 134 | {password, list_to_binary(unescape_string(P))}], Ps); 135 | 136 | [U] -> set([{username, list_to_binary(unescape_string(U))}], Ps); 137 | [] -> Ps 138 | end. 139 | 140 | set(KVs, Ps = #amqp_params_direct{}) -> 141 | set(KVs, Ps, record_info(fields, amqp_params_direct)); 142 | set(KVs, Ps = #amqp_params_network{}) -> 143 | set(KVs, Ps, record_info(fields, amqp_params_network)). 144 | 145 | set(KVs, Ps, Fields) -> 146 | {Ps1, _Ix} = lists:foldl(fun (Field, {PsN, Ix}) -> 147 | {case lists:keyfind(Field, 1, KVs) of 148 | false -> PsN; 149 | {_, V} -> setelement(Ix, PsN, V) 150 | end, Ix + 1} 151 | end, {Ps, 2}, Fields), 152 | Ps1. 153 | 154 | build_ssl_broker(ParsedUri, DefaultVHost) -> 155 | Params = build_broker(ParsedUri, DefaultVHost), 156 | Query = proplists:get_value('query', ParsedUri), 157 | SSLOptions = 158 | run_state_monad( 159 | [fun (L) -> KeyString = atom_to_list(Key), 160 | case lists:keysearch(KeyString, 1, Query) of 161 | {value, {_, Value}} -> 162 | try return([{Key, unescape_string(Fun(Value))} | L]) 163 | catch throw:Reason -> 164 | fail({invalid_ssl_parameter, 165 | Key, Value, Query, Reason}) 166 | end; 167 | false -> 168 | L 169 | end 170 | end || {Fun, Key} <- 171 | [{fun find_path_parameter/1, cacertfile}, 172 | {fun find_path_parameter/1, certfile}, 173 | {fun find_path_parameter/1, keyfile}, 174 | {fun find_atom_parameter/1, verify}, 175 | {fun find_boolean_parameter/1, fail_if_no_peer_cert}, 176 | {fun find_identity_parameter/1, password}, 177 | {fun find_integer_parameter/1, depth}]], 178 | []), 179 | Params#amqp_params_network{ssl_options = SSLOptions}. 180 | 181 | broker_add_query(Params = #amqp_params_direct{}, Uri) -> 182 | broker_add_query(Params, Uri, record_info(fields, amqp_params_direct)); 183 | broker_add_query(Params = #amqp_params_network{}, Uri) -> 184 | broker_add_query(Params, Uri, record_info(fields, amqp_params_network)). 185 | 186 | broker_add_query(Params, ParsedUri, Fields) -> 187 | Query = proplists:get_value('query', ParsedUri), 188 | {Params1, _Pos} = 189 | run_state_monad( 190 | [fun ({ParamsN, Pos}) -> 191 | Pos1 = Pos + 1, 192 | KeyString = atom_to_list(Field), 193 | case proplists:get_value(KeyString, Query) of 194 | undefined -> 195 | return({ParamsN, Pos1}); 196 | true -> %% proplists short form, not permitted 197 | return({ParamsN, Pos1}); 198 | Value -> 199 | try 200 | ValueParsed = parse_amqp_param(Field, Value), 201 | return( 202 | {setelement(Pos, ParamsN, ValueParsed), Pos1}) 203 | catch throw:Reason -> 204 | fail({invalid_amqp_params_parameter, 205 | Field, Value, Query, Reason}) 206 | end 207 | end 208 | end || Field <- Fields], {Params, 2}), 209 | Params1. 210 | 211 | parse_amqp_param(Field, String) when Field =:= channel_max orelse 212 | Field =:= frame_max orelse 213 | Field =:= heartbeat orelse 214 | Field =:= connection_timeout orelse 215 | Field =:= depth -> 216 | find_integer_parameter(String); 217 | parse_amqp_param(Field, String) when Field =:= password -> 218 | find_identity_parameter(String); 219 | parse_amqp_param(Field, String) -> 220 | fail({parameter_unconfigurable_in_query, Field, String}). 221 | 222 | find_path_parameter(Value) -> 223 | find_identity_parameter(Value). 224 | 225 | find_identity_parameter(Value) -> return(Value). 226 | 227 | find_integer_parameter(Value) -> 228 | try return(list_to_integer(Value)) 229 | catch error:badarg -> fail({not_an_integer, Value}) 230 | end. 231 | 232 | find_boolean_parameter(Value) -> 233 | Bool = list_to_atom(Value), 234 | case is_boolean(Bool) of 235 | true -> return(Bool); 236 | false -> fail({require_boolean, Bool}) 237 | end. 238 | 239 | find_atom_parameter(Value) -> return(list_to_atom(Value)). 240 | 241 | mechanisms(ParsedUri) -> 242 | Query = proplists:get_value('query', ParsedUri), 243 | Mechanisms = case proplists:get_all_values("auth_mechanism", Query) of 244 | [] -> ["plain", "amqplain"]; 245 | Mechs -> Mechs 246 | end, 247 | [case [list_to_atom(T) || T <- string:tokens(Mech, ":")] of 248 | [F] -> fun (R, P, S) -> amqp_auth_mechanisms:F(R, P, S) end; 249 | [M, F] -> fun (R, P, S) -> M:F(R, P, S) end; 250 | L -> throw({not_mechanism, L}) 251 | end || Mech <- Mechanisms]. 252 | 253 | %% --=: Plain state monad implementation start :=-- 254 | run_state_monad(FunList, State) -> 255 | lists:foldl(fun (Fun, StateN) -> Fun(StateN) end, State, FunList). 256 | 257 | return(V) -> V. 258 | 259 | fail(Reason) -> throw(Reason). 260 | %% --=: end :=-- 261 | -------------------------------------------------------------------------------- /src/amqp_gen_consumer.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2011-2015 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @doc A behaviour module for implementing consumers for 18 | %% amqp_channel. To specify a consumer implementation for a channel, 19 | %% use amqp_connection:open_channel/{2,3}. 20 | %%
21 | %% All callbacks are called within the gen_consumer process.
22 | %%
23 | %% See comments in amqp_gen_consumer.erl source file for documentation 24 | %% on the callback functions. 25 | %%
26 | %% Note that making calls to the channel from the callback module will 27 | %% result in deadlock. 28 | -module(amqp_gen_consumer). 29 | 30 | -include("amqp_client.hrl"). 31 | 32 | -behaviour(gen_server2). 33 | 34 | -export([start_link/3, call_consumer/2, call_consumer/3, call_consumer/4]). 35 | -export([behaviour_info/1]). 36 | -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, 37 | handle_info/2, prioritise_info/3]). 38 | 39 | -record(state, {module, 40 | module_state}). 41 | 42 | %%--------------------------------------------------------------------------- 43 | %% Interface 44 | %%--------------------------------------------------------------------------- 45 | 46 | %% @type ok_error() = {ok, state()} | {error, reason(), state()}. 47 | %% Denotes a successful or an error return from a consumer module call. 48 | 49 | start_link(ConsumerModule, ExtraParams, Identity) -> 50 | gen_server2:start_link( 51 | ?MODULE, [ConsumerModule, ExtraParams, Identity], []). 52 | 53 | %% @spec (Consumer, Msg) -> ok 54 | %% where 55 | %% Consumer = pid() 56 | %% Msg = any() 57 | %% 58 | %% @doc This function is used to perform arbitrary calls into the 59 | %% consumer module. 60 | call_consumer(Pid, Msg) -> 61 | gen_server2:call(Pid, {consumer_call, Msg}, infinity). 62 | 63 | %% @spec (Consumer, Method, Args) -> ok 64 | %% where 65 | %% Consumer = pid() 66 | %% Method = amqp_method() 67 | %% Args = any() 68 | %% 69 | %% @doc This function is used by amqp_channel to forward received 70 | %% methods and deliveries to the consumer module. 71 | call_consumer(Pid, Method, Args) -> 72 | gen_server2:call(Pid, {consumer_call, Method, Args}, infinity). 73 | 74 | call_consumer(Pid, Method, Args, DeliveryCtx) -> 75 | gen_server2:call(Pid, {consumer_call, Method, Args, DeliveryCtx}, infinity). 76 | 77 | %%--------------------------------------------------------------------------- 78 | %% Behaviour 79 | %%--------------------------------------------------------------------------- 80 | 81 | %% @private 82 | behaviour_info(callbacks) -> 83 | [ 84 | %% init(Args) -> {ok, InitialState} | {stop, Reason} | ignore 85 | %% where 86 | %% Args = [any()] 87 | %% InitialState = state() 88 | %% Reason = term() 89 | %% 90 | %% This callback is invoked by the channel, when it starts 91 | %% up. Use it to initialize the state of the consumer. In case of 92 | %% an error, return {stop, Reason} or ignore. 93 | {init, 1}, 94 | 95 | %% handle_consume(Consume, Sender, State) -> ok_error() 96 | %% where 97 | %% Consume = #'basic.consume'{} 98 | %% Sender = pid() 99 | %% State = state() 100 | %% 101 | %% This callback is invoked by the channel before a basic.consume 102 | %% is sent to the server. 103 | {handle_consume, 3}, 104 | 105 | %% handle_consume_ok(ConsumeOk, Consume, State) -> ok_error() 106 | %% where 107 | %% ConsumeOk = #'basic.consume_ok'{} 108 | %% Consume = #'basic.consume'{} 109 | %% State = state() 110 | %% 111 | %% This callback is invoked by the channel every time a 112 | %% basic.consume_ok is received from the server. Consume is the original 113 | %% method sent out to the server - it can be used to associate the 114 | %% call with the response. 115 | {handle_consume_ok, 3}, 116 | 117 | %% handle_cancel(Cancel, State) -> ok_error() 118 | %% where 119 | %% Cancel = #'basic.cancel'{} 120 | %% State = state() 121 | %% 122 | %% This callback is invoked by the channel every time a basic.cancel 123 | %% is sent to the server. 124 | {handle_cancel, 2}, 125 | 126 | %% handle_cancel_ok(CancelOk, Cancel, State) -> ok_error() 127 | %% where 128 | %% CancelOk = #'basic.cancel_ok'{} 129 | %% Cancel = #'basic.cancel'{} 130 | %% State = state() 131 | %% 132 | %% This callback is invoked by the channel every time a basic.cancel_ok 133 | %% is received from the server. 134 | {handle_cancel_ok, 3}, 135 | 136 | %% handle_server_cancel(Cancel, State) -> ok_error() 137 | %% where 138 | %% Cancel = #'basic.cancel'{} 139 | %% State = state() 140 | %% 141 | %% This callback is invoked by the channel every time a basic.cancel 142 | %% is received from the server. 143 | {handle_server_cancel, 2}, 144 | 145 | %% handle_deliver(Deliver, Message, State) -> ok_error() 146 | %% where 147 | %% Deliver = #'basic.deliver'{} 148 | %% Message = #amqp_msg{} 149 | %% State = state() 150 | %% 151 | %% This callback is invoked by the channel every time a basic.deliver 152 | %% is received from the server. 153 | {handle_deliver, 3}, 154 | 155 | %% handle_deliver(Deliver, Message, 156 | %% DeliveryCtx, State) -> ok_error() 157 | %% where 158 | %% Deliver = #'basic.deliver'{} 159 | %% Message = #amqp_msg{} 160 | %% DeliveryCtx = {pid(), pid(), pid()} 161 | %% State = state() 162 | %% 163 | %% This callback is invoked by the channel every time a basic.deliver 164 | %% is received from the server. Only relevant for channels that use 165 | %% direct client connection and manual flow control. 166 | {handle_deliver, 4}, 167 | 168 | %% handle_info(Info, State) -> ok_error() 169 | %% where 170 | %% Info = any() 171 | %% State = state() 172 | %% 173 | %% This callback is invoked the consumer process receives a 174 | %% message. 175 | {handle_info, 2}, 176 | 177 | %% handle_call(Msg, From, State) -> {reply, Reply, NewState} | 178 | %% {noreply, NewState} | 179 | %% {error, Reason, NewState} 180 | %% where 181 | %% Msg = any() 182 | %% From = any() 183 | %% Reply = any() 184 | %% State = state() 185 | %% NewState = state() 186 | %% 187 | %% This callback is invoked by the channel when calling 188 | %% amqp_channel:call_consumer/2. Reply is the term that 189 | %% amqp_channel:call_consumer/2 will return. If the callback 190 | %% returns {noreply, _}, then the caller to 191 | %% amqp_channel:call_consumer/2 and the channel remain blocked 192 | %% until gen_server2:reply/2 is used with the provided From as 193 | %% the first argument. 194 | {handle_call, 3}, 195 | 196 | %% terminate(Reason, State) -> any() 197 | %% where 198 | %% Reason = any() 199 | %% State = state() 200 | %% 201 | %% This callback is invoked by the channel after it has shut down and 202 | %% just before its process exits. 203 | {terminate, 2} 204 | ]; 205 | behaviour_info(_Other) -> 206 | undefined. 207 | 208 | %%--------------------------------------------------------------------------- 209 | %% gen_server2 callbacks 210 | %%--------------------------------------------------------------------------- 211 | 212 | init([ConsumerModule, ExtraParams, Identity]) -> 213 | ?store_proc_name(Identity), 214 | case ConsumerModule:init(ExtraParams) of 215 | {ok, MState} -> 216 | {ok, #state{module = ConsumerModule, module_state = MState}}; 217 | {stop, Reason} -> 218 | {stop, Reason}; 219 | ignore -> 220 | ignore 221 | end. 222 | 223 | prioritise_info({'DOWN', _MRef, process, _Pid, _Info}, _Len, _State) -> 1; 224 | prioritise_info(_, _Len, _State) -> 0. 225 | 226 | consumer_call_reply(Return, State) -> 227 | case Return of 228 | {ok, NewMState} -> 229 | {reply, ok, State#state{module_state = NewMState}}; 230 | {error, Reason, NewMState} -> 231 | {stop, {error, Reason}, {error, Reason}, 232 | State#state{module_state = NewMState}} 233 | end. 234 | 235 | handle_call({consumer_call, Msg}, From, 236 | State = #state{module = ConsumerModule, 237 | module_state = MState}) -> 238 | case ConsumerModule:handle_call(Msg, From, MState) of 239 | {noreply, NewMState} -> 240 | {noreply, State#state{module_state = NewMState}}; 241 | {reply, Reply, NewMState} -> 242 | {reply, Reply, State#state{module_state = NewMState}}; 243 | {error, Reason, NewMState} -> 244 | {stop, {error, Reason}, {error, Reason}, 245 | State#state{module_state = NewMState}} 246 | end; 247 | handle_call({consumer_call, Method, Args}, _From, 248 | State = #state{module = ConsumerModule, 249 | module_state = MState}) -> 250 | Return = 251 | case Method of 252 | #'basic.consume'{} -> 253 | ConsumerModule:handle_consume(Method, Args, MState); 254 | #'basic.consume_ok'{} -> 255 | ConsumerModule:handle_consume_ok(Method, Args, MState); 256 | #'basic.cancel'{} -> 257 | case Args of 258 | none -> %% server-sent 259 | ConsumerModule:handle_server_cancel(Method, MState); 260 | Pid when is_pid(Pid) -> %% client-sent 261 | ConsumerModule:handle_cancel(Method, MState) 262 | end; 263 | #'basic.cancel_ok'{} -> 264 | ConsumerModule:handle_cancel_ok(Method, Args, MState); 265 | #'basic.deliver'{} -> 266 | ConsumerModule:handle_deliver(Method, Args, MState) 267 | end, 268 | consumer_call_reply(Return, State); 269 | 270 | %% only supposed to be used with basic.deliver 271 | handle_call({consumer_call, Method = #'basic.deliver'{}, Args, DeliveryCtx}, _From, 272 | State = #state{module = ConsumerModule, 273 | module_state = MState}) -> 274 | Return = ConsumerModule:handle_deliver(Method, Args, DeliveryCtx, MState), 275 | consumer_call_reply(Return, State). 276 | 277 | handle_cast(_What, State) -> 278 | {noreply, State}. 279 | 280 | handle_info(Info, State = #state{module_state = MState, 281 | module = ConsumerModule}) -> 282 | case ConsumerModule:handle_info(Info, MState) of 283 | {ok, NewMState} -> 284 | {noreply, State#state{module_state = NewMState}}; 285 | {error, Reason, NewMState} -> 286 | {stop, {error, Reason}, State#state{module_state = NewMState}} 287 | end. 288 | 289 | terminate(Reason, #state{module = ConsumerModule, module_state = MState}) -> 290 | ConsumerModule:terminate(Reason, MState). 291 | 292 | code_change(_OldVsn, State, _Extra) -> 293 | {ok, State}. 294 | -------------------------------------------------------------------------------- /src/amqp_gen_connection.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_gen_connection). 19 | 20 | -include("amqp_client_internal.hrl"). 21 | 22 | -behaviour(gen_server). 23 | 24 | -export([start_link/2, connect/1, open_channel/3, hard_error_in_channel/3, 25 | channel_internal_error/3, server_misbehaved/2, channels_terminated/1, 26 | close/3, server_close/2, info/2, info_keys/0, info_keys/1, 27 | register_blocked_handler/2]). 28 | -export([behaviour_info/1]). 29 | -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, 30 | handle_info/2]). 31 | 32 | -define(INFO_KEYS, [server_properties, is_closing, amqp_params, num_channels, 33 | channel_max]). 34 | 35 | -record(state, {module, 36 | module_state, 37 | channels_manager, 38 | amqp_params, 39 | channel_max, 40 | server_properties, 41 | %% connection.block, connection.unblock handler 42 | block_handler, 43 | closing = false %% #closing{} | false 44 | }). 45 | 46 | -record(closing, {reason, 47 | close, 48 | from = none}). 49 | 50 | %%--------------------------------------------------------------------------- 51 | %% Interface 52 | %%--------------------------------------------------------------------------- 53 | 54 | start_link(TypeSup, AMQPParams) -> 55 | gen_server:start_link(?MODULE, {TypeSup, AMQPParams}, []). 56 | 57 | connect(Pid) -> 58 | gen_server:call(Pid, connect, infinity). 59 | 60 | open_channel(Pid, ProposedNumber, Consumer) -> 61 | case gen_server:call(Pid, 62 | {command, {open_channel, ProposedNumber, Consumer}}, 63 | infinity) of 64 | {ok, ChannelPid} -> ok = amqp_channel:open(ChannelPid), 65 | {ok, ChannelPid}; 66 | Error -> Error 67 | end. 68 | 69 | hard_error_in_channel(Pid, ChannelPid, Reason) -> 70 | gen_server:cast(Pid, {hard_error_in_channel, ChannelPid, Reason}). 71 | 72 | channel_internal_error(Pid, ChannelPid, Reason) -> 73 | gen_server:cast(Pid, {channel_internal_error, ChannelPid, Reason}). 74 | 75 | server_misbehaved(Pid, AmqpError) -> 76 | gen_server:cast(Pid, {server_misbehaved, AmqpError}). 77 | 78 | channels_terminated(Pid) -> 79 | gen_server:cast(Pid, channels_terminated). 80 | 81 | close(Pid, Close, Timeout) -> 82 | gen_server:call(Pid, {command, {close, Close, Timeout}}, infinity). 83 | 84 | server_close(Pid, Close) -> 85 | gen_server:cast(Pid, {server_close, Close}). 86 | 87 | info(Pid, Items) -> 88 | gen_server:call(Pid, {info, Items}, infinity). 89 | 90 | info_keys() -> 91 | ?INFO_KEYS. 92 | 93 | info_keys(Pid) -> 94 | gen_server:call(Pid, info_keys, infinity). 95 | 96 | %%--------------------------------------------------------------------------- 97 | %% Behaviour 98 | %%--------------------------------------------------------------------------- 99 | 100 | behaviour_info(callbacks) -> 101 | [ 102 | %% init() -> {ok, InitialState} 103 | {init, 0}, 104 | 105 | %% terminate(Reason, FinalState) -> Ignored 106 | {terminate, 2}, 107 | 108 | %% connect(AmqpParams, SIF, TypeSup, State) -> 109 | %% {ok, ConnectParams} | {closing, ConnectParams, AmqpError, Reply} | 110 | %% {error, Error} 111 | %% where 112 | %% ConnectParams = {ServerProperties, ChannelMax, ChMgr, NewState} 113 | {connect, 4}, 114 | 115 | %% do(Method, State) -> Ignored 116 | {do, 2}, 117 | 118 | %% open_channel_args(State) -> OpenChannelArgs 119 | {open_channel_args, 1}, 120 | 121 | %% i(InfoItem, State) -> Info 122 | {i, 2}, 123 | 124 | %% info_keys() -> [InfoItem] 125 | {info_keys, 0}, 126 | 127 | %% CallbackReply = {ok, NewState} | {stop, Reason, FinalState} 128 | 129 | %% handle_message(Message, State) -> CallbackReply 130 | {handle_message, 2}, 131 | 132 | %% closing(flush|abrupt, Reason, State) -> CallbackReply 133 | {closing, 3}, 134 | 135 | %% channels_terminated(State) -> CallbackReply 136 | {channels_terminated, 1} 137 | ]; 138 | behaviour_info(_Other) -> 139 | undefined. 140 | 141 | callback(Function, Params, State = #state{module = Mod, 142 | module_state = MState}) -> 143 | case erlang:apply(Mod, Function, Params ++ [MState]) of 144 | {ok, NewMState} -> {noreply, 145 | State#state{module_state = NewMState}}; 146 | {stop, Reason, NewMState} -> {stop, Reason, 147 | State#state{module_state = NewMState}} 148 | end. 149 | 150 | %%--------------------------------------------------------------------------- 151 | %% gen_server callbacks 152 | %%--------------------------------------------------------------------------- 153 | 154 | init({TypeSup, AMQPParams}) -> 155 | %% Trapping exits since we need to make sure that the `terminate/2' is 156 | %% called in the case of direct connection (it does not matter for a network 157 | %% connection). See bug25116. 158 | process_flag(trap_exit, true), 159 | %% connect() has to be called first, so we can use a special state here 160 | {ok, {TypeSup, AMQPParams}}. 161 | 162 | handle_call(connect, _From, {TypeSup, AMQPParams}) -> 163 | {Type, Mod} = amqp_connection_type_sup:type_module(AMQPParams), 164 | {ok, MState} = Mod:init(), 165 | SIF = amqp_connection_type_sup:start_infrastructure_fun( 166 | TypeSup, self(), Type), 167 | State = #state{module = Mod, 168 | module_state = MState, 169 | amqp_params = AMQPParams, 170 | block_handler = none}, 171 | case Mod:connect(AMQPParams, SIF, TypeSup, MState) of 172 | {ok, Params} -> 173 | {reply, {ok, self()}, after_connect(Params, State)}; 174 | {closing, #amqp_error{name = access_refused} = AmqpError, Error} -> 175 | {stop, {shutdown, AmqpError}, Error, State}; 176 | {closing, Params, #amqp_error{} = AmqpError, Error} -> 177 | server_misbehaved(self(), AmqpError), 178 | {reply, Error, after_connect(Params, State)}; 179 | {error, _} = Error -> 180 | {stop, {shutdown, Error}, Error, State} 181 | end; 182 | handle_call({command, Command}, From, State = #state{closing = false}) -> 183 | handle_command(Command, From, State); 184 | handle_call({command, _Command}, _From, State) -> 185 | {reply, closing, State}; 186 | handle_call({info, Items}, _From, State) -> 187 | {reply, [{Item, i(Item, State)} || Item <- Items], State}; 188 | handle_call(info_keys, _From, State = #state{module = Mod}) -> 189 | {reply, ?INFO_KEYS ++ Mod:info_keys(), State}. 190 | 191 | after_connect({ServerProperties, ChannelMax, ChMgr, NewMState}, State) -> 192 | case ChannelMax of 193 | 0 -> ok; 194 | _ -> amqp_channels_manager:set_channel_max(ChMgr, ChannelMax) 195 | end, 196 | State1 = State#state{server_properties = ServerProperties, 197 | channel_max = ChannelMax, 198 | channels_manager = ChMgr, 199 | module_state = NewMState}, 200 | rabbit_misc:store_proc_name(?MODULE, i(name, State1)), 201 | State1. 202 | 203 | handle_cast({method, Method, none, noflow}, State) -> 204 | handle_method(Method, State); 205 | handle_cast(channels_terminated, State) -> 206 | handle_channels_terminated(State); 207 | handle_cast({hard_error_in_channel, _Pid, Reason}, State) -> 208 | server_initiated_close(Reason, State); 209 | handle_cast({channel_internal_error, Pid, Reason}, State) -> 210 | ?LOG_WARN("Connection (~p) closing: internal error in channel (~p): ~p~n", 211 | [self(), Pid, Reason]), 212 | internal_error(Pid, Reason, State); 213 | handle_cast({server_misbehaved, AmqpError}, State) -> 214 | server_misbehaved_close(AmqpError, State); 215 | handle_cast({server_close, #'connection.close'{} = Close}, State) -> 216 | server_initiated_close(Close, State); 217 | handle_cast({register_blocked_handler, HandlerPid}, State) -> 218 | Ref = erlang:monitor(process, HandlerPid), 219 | {noreply, State#state{block_handler = {HandlerPid, Ref}}}. 220 | 221 | %% @private 222 | handle_info({'DOWN', _, process, BlockHandler, Reason}, 223 | State = #state{block_handler = {BlockHandler, _Ref}}) -> 224 | ?LOG_WARN("Connection (~p): Unregistering block handler ~p because it died. " 225 | "Reason: ~p~n", [self(), BlockHandler, Reason]), 226 | {noreply, State#state{block_handler = none}}; 227 | handle_info(Info, State) -> 228 | callback(handle_message, [Info], State). 229 | 230 | terminate(Reason, #state{module = Mod, module_state = MState}) -> 231 | Mod:terminate(Reason, MState). 232 | 233 | code_change(_OldVsn, State, _Extra) -> 234 | {ok, State}. 235 | 236 | %%--------------------------------------------------------------------------- 237 | %% Infos 238 | %%--------------------------------------------------------------------------- 239 | 240 | i(server_properties, State) -> State#state.server_properties; 241 | i(is_closing, State) -> State#state.closing =/= false; 242 | i(amqp_params, State) -> State#state.amqp_params; 243 | i(channel_max, State) -> State#state.channel_max; 244 | i(num_channels, State) -> amqp_channels_manager:num_channels( 245 | State#state.channels_manager); 246 | i(Item, #state{module = Mod, module_state = MState}) -> Mod:i(Item, MState). 247 | 248 | %%--------------------------------------------------------------------------- 249 | %% connection.blocked, connection.unblocked 250 | %%--------------------------------------------------------------------------- 251 | 252 | register_blocked_handler(Pid, HandlerPid) -> 253 | gen_server:cast(Pid, {register_blocked_handler, HandlerPid}). 254 | 255 | %%--------------------------------------------------------------------------- 256 | %% Command handling 257 | %%--------------------------------------------------------------------------- 258 | 259 | handle_command({open_channel, ProposedNumber, Consumer}, _From, 260 | State = #state{channels_manager = ChMgr, 261 | module = Mod, 262 | module_state = MState}) -> 263 | {reply, amqp_channels_manager:open_channel(ChMgr, ProposedNumber, Consumer, 264 | Mod:open_channel_args(MState)), 265 | State}; 266 | handle_command({close, #'connection.close'{} = Close, Timeout}, From, State) -> 267 | app_initiated_close(Close, From, Timeout, State). 268 | 269 | %%--------------------------------------------------------------------------- 270 | %% Handling methods from broker 271 | %%--------------------------------------------------------------------------- 272 | 273 | handle_method(#'connection.close'{} = Close, State) -> 274 | server_initiated_close(Close, State); 275 | handle_method(#'connection.close_ok'{}, State = #state{closing = Closing}) -> 276 | case Closing of #closing{from = none} -> ok; 277 | #closing{from = From} -> gen_server:reply(From, ok) 278 | end, 279 | {stop, {shutdown, closing_to_reason(Closing)}, State}; 280 | handle_method(#'connection.blocked'{} = Blocked, State = #state{block_handler = BlockHandler}) -> 281 | case BlockHandler of none -> ok; 282 | {Pid, _Ref} -> Pid ! Blocked 283 | end, 284 | {noreply, State}; 285 | handle_method(#'connection.unblocked'{} = Unblocked, State = #state{block_handler = BlockHandler}) -> 286 | case BlockHandler of none -> ok; 287 | {Pid, _Ref} -> Pid ! Unblocked 288 | end, 289 | {noreply, State}; 290 | handle_method(Other, State) -> 291 | server_misbehaved_close(#amqp_error{name = command_invalid, 292 | explanation = "unexpected method on " 293 | "channel 0", 294 | method = element(1, Other)}, 295 | State). 296 | 297 | %%--------------------------------------------------------------------------- 298 | %% Closing 299 | %%--------------------------------------------------------------------------- 300 | 301 | app_initiated_close(Close, From, Timeout, State) -> 302 | case Timeout of 303 | infinity -> ok; 304 | _ -> erlang:send_after(Timeout, self(), closing_timeout) 305 | end, 306 | set_closing_state(flush, #closing{reason = app_initiated_close, 307 | close = Close, 308 | from = From}, State). 309 | 310 | internal_error(Pid, Reason, State) -> 311 | Str = list_to_binary(rabbit_misc:format("~p:~p", [Pid, Reason])), 312 | Close = #'connection.close'{reply_text = Str, 313 | reply_code = ?INTERNAL_ERROR, 314 | class_id = 0, 315 | method_id = 0}, 316 | set_closing_state(abrupt, #closing{reason = internal_error, close = Close}, 317 | State). 318 | 319 | server_initiated_close(Close, State) -> 320 | ?LOG_WARN("Connection (~p) closing: received hard error ~p " 321 | "from server~n", [self(), Close]), 322 | set_closing_state(abrupt, #closing{reason = server_initiated_close, 323 | close = Close}, State). 324 | 325 | server_misbehaved_close(AmqpError, State) -> 326 | ?LOG_WARN("Connection (~p) closing: server misbehaved: ~p~n", 327 | [self(), AmqpError]), 328 | {0, Close} = rabbit_binary_generator:map_exception(0, AmqpError, ?PROTOCOL), 329 | set_closing_state(abrupt, #closing{reason = server_misbehaved, 330 | close = Close}, State). 331 | 332 | set_closing_state(ChannelCloseType, NewClosing, 333 | State = #state{channels_manager = ChMgr, 334 | closing = CurClosing}) -> 335 | ResClosing = 336 | case closing_priority(NewClosing) =< closing_priority(CurClosing) of 337 | true -> NewClosing; 338 | false -> CurClosing 339 | end, 340 | ClosingReason = closing_to_reason(ResClosing), 341 | amqp_channels_manager:signal_connection_closing(ChMgr, ChannelCloseType, 342 | ClosingReason), 343 | callback(closing, [ChannelCloseType, ClosingReason], 344 | State#state{closing = ResClosing}). 345 | 346 | closing_priority(false) -> 99; 347 | closing_priority(#closing{reason = app_initiated_close}) -> 4; 348 | closing_priority(#closing{reason = internal_error}) -> 3; 349 | closing_priority(#closing{reason = server_misbehaved}) -> 2; 350 | closing_priority(#closing{reason = server_initiated_close}) -> 1. 351 | 352 | closing_to_reason(#closing{close = #'connection.close'{reply_code = 200}}) -> 353 | normal; 354 | closing_to_reason(#closing{reason = Reason, 355 | close = #'connection.close'{reply_code = Code, 356 | reply_text = Text}}) -> 357 | {Reason, Code, Text}; 358 | closing_to_reason(#closing{reason = Reason, 359 | close = {Reason, _Code, _Text} = Close}) -> 360 | Close. 361 | 362 | handle_channels_terminated(State = #state{closing = Closing, 363 | module = Mod, 364 | module_state = MState}) -> 365 | #closing{reason = Reason, close = Close, from = From} = Closing, 366 | case Reason of 367 | server_initiated_close -> 368 | Mod:do(#'connection.close_ok'{}, MState); 369 | _ -> 370 | Mod:do(Close, MState) 371 | end, 372 | case callback(channels_terminated, [], State) of 373 | {stop, _, _} = Stop -> case From of none -> ok; 374 | _ -> gen_server:reply(From, ok) 375 | end, 376 | Stop; 377 | Other -> Other 378 | end. 379 | -------------------------------------------------------------------------------- /src/amqp_network_connection.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @private 18 | -module(amqp_network_connection). 19 | 20 | -include("amqp_client_internal.hrl"). 21 | 22 | -behaviour(amqp_gen_connection). 23 | -export([init/0, terminate/2, connect/4, do/2, open_channel_args/1, i/2, 24 | info_keys/0, handle_message/2, closing/3, channels_terminated/1]). 25 | 26 | -define(RABBIT_TCP_OPTS, [binary, {packet, 0}, {active,false}, {nodelay, true}]). 27 | -define(SOCKET_CLOSING_TIMEOUT, 1000). 28 | -define(HANDSHAKE_RECEIVE_TIMEOUT, 60000). 29 | -define(TCP_MAX_PACKET_SIZE, (16#4000000 + ?EMPTY_FRAME_SIZE - 1)). 30 | 31 | -record(state, {sock, 32 | name, 33 | heartbeat, 34 | writer0, 35 | frame_max, 36 | type_sup, 37 | closing_reason, %% undefined | Reason 38 | waiting_socket_close = false}). 39 | 40 | -define(INFO_KEYS, [type, heartbeat, frame_max, sock, name]). 41 | 42 | %%--------------------------------------------------------------------------- 43 | 44 | init() -> 45 | {ok, #state{}}. 46 | 47 | open_channel_args(#state{sock = Sock, frame_max = FrameMax}) -> 48 | [Sock, FrameMax]. 49 | 50 | do(#'connection.close_ok'{} = CloseOk, State) -> 51 | erlang:send_after(?SOCKET_CLOSING_TIMEOUT, self(), socket_closing_timeout), 52 | do2(CloseOk, State); 53 | do(Method, State) -> 54 | do2(Method, State). 55 | 56 | do2(Method, #state{writer0 = Writer}) -> 57 | %% Catching because it expects the {channel_exit, _, _} message on error 58 | catch rabbit_writer:send_command_sync(Writer, Method). 59 | 60 | handle_message(socket_closing_timeout, 61 | State = #state{closing_reason = Reason}) -> 62 | {stop, {socket_closing_timeout, Reason}, State}; 63 | handle_message(socket_closed, State = #state{waiting_socket_close = true, 64 | closing_reason = Reason}) -> 65 | {stop, {shutdown, Reason}, State}; 66 | handle_message(socket_closed, State = #state{waiting_socket_close = false}) -> 67 | {stop, socket_closed_unexpectedly, State}; 68 | handle_message({socket_error, _} = SocketError, State) -> 69 | {stop, SocketError, State}; 70 | handle_message({channel_exit, 0, Reason}, State) -> 71 | {stop, {channel0_died, Reason}, State}; 72 | handle_message(heartbeat_timeout, State) -> 73 | {stop, heartbeat_timeout, State}; 74 | handle_message(closing_timeout, State = #state{closing_reason = Reason}) -> 75 | {stop, Reason, State}; 76 | %% see http://erlang.org/pipermail/erlang-bugs/2012-June/002933.html 77 | handle_message({Ref, {error, Reason}}, 78 | State = #state{waiting_socket_close = Waiting, 79 | closing_reason = CloseReason}) 80 | when is_reference(Ref) -> 81 | {stop, case {Reason, Waiting} of 82 | {closed, true} -> {shutdown, CloseReason}; 83 | {closed, false} -> socket_closed_unexpectedly; 84 | {_, _} -> {socket_error, Reason} 85 | end, State}. 86 | 87 | closing(_ChannelCloseType, Reason, State) -> 88 | {ok, State#state{closing_reason = Reason}}. 89 | 90 | channels_terminated(State = #state{closing_reason = 91 | {server_initiated_close, _, _}}) -> 92 | {ok, State#state{waiting_socket_close = true}}; 93 | channels_terminated(State) -> 94 | {ok, State}. 95 | 96 | terminate(_Reason, _State) -> 97 | ok. 98 | 99 | i(type, _State) -> network; 100 | i(heartbeat, State) -> State#state.heartbeat; 101 | i(frame_max, State) -> State#state.frame_max; 102 | i(sock, State) -> State#state.sock; 103 | i(name, State) -> State#state.name; 104 | i(Item, _State) -> throw({bad_argument, Item}). 105 | 106 | info_keys() -> 107 | ?INFO_KEYS. 108 | 109 | %%--------------------------------------------------------------------------- 110 | %% Handshake 111 | %%--------------------------------------------------------------------------- 112 | 113 | connect(AmqpParams = #amqp_params_network{host = Host}, SIF, TypeSup, State) -> 114 | case gethostaddr(Host) of 115 | [] -> {error, unknown_host}; 116 | [AF|_] -> do_connect( 117 | AF, AmqpParams, SIF, State#state{type_sup = TypeSup}) 118 | end. 119 | 120 | do_connect({Addr, Family}, 121 | AmqpParams = #amqp_params_network{ssl_options = none, 122 | port = Port, 123 | connection_timeout = Timeout, 124 | socket_options = ExtraOpts}, 125 | SIF, State) -> 126 | obtain(), 127 | case gen_tcp:connect(Addr, Port, 128 | [Family | ?RABBIT_TCP_OPTS] ++ ExtraOpts, 129 | Timeout) of 130 | {ok, Sock} -> try_handshake(AmqpParams, SIF, 131 | State#state{sock = Sock}); 132 | {error, _} = E -> E 133 | end; 134 | do_connect({Addr, Family}, 135 | AmqpParams = #amqp_params_network{ssl_options = SslOpts0, 136 | port = Port, 137 | connection_timeout = Timeout, 138 | socket_options = ExtraOpts}, 139 | SIF, State) -> 140 | {ok, GlobalSslOpts} = application:get_env(amqp_client, ssl_options), 141 | app_utils:start_applications([asn1, crypto, public_key, ssl]), 142 | obtain(), 143 | case gen_tcp:connect(Addr, Port, 144 | [Family | ?RABBIT_TCP_OPTS] ++ ExtraOpts, 145 | Timeout) of 146 | {ok, Sock} -> 147 | SslOpts = rabbit_networking:fix_ssl_options( 148 | orddict:to_list( 149 | orddict:merge(fun (_, _A, B) -> B end, 150 | orddict:from_list(GlobalSslOpts), 151 | orddict:from_list(SslOpts0)))), 152 | case ssl:connect(Sock, SslOpts) of 153 | {ok, SslSock} -> 154 | try_handshake(AmqpParams, SIF, 155 | State#state{sock = SslSock}); 156 | {error, _} = E -> 157 | E 158 | end; 159 | {error, _} = E -> 160 | E 161 | end. 162 | 163 | inet_address_preference() -> 164 | case application:get_env(amqp_client, prefer_ipv6) of 165 | {ok, true} -> [inet6, inet]; 166 | {ok, false} -> [inet, inet6] 167 | end. 168 | 169 | gethostaddr(Host) -> 170 | Lookups = [{Family, inet:getaddr(Host, Family)} 171 | || Family <- inet_address_preference()], 172 | [{IP, Family} || {Family, {ok, IP}} <- Lookups]. 173 | 174 | try_handshake(AmqpParams, SIF, State = #state{sock = Sock}) -> 175 | Name = case rabbit_net:connection_string(Sock, outbound) of 176 | {ok, Str} -> list_to_binary(Str); 177 | {error, _} -> <<"unknown">> 178 | end, 179 | try handshake(AmqpParams, SIF, 180 | State#state{name = <<"client ", Name/binary>>}) of 181 | Return -> Return 182 | catch exit:Reason -> {error, Reason} 183 | end. 184 | 185 | handshake(AmqpParams, SIF, State0 = #state{sock = Sock}) -> 186 | ok = rabbit_net:send(Sock, ?PROTOCOL_HEADER), 187 | network_handshake(AmqpParams, start_infrastructure(SIF, State0)). 188 | 189 | start_infrastructure(SIF, State = #state{sock = Sock, name = Name}) -> 190 | {ok, ChMgr, Writer} = SIF(Sock, Name), 191 | {ChMgr, State#state{writer0 = Writer}}. 192 | 193 | network_handshake(AmqpParams = #amqp_params_network{virtual_host = VHost}, 194 | {ChMgr, State0}) -> 195 | Start = #'connection.start'{server_properties = ServerProperties, 196 | mechanisms = Mechanisms} = 197 | handshake_recv('connection.start'), 198 | ok = check_version(Start), 199 | case login(AmqpParams, Mechanisms, State0) of 200 | {closing, #amqp_error{}, _Error} = Err -> 201 | do(#'connection.close_ok'{}, State0), 202 | Err; 203 | Tune -> 204 | {TuneOk, ChannelMax, State1} = tune(Tune, AmqpParams, State0), 205 | do2(TuneOk, State1), 206 | do2(#'connection.open'{virtual_host = VHost}, State1), 207 | Params = {ServerProperties, ChannelMax, ChMgr, State1}, 208 | case handshake_recv('connection.open_ok') of 209 | #'connection.open_ok'{} -> {ok, Params}; 210 | {closing, #amqp_error{} = AmqpError, Error} -> {closing, Params, 211 | AmqpError, Error} 212 | end 213 | end. 214 | 215 | check_version(#'connection.start'{version_major = ?PROTOCOL_VERSION_MAJOR, 216 | version_minor = ?PROTOCOL_VERSION_MINOR}) -> 217 | ok; 218 | check_version(#'connection.start'{version_major = 8, 219 | version_minor = 0}) -> 220 | exit({protocol_version_mismatch, 0, 8}); 221 | check_version(#'connection.start'{version_major = Major, 222 | version_minor = Minor}) -> 223 | exit({protocol_version_mismatch, Major, Minor}). 224 | 225 | tune(#'connection.tune'{channel_max = ServerChannelMax, 226 | frame_max = ServerFrameMax, 227 | heartbeat = ServerHeartbeat}, 228 | #amqp_params_network{channel_max = ClientChannelMax, 229 | frame_max = ClientFrameMax, 230 | heartbeat = ClientHeartbeat}, State) -> 231 | [ChannelMax, Heartbeat, FrameMax] = 232 | lists:zipwith(fun (Client, Server) when Client =:= 0; Server =:= 0 -> 233 | lists:max([Client, Server]); 234 | (Client, Server) -> 235 | lists:min([Client, Server]) 236 | end, 237 | [ClientChannelMax, ClientHeartbeat, ClientFrameMax], 238 | [ServerChannelMax, ServerHeartbeat, ServerFrameMax]), 239 | %% If we attempt to recv > 64Mb, inet_drv will return enomem, so 240 | %% we cap the max negotiated frame size accordingly. Note that 241 | %% since we receive the frame header separately, we can actually 242 | %% cope with frame sizes of 64M + ?EMPTY_FRAME_SIZE - 1. 243 | CappedFrameMax = case FrameMax of 244 | 0 -> ?TCP_MAX_PACKET_SIZE; 245 | _ -> lists:min([FrameMax, ?TCP_MAX_PACKET_SIZE]) 246 | end, 247 | NewState = State#state{heartbeat = Heartbeat, frame_max = CappedFrameMax}, 248 | start_heartbeat(NewState), 249 | {#'connection.tune_ok'{channel_max = ChannelMax, 250 | frame_max = CappedFrameMax, 251 | heartbeat = Heartbeat}, ChannelMax, NewState}. 252 | 253 | start_heartbeat(#state{sock = Sock, 254 | name = Name, 255 | heartbeat = Heartbeat, 256 | type_sup = Sup}) -> 257 | Frame = rabbit_binary_generator:build_heartbeat_frame(), 258 | SendFun = fun () -> catch rabbit_net:send(Sock, Frame) end, 259 | Connection = self(), 260 | ReceiveFun = fun () -> Connection ! heartbeat_timeout end, 261 | rabbit_heartbeat:start( 262 | Sup, Sock, Name, Heartbeat, SendFun, Heartbeat, ReceiveFun). 263 | 264 | login(Params = #amqp_params_network{auth_mechanisms = ClientMechanisms, 265 | client_properties = UserProps}, 266 | ServerMechanismsStr, State) -> 267 | ServerMechanisms = string:tokens(binary_to_list(ServerMechanismsStr), " "), 268 | case [{N, S, F} || F <- ClientMechanisms, 269 | {N, S} <- [F(none, Params, init)], 270 | lists:member(binary_to_list(N), ServerMechanisms)] of 271 | [{Name, MState0, Mech}|_] -> 272 | {Resp, MState1} = Mech(none, Params, MState0), 273 | StartOk = #'connection.start_ok'{ 274 | client_properties = client_properties(UserProps), 275 | mechanism = Name, 276 | response = Resp}, 277 | do2(StartOk, State), 278 | login_loop(Mech, MState1, Params, State); 279 | [] -> 280 | exit({no_suitable_auth_mechanism, ServerMechanisms}) 281 | end. 282 | 283 | login_loop(Mech, MState0, Params, State) -> 284 | case handshake_recv('connection.tune') of 285 | Tune = #'connection.tune'{} -> 286 | Tune; 287 | #'connection.secure'{challenge = Challenge} -> 288 | {Resp, MState1} = Mech(Challenge, Params, MState0), 289 | do2(#'connection.secure_ok'{response = Resp}, State), 290 | login_loop(Mech, MState1, Params, State); 291 | #'connection.close'{reply_code = ?ACCESS_REFUSED, 292 | reply_text = ExplanationBin} -> 293 | Explanation = binary_to_list(ExplanationBin), 294 | {closing, 295 | #amqp_error{name = access_refused, 296 | explanation = Explanation}, 297 | {error, {auth_failure, Explanation}}} 298 | end. 299 | 300 | client_properties(UserProperties) -> 301 | {ok, Vsn} = application:get_key(amqp_client, vsn), 302 | Default = [{<<"product">>, longstr, <<"RabbitMQ">>}, 303 | {<<"version">>, longstr, list_to_binary(Vsn)}, 304 | {<<"platform">>, longstr, <<"Erlang">>}, 305 | {<<"copyright">>, longstr, 306 | <<"Copyright (c) 2007-2016 Pivotal Software, Inc.">>}, 307 | {<<"information">>, longstr, 308 | <<"Licensed under the MPL. " 309 | "See http://www.rabbitmq.com/">>}, 310 | {<<"capabilities">>, table, ?CLIENT_CAPABILITIES}], 311 | lists:foldl(fun({K, _, _} = Tuple, Acc) -> 312 | lists:keystore(K, 1, Acc, Tuple) 313 | end, Default, UserProperties). 314 | 315 | handshake_recv(Expecting) -> 316 | receive 317 | {'$gen_cast', {method, Method, none, noflow}} -> 318 | case {Expecting, element(1, Method)} of 319 | {E, M} when E =:= M -> 320 | Method; 321 | {'connection.tune', 'connection.secure'} -> 322 | Method; 323 | {'connection.tune', 'connection.close'} -> 324 | Method; 325 | {'connection.open_ok', 'connection.close'} -> 326 | exit(get_reason(Method)); 327 | {'connection.open_ok', _} -> 328 | {closing, 329 | #amqp_error{name = command_invalid, 330 | explanation = "was expecting " 331 | "connection.open_ok"}, 332 | {error, {unexpected_method, Method, 333 | {expecting, Expecting}}}}; 334 | _ -> 335 | throw({unexpected_method, Method, 336 | {expecting, Expecting}}) 337 | end; 338 | socket_closed -> 339 | case Expecting of 340 | 'connection.tune' -> exit({auth_failure, "Disconnected"}); 341 | 'connection.open_ok' -> exit(access_refused); 342 | _ -> exit({socket_closed_unexpectedly, 343 | Expecting}) 344 | end; 345 | {socket_error, _} = SocketError -> 346 | exit({SocketError, {expecting, Expecting}}); 347 | {refused, Version} -> 348 | exit({server_refused_connection, Version}); 349 | {malformed_header, All} -> 350 | exit({server_sent_malformed_header, All}); 351 | heartbeat_timeout -> 352 | exit(heartbeat_timeout); 353 | Other -> 354 | throw({handshake_recv_unexpected_message, Other}) 355 | after ?HANDSHAKE_RECEIVE_TIMEOUT -> 356 | case Expecting of 357 | 'connection.open_ok' -> 358 | {closing, 359 | #amqp_error{name = internal_error, 360 | explanation = "handshake timed out waiting " 361 | "connection.open_ok"}, 362 | {error, handshake_receive_timed_out}}; 363 | _ -> 364 | exit(handshake_receive_timed_out) 365 | end 366 | end. 367 | 368 | obtain() -> 369 | case code:is_loaded(file_handle_cache) of 370 | false -> ok; 371 | _ -> file_handle_cache:obtain() 372 | end. 373 | 374 | get_reason(#'connection.close'{reply_code = ErrCode}) -> 375 | ?PROTOCOL:amqp_exception(ErrCode). 376 | -------------------------------------------------------------------------------- /src/amqp_connection.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. 15 | %% 16 | 17 | %% @type close_reason(Type) = {shutdown, amqp_reason(Type)}. 18 | %% @type amqp_reason(Type) = {Type, Code, Text} 19 | %% Code = non_neg_integer() 20 | %% Text = binary(). 21 | %% @doc This module is responsible for maintaining a connection to an AMQP 22 | %% broker and manages channels within the connection. This module is used to 23 | %% open and close connections to the broker as well as creating new channels 24 | %% within a connection.
25 | %% The connections and channels created by this module are supervised under 26 | %% amqp_client's supervision tree. Please note that connections and channels 27 | %% do not get restarted automatically by the supervision tree in the case of a 28 | %% failure. If you need robust connections and channels, we recommend you use 29 | %% Erlang monitors on the returned connection and channel PIDs.
30 | %%
31 | %% In case of a failure or an AMQP error, the connection process exits with a 32 | %% meaningful exit reason:
33 | %%
34 | %% 35 | %% 36 | %% 37 | %% 38 | %% 39 | %% 40 | %% 41 | %% 42 | %% 43 | %% 44 | %% 45 | %% 46 | %% 47 | %% 48 | %% 49 | %% 50 | %% 51 | %% 52 | %% 53 | %% 54 | %% 55 | %% 56 | %% 59 | %% 60 | %% 61 | %% 62 | %% 63 | %% 64 | %% 65 | %%
CauseExit reason
Any reason, where Code would have been 200 otherwise```normal'''
User application calls amqp_connection:close/3```close_reason(app_initiated_close)'''
Server closes connection (hard error)```close_reason(server_initiated_close)'''
Server misbehaved (did not follow protocol)```close_reason(server_misbehaved)'''
AMQP client internal error - usually caused by a channel exiting 57 | %% with an unusual reason. This is usually accompanied by a more 58 | %% detailed error log from the channel```close_reason(internal_error)'''
Other error(various error reasons, causing more detailed logging)
66 | %%
67 | %% See type definitions below. 68 | -module(amqp_connection). 69 | 70 | -include("amqp_client_internal.hrl"). 71 | 72 | -export([open_channel/1, open_channel/2, open_channel/3, register_blocked_handler/2]). 73 | -export([start/1, start/2, close/1, close/2, close/3]). 74 | -export([error_atom/1]). 75 | -export([info/2, info_keys/1, info_keys/0]). 76 | -export([connection_name/1]). 77 | -export([socket_adapter_info/2]). 78 | 79 | -define(DEFAULT_CONSUMER, {amqp_selective_consumer, []}). 80 | 81 | -define(PROTOCOL_SSL_PORT, (?PROTOCOL_PORT - 1)). 82 | 83 | %%--------------------------------------------------------------------------- 84 | %% Type Definitions 85 | %%--------------------------------------------------------------------------- 86 | 87 | %% @type amqp_adapter_info() = #amqp_adapter_info{}. 88 | %% @type amqp_params_direct() = #amqp_params_direct{}. 89 | %% As defined in amqp_client.hrl. It contains the following fields: 90 | %%
    91 | %%
  • username :: binary() - The name of a user registered with the broker, 92 | %% defaults to <<guest">>
  • 93 | %%
  • password :: binary() - The password of user, defaults to 'none'
  • 94 | %%
  • virtual_host :: binary() - The name of a virtual host in the broker, 95 | %% defaults to <<"/">>
  • 96 | %%
  • node :: atom() - The node the broker runs on (direct only)
  • 97 | %%
  • adapter_info :: amqp_adapter_info() - Extra management information for if 98 | %% this connection represents a non-AMQP network connection.
  • 99 | %%
  • client_properties :: [{binary(), atom(), binary()}] - A list of extra 100 | %% client properties to be sent to the server, defaults to []
  • 101 | %%
102 | %% 103 | %% @type amqp_params_network() = #amqp_params_network{}. 104 | %% As defined in amqp_client.hrl. It contains the following fields: 105 | %%
    106 | %%
  • username :: binary() - The name of a user registered with the broker, 107 | %% defaults to <<guest">>
  • 108 | %%
  • password :: binary() - The user's password, defaults to 109 | %% <<"guest">>
  • 110 | %%
  • virtual_host :: binary() - The name of a virtual host in the broker, 111 | %% defaults to <<"/">>
  • 112 | %%
  • host :: string() - The hostname of the broker, 113 | %% defaults to "localhost" (network only)
  • 114 | %%
  • port :: integer() - The port the broker is listening on, 115 | %% defaults to 5672 (network only)
  • 116 | %%
  • channel_max :: non_neg_integer() - The channel_max handshake parameter, 117 | %% defaults to 0
  • 118 | %%
  • frame_max :: non_neg_integer() - The frame_max handshake parameter, 119 | %% defaults to 0 (network only)
  • 120 | %%
  • heartbeat :: non_neg_integer() - The hearbeat interval in seconds, 121 | %% defaults to 0 (turned off) (network only)
  • 122 | %%
  • connection_timeout :: non_neg_integer() | 'infinity' 123 | %% - The connection timeout in milliseconds, 124 | %% defaults to 'infinity' (network only)
  • 125 | %%
  • ssl_options :: term() - The second parameter to be used with the 126 | %% ssl:connect/2 function, defaults to 'none' (network only)
  • 127 | %%
  • client_properties :: [{binary(), atom(), binary()}] - A list of extra 128 | %% client properties to be sent to the server, defaults to []
  • 129 | %%
  • socket_options :: [any()] - Extra socket options. These are 130 | %% appended to the default options. See 131 | %% inet:setopts/2 132 | %% and 133 | %% gen_tcp:connect/4 for descriptions of the available options.
  • 134 | %%
135 | 136 | 137 | %%--------------------------------------------------------------------------- 138 | %% Starting a connection 139 | %%--------------------------------------------------------------------------- 140 | 141 | %% @spec (Params) -> {ok, Connection} | {error, Error} 142 | %% where 143 | %% Params = amqp_params_network() | amqp_params_direct() 144 | %% Connection = pid() 145 | %% @doc same as {@link amqp_connection:start/2. start(Params, undefined)} 146 | start(AmqpParams) -> 147 | start(AmqpParams, undefined). 148 | 149 | %% @spec (Params, ConnectionName) -> {ok, Connection} | {error, Error} 150 | %% where 151 | %% Params = amqp_params_network() | amqp_params_direct() 152 | %% ConnectionName = undefined | binary() 153 | %% Connection = pid() 154 | %% @doc Starts a connection to an AMQP server. Use network params to 155 | %% connect to a remote AMQP server or direct params for a direct 156 | %% connection to a RabbitMQ server, assuming that the server is 157 | %% running in the same process space. If the port is set to 'undefined', 158 | %% the default ports will be selected depending on whether this is a 159 | %% normal or an SSL connection. 160 | %% If ConnectionName is binary - it will be added to client_properties as 161 | %% user specified connection name. 162 | start(AmqpParams, ConnName) when ConnName == undefined; is_binary(ConnName) -> 163 | ensure_started(), 164 | AmqpParams1 = 165 | case AmqpParams of 166 | #amqp_params_network{port = undefined, ssl_options = none} -> 167 | AmqpParams#amqp_params_network{port = ?PROTOCOL_PORT}; 168 | #amqp_params_network{port = undefined, ssl_options = _} -> 169 | AmqpParams#amqp_params_network{port = ?PROTOCOL_SSL_PORT}; 170 | _ -> 171 | AmqpParams 172 | end, 173 | AmqpParams2 = set_connection_name(ConnName, AmqpParams1), 174 | {ok, _Sup, Connection} = amqp_sup:start_connection_sup(AmqpParams2), 175 | amqp_gen_connection:connect(Connection). 176 | 177 | set_connection_name(undefined, Params) -> Params; 178 | set_connection_name(ConnName, 179 | #amqp_params_network{client_properties = Props} = Params) -> 180 | Params#amqp_params_network{ 181 | client_properties = [ 182 | {<<"connection_name">>, longstr, ConnName} | Props 183 | ]}; 184 | set_connection_name(ConnName, 185 | #amqp_params_direct{client_properties = Props} = Params) -> 186 | Params#amqp_params_direct{ 187 | client_properties = [ 188 | {<<"connection_name">>, longstr, ConnName} | Props 189 | ]}. 190 | 191 | %% Usually the amqp_client application will already be running. We 192 | %% check whether that is the case by invoking an undocumented function 193 | %% which does not require a synchronous call to the application 194 | %% controller. That way we don't risk a dead-lock if, say, the 195 | %% application controller is in the process of shutting down the very 196 | %% application which is making this call. 197 | ensure_started() -> 198 | [ensure_started(App) || App <- [xmerl, rabbit_common, amqp_client]]. 199 | 200 | ensure_started(App) -> 201 | case is_pid(application_controller:get_master(App)) andalso amqp_sup:is_ready() of 202 | true -> ok; 203 | false -> case application:start(App) of 204 | ok -> ok; 205 | {error, {already_started, App}} -> ok; 206 | {error, _} = E -> throw(E) 207 | end 208 | end. 209 | 210 | %%--------------------------------------------------------------------------- 211 | %% Commands 212 | %%--------------------------------------------------------------------------- 213 | 214 | %% @doc Invokes open_channel(ConnectionPid, none, 215 | %% {amqp_selective_consumer, []}). Opens a channel without having to 216 | %% specify a channel number. This uses the default consumer 217 | %% implementation. 218 | open_channel(ConnectionPid) -> 219 | open_channel(ConnectionPid, none, ?DEFAULT_CONSUMER). 220 | 221 | %% @doc Invokes open_channel(ConnectionPid, none, Consumer). 222 | %% Opens a channel without having to specify a channel number. 223 | open_channel(ConnectionPid, {_, _} = Consumer) -> 224 | open_channel(ConnectionPid, none, Consumer); 225 | 226 | %% @doc Invokes open_channel(ConnectionPid, ChannelNumber, 227 | %% {amqp_selective_consumer, []}). Opens a channel, using the default 228 | %% consumer implementation. 229 | open_channel(ConnectionPid, ChannelNumber) 230 | when is_number(ChannelNumber) orelse ChannelNumber =:= none -> 231 | open_channel(ConnectionPid, ChannelNumber, ?DEFAULT_CONSUMER). 232 | 233 | %% @spec (ConnectionPid, ChannelNumber, Consumer) -> Result 234 | %% where 235 | %% ConnectionPid = pid() 236 | %% ChannelNumber = pos_integer() | 'none' 237 | %% Consumer = {ConsumerModule, ConsumerArgs} 238 | %% ConsumerModule = atom() 239 | %% ConsumerArgs = [any()] 240 | %% Result = {ok, ChannelPid} | {error, Error} 241 | %% ChannelPid = pid() 242 | %% @doc Opens an AMQP channel.
243 | %% Opens a channel, using a proposed channel number and a specific consumer 244 | %% implementation.
245 | %% ConsumerModule must implement the amqp_gen_consumer behaviour. ConsumerArgs 246 | %% is passed as parameter to ConsumerModule:init/1.
247 | %% This function assumes that an AMQP connection (networked or direct) 248 | %% has already been successfully established.
249 | %% ChannelNumber must be less than or equal to the negotiated 250 | %% max_channel value, or less than or equal to ?MAX_CHANNEL_NUMBER 251 | %% (65535) if the negotiated max_channel value is 0.
252 | %% In the direct connection, max_channel is always 0. 253 | open_channel(ConnectionPid, ChannelNumber, 254 | {_ConsumerModule, _ConsumerArgs} = Consumer) -> 255 | amqp_gen_connection:open_channel(ConnectionPid, ChannelNumber, Consumer). 256 | 257 | %% @spec (ConnectionPid) -> ok | Error 258 | %% where 259 | %% ConnectionPid = pid() 260 | %% @doc Closes the channel, invokes 261 | %% close(Channel, 200, <<"Goodbye">>). 262 | close(ConnectionPid) -> 263 | close(ConnectionPid, 200, <<"Goodbye">>). 264 | 265 | %% @spec (ConnectionPid, Timeout) -> ok | Error 266 | %% where 267 | %% ConnectionPid = pid() 268 | %% Timeout = integer() 269 | %% @doc Closes the channel, using the supplied Timeout value. 270 | close(ConnectionPid, Timeout) -> 271 | close(ConnectionPid, 200, <<"Goodbye">>, Timeout). 272 | 273 | %% @spec (ConnectionPid, Code, Text) -> ok | closing 274 | %% where 275 | %% ConnectionPid = pid() 276 | %% Code = integer() 277 | %% Text = binary() 278 | %% @doc Closes the AMQP connection, allowing the caller to set the reply 279 | %% code and text. 280 | close(ConnectionPid, Code, Text) -> 281 | close(ConnectionPid, Code, Text, infinity). 282 | 283 | %% @spec (ConnectionPid, Code, Text, Timeout) -> ok | closing 284 | %% where 285 | %% ConnectionPid = pid() 286 | %% Code = integer() 287 | %% Text = binary() 288 | %% Timeout = integer() 289 | %% @doc Closes the AMQP connection, allowing the caller to set the reply 290 | %% code and text, as well as a timeout for the operation, after which the 291 | %% connection will be abruptly terminated. 292 | close(ConnectionPid, Code, Text, Timeout) -> 293 | Close = #'connection.close'{reply_text = Text, 294 | reply_code = Code, 295 | class_id = 0, 296 | method_id = 0}, 297 | amqp_gen_connection:close(ConnectionPid, Close, Timeout). 298 | 299 | register_blocked_handler(ConnectionPid, BlockHandler) -> 300 | amqp_gen_connection:register_blocked_handler(ConnectionPid, BlockHandler). 301 | 302 | %%--------------------------------------------------------------------------- 303 | %% Other functions 304 | %%--------------------------------------------------------------------------- 305 | 306 | %% @spec (Code) -> atom() 307 | %% where 308 | %% Code = integer() 309 | %% @doc Returns a descriptive atom corresponding to the given AMQP 310 | %% error code. 311 | error_atom(Code) -> ?PROTOCOL:amqp_exception(Code). 312 | 313 | %% @spec (ConnectionPid, Items) -> ResultList 314 | %% where 315 | %% ConnectionPid = pid() 316 | %% Items = [Item] 317 | %% ResultList = [{Item, Result}] 318 | %% Item = atom() 319 | %% Result = term() 320 | %% @doc Returns information about the connection, as specified by the Items 321 | %% list. Item may be any atom returned by info_keys/1: 322 | %%
    323 | %%
  • type - returns the type of the connection (network or direct)
  • 324 | %%
  • server_properties - returns the server_properties fields sent by the 325 | %% server while establishing the connection
  • 326 | %%
  • is_closing - returns true if the connection is in the process of closing 327 | %% and false otherwise
  • 328 | %%
  • amqp_params - returns the #amqp_params{} structure used to start the 329 | %% connection
  • 330 | %%
  • num_channels - returns the number of channels currently open under the 331 | %% connection (excluding channel 0)
  • 332 | %%
  • channel_max - returns the channel_max value negotiated with the 333 | %% server
  • 334 | %%
  • heartbeat - returns the heartbeat value negotiated with the server 335 | %% (only for the network connection)
  • 336 | %%
  • frame_max - returns the frame_max value negotiated with the 337 | %% server (only for the network connection)
  • 338 | %%
  • sock - returns the socket for the network connection (for use with 339 | %% e.g. inet:sockname/1) (only for the network connection)
  • 340 | %%
  • any other value - throws an exception
  • 341 | %%
342 | info(ConnectionPid, Items) -> 343 | amqp_gen_connection:info(ConnectionPid, Items). 344 | 345 | %% @spec (ConnectionPid) -> Items 346 | %% where 347 | %% ConnectionPid = pid() 348 | %% Items = [Item] 349 | %% Item = atom() 350 | %% @doc Returns a list of atoms that can be used in conjunction with info/2. 351 | %% Note that the list differs from a type of connection to another (network vs. 352 | %% direct). Use info_keys/0 to get a list of info keys that can be used for 353 | %% any connection. 354 | info_keys(ConnectionPid) -> 355 | amqp_gen_connection:info_keys(ConnectionPid). 356 | 357 | %% @spec () -> Items 358 | %% where 359 | %% Items = [Item] 360 | %% Item = atom() 361 | %% @doc Returns a list of atoms that can be used in conjunction with info/2. 362 | %% These are general info keys, which can be used in any type of connection. 363 | %% Other info keys may exist for a specific type. To get the full list of 364 | %% atoms that can be used for a certain connection, use info_keys/1. 365 | info_keys() -> 366 | amqp_gen_connection:info_keys(). 367 | 368 | %% @doc Takes a socket and a protocol, returns an #amqp_adapter_info{} 369 | %% based on the socket for the protocol given. 370 | socket_adapter_info(Sock, Protocol) -> 371 | amqp_direct_connection:socket_adapter_info(Sock, Protocol). 372 | 373 | %% @spec (ConnectionPid) -> ConnectionName 374 | %% where 375 | %% ConnectionPid = pid() 376 | %% ConnectionName = binary() 377 | %% @doc Returns user specified connection name from client properties 378 | connection_name(ConnectionPid) -> 379 | ClientProperties = case info(ConnectionPid, [amqp_params]) of 380 | [{_, #amqp_params_network{client_properties = Props}}] -> Props; 381 | [{_, #amqp_params_direct{client_properties = Props}}] -> Props 382 | end, 383 | case lists:keyfind(<<"connection_name">>, 1, ClientProperties) of 384 | {<<"connection_name">>, _, ConnName} -> ConnName; 385 | false -> undefined 386 | end. 387 | -------------------------------------------------------------------------------- /erlang.mk: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2014, Loïc Hoguin 2 | # 3 | # Permission to use, copy, modify, and/or distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | .PHONY: all deps app rel docs tests clean distclean help 16 | 17 | ERLANG_MK_VERSION = 1 18 | 19 | # Core configuration. 20 | 21 | PROJECT ?= $(notdir $(CURDIR)) 22 | PROJECT := $(strip $(PROJECT)) 23 | 24 | # Verbosity. 25 | 26 | V ?= 0 27 | 28 | gen_verbose_0 = @echo " GEN " $@; 29 | gen_verbose = $(gen_verbose_$(V)) 30 | 31 | # Core targets. 32 | 33 | all:: deps app rel 34 | 35 | clean:: 36 | $(gen_verbose) rm -f erl_crash.dump 37 | 38 | distclean:: clean 39 | 40 | help:: 41 | @printf "%s\n" \ 42 | "erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \ 43 | "Copyright (c) 2013-2014 Loïc Hoguin " \ 44 | "" \ 45 | "Usage: [V=1] make [target]" \ 46 | "" \ 47 | "Core targets:" \ 48 | " all Run deps, app and rel targets in that order" \ 49 | " deps Fetch dependencies (if needed) and compile them" \ 50 | " app Compile the project" \ 51 | " rel Build a release for this project, if applicable" \ 52 | " docs Build the documentation for this project" \ 53 | " tests Run the tests for this project" \ 54 | " clean Delete temporary and output files from most targets" \ 55 | " distclean Delete all temporary and output files" \ 56 | " help Display this help and exit" \ 57 | "" \ 58 | "The target clean only removes files that are commonly removed." \ 59 | "Dependencies and releases are left untouched." \ 60 | "" \ 61 | "Setting V=1 when calling make enables verbose mode." 62 | 63 | # Core functions. 64 | 65 | ifeq ($(shell which wget 2>/dev/null | wc -l), 1) 66 | define core_http_get 67 | wget --no-check-certificate -O $(1) $(2)|| rm $(1) 68 | endef 69 | else 70 | define core_http_get 71 | erl -noshell -eval 'ssl:start(), inets:start(), case httpc:request(get, {"$(2)", []}, [{autoredirect, true}], []) of {ok, {{_, 200, _}, _, Body}} -> case file:write_file("$(1)", Body) of ok -> ok; {error, R1} -> halt(R1) end; {error, R2} -> halt(R2) end, halt(0).' 72 | endef 73 | endif 74 | 75 | # Copyright (c) 2013-2014, Loïc Hoguin 76 | # This file is part of erlang.mk and subject to the terms of the ISC License. 77 | 78 | .PHONY: distclean-deps distclean-pkg pkg-list pkg-search 79 | 80 | # Configuration. 81 | 82 | DEPS_DIR ?= $(CURDIR)/deps 83 | export DEPS_DIR 84 | 85 | REBAR_DEPS_DIR = $(DEPS_DIR) 86 | export REBAR_DEPS_DIR 87 | 88 | ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DEPS)) 89 | 90 | ifeq ($(filter $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),) 91 | ifeq ($(ERL_LIBS),) 92 | ERL_LIBS = $(DEPS_DIR) 93 | else 94 | ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR) 95 | endif 96 | endif 97 | export ERL_LIBS 98 | 99 | PKG_FILE2 ?= $(CURDIR)/.erlang.mk.packages.v2 100 | export PKG_FILE2 101 | 102 | PKG_FILE_URL ?= https://raw.githubusercontent.com/ninenines/erlang.mk/master/packages.v2.tsv 103 | 104 | # Core targets. 105 | 106 | deps:: $(ALL_DEPS_DIRS) 107 | @for dep in $(ALL_DEPS_DIRS) ; do \ 108 | if [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ] ; then \ 109 | $(MAKE) -C $$dep ; \ 110 | else \ 111 | echo "include $(CURDIR)/erlang.mk" | $(MAKE) -f - -C $$dep ; \ 112 | fi ; \ 113 | done 114 | 115 | distclean:: distclean-deps distclean-pkg 116 | 117 | # Deps related targets. 118 | 119 | define dep_fetch 120 | if [ "$$$$VS" = "git" ]; then \ 121 | git clone -n -- $$$$REPO $(DEPS_DIR)/$(1); \ 122 | cd $(DEPS_DIR)/$(1) && git checkout -q $$$$COMMIT; \ 123 | elif [ "$$$$VS" = "hg" ]; then \ 124 | hg clone -U $$$$REPO $(DEPS_DIR)/$(1); \ 125 | cd $(DEPS_DIR)/$(1) && hg update -q $$$$COMMIT; \ 126 | else \ 127 | echo "Unknown or invalid dependency: $(1). Please consult the erlang.mk README for instructions." >&2; \ 128 | exit 78; \ 129 | fi 130 | endef 131 | 132 | define dep_target 133 | $(DEPS_DIR)/$(1): 134 | @mkdir -p $(DEPS_DIR) 135 | @if [ ! -f $(PKG_FILE2) ]; then $(call core_http_get,$(PKG_FILE2),$(PKG_FILE_URL)); fi 136 | ifeq (,$(dep_$(1))) 137 | @DEPPKG=$$$$(awk 'BEGIN { FS = "\t" }; $$$$1 == "$(1)" { print $$$$2 " " $$$$3 " " $$$$4 }' $(PKG_FILE2);); \ 138 | VS=$$$$(echo $$$$DEPPKG | cut -d " " -f1); \ 139 | REPO=$$$$(echo $$$$DEPPKG | cut -d " " -f2); \ 140 | COMMIT=$$$$(echo $$$$DEPPKG | cut -d " " -f3); \ 141 | $(call dep_fetch,$(1)) 142 | else 143 | @VS=$(word 1,$(dep_$(1))); \ 144 | REPO=$(word 2,$(dep_$(1))); \ 145 | COMMIT=$(word 3,$(dep_$(1))); \ 146 | $(call dep_fetch,$(1)) 147 | endif 148 | endef 149 | 150 | $(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep)))) 151 | 152 | distclean-deps: 153 | $(gen_verbose) rm -rf $(DEPS_DIR) 154 | 155 | # Packages related targets. 156 | 157 | $(PKG_FILE2): 158 | @$(call core_http_get,$(PKG_FILE2),$(PKG_FILE_URL)) 159 | 160 | pkg-list: $(PKG_FILE2) 161 | @cat $(PKG_FILE2) | awk 'BEGIN { FS = "\t" }; { print \ 162 | "Name:\t\t" $$1 "\n" \ 163 | "Repository:\t" $$3 "\n" \ 164 | "Website:\t" $$5 "\n" \ 165 | "Description:\t" $$6 "\n" }' 166 | 167 | ifdef q 168 | pkg-search: $(PKG_FILE2) 169 | @cat $(PKG_FILE2) | grep -i ${q} | awk 'BEGIN { FS = "\t" }; { print \ 170 | "Name:\t\t" $$1 "\n" \ 171 | "Repository:\t" $$3 "\n" \ 172 | "Website:\t" $$5 "\n" \ 173 | "Description:\t" $$6 "\n" }' 174 | else 175 | pkg-search: 176 | $(error Usage: make pkg-search q=STRING) 177 | endif 178 | 179 | distclean-pkg: 180 | $(gen_verbose) rm -f $(PKG_FILE2) 181 | 182 | help:: 183 | @printf "%s\n" "" \ 184 | "Package-related targets:" \ 185 | " pkg-list List all known packages" \ 186 | " pkg-search q=STRING Search for STRING in the package index" 187 | 188 | # Copyright (c) 2013-2014, Loïc Hoguin 189 | # This file is part of erlang.mk and subject to the terms of the ISC License. 190 | 191 | .PHONY: clean-app 192 | 193 | # Configuration. 194 | 195 | ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \ 196 | +warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec 197 | COMPILE_FIRST ?= 198 | COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) 199 | 200 | # Verbosity. 201 | 202 | appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src; 203 | appsrc_verbose = $(appsrc_verbose_$(V)) 204 | 205 | erlc_verbose_0 = @echo " ERLC " $(filter %.erl %.core,$(?F)); 206 | erlc_verbose = $(erlc_verbose_$(V)) 207 | 208 | xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F)); 209 | xyrl_verbose = $(xyrl_verbose_$(V)) 210 | 211 | # Core targets. 212 | 213 | app:: erlc-include ebin/$(PROJECT).app 214 | $(eval MODULES := $(shell find ebin -type f -name \*.beam \ 215 | | sed "s/ebin\//'/;s/\.beam/',/" | sed '$$s/.$$//')) 216 | @if [ -z "$$(grep -E '^[^%]*{modules,' src/$(PROJECT).app.src)" ]; then \ 217 | echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk README for instructions." >&2; \ 218 | exit 1; \ 219 | fi 220 | $(appsrc_verbose) cat src/$(PROJECT).app.src \ 221 | | sed "s/{modules,[[:space:]]*\[\]}/{modules, \[$(MODULES)\]}/" \ 222 | > ebin/$(PROJECT).app 223 | 224 | define compile_erl 225 | $(erlc_verbose) erlc -v $(ERLC_OPTS) -o ebin/ \ 226 | -pa ebin/ -I include/ $(COMPILE_FIRST_PATHS) $(1) 227 | endef 228 | 229 | define compile_xyrl 230 | $(xyrl_verbose) erlc -v -o ebin/ $(1) 231 | $(xyrl_verbose) erlc $(ERLC_OPTS) -o ebin/ ebin/*.erl 232 | @rm ebin/*.erl 233 | endef 234 | 235 | ifneq ($(wildcard src/),) 236 | ebin/$(PROJECT).app:: 237 | @mkdir -p ebin/ 238 | 239 | ebin/$(PROJECT).app:: $(shell find src -type f -name \*.erl) \ 240 | $(shell find src -type f -name \*.core) 241 | $(if $(strip $?),$(call compile_erl,$?)) 242 | 243 | ebin/$(PROJECT).app:: $(shell find src -type f -name \*.xrl) \ 244 | $(shell find src -type f -name \*.yrl) 245 | $(if $(strip $?),$(call compile_xyrl,$?)) 246 | endif 247 | 248 | clean:: clean-app 249 | 250 | # Extra targets. 251 | 252 | erlc-include: 253 | -@if [ -d ebin/ ]; then \ 254 | find include/ src/ -type f -name \*.hrl -newer ebin -exec touch $(shell find src/ -type f -name "*.erl") \; 2>/dev/null || printf ''; \ 255 | fi 256 | 257 | clean-app: 258 | $(gen_verbose) rm -rf ebin/ 259 | 260 | # Copyright (c) 2014, Loïc Hoguin 261 | # This file is part of erlang.mk and subject to the terms of the ISC License. 262 | 263 | .PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates 264 | 265 | # Core targets. 266 | 267 | help:: 268 | @printf "%s\n" "" \ 269 | "Bootstrap targets:" \ 270 | " bootstrap Generate a skeleton of an OTP application" \ 271 | " bootstrap-lib Generate a skeleton of an OTP library" \ 272 | " bootstrap-rel Generate the files needed to build a release" \ 273 | " new t=TPL n=NAME Generate a module NAME based on the template TPL" \ 274 | " list-templates List available templates" 275 | 276 | # Bootstrap templates. 277 | 278 | bs_appsrc = "{application, $(PROJECT), [" \ 279 | " {description, \"\"}," \ 280 | " {vsn, \"0.1.0\"}," \ 281 | " {modules, []}," \ 282 | " {registered, []}," \ 283 | " {applications, [" \ 284 | " kernel," \ 285 | " stdlib" \ 286 | " ]}," \ 287 | " {mod, {$(PROJECT)_app, []}}," \ 288 | " {env, []}" \ 289 | "]}." 290 | bs_appsrc_lib = "{application, $(PROJECT), [" \ 291 | " {description, \"\"}," \ 292 | " {vsn, \"0.1.0\"}," \ 293 | " {modules, []}," \ 294 | " {registered, []}," \ 295 | " {applications, [" \ 296 | " kernel," \ 297 | " stdlib" \ 298 | " ]}" \ 299 | "]}." 300 | bs_Makefile = "PROJECT = $(PROJECT)" \ 301 | "include erlang.mk" 302 | bs_app = "-module($(PROJECT)_app)." \ 303 | "-behaviour(application)." \ 304 | "" \ 305 | "-export([start/2])." \ 306 | "-export([stop/1])." \ 307 | "" \ 308 | "start(_Type, _Args) ->" \ 309 | " $(PROJECT)_sup:start_link()." \ 310 | "" \ 311 | "stop(_State) ->" \ 312 | " ok." 313 | bs_relx_config = "{release, {$(PROJECT)_release, \"1\"}, [$(PROJECT)]}." \ 314 | "{extended_start_script, true}." \ 315 | "{sys_config, \"rel/sys.config\"}." \ 316 | "{vm_args, \"rel/vm.args\"}." 317 | bs_sys_config = "[" \ 318 | "]." 319 | bs_vm_args = "-name $(PROJECT)@127.0.0.1" \ 320 | "-setcookie $(PROJECT)" \ 321 | "-heart" 322 | # Normal templates. 323 | tpl_supervisor = "-module($(n))." \ 324 | "-behaviour(supervisor)." \ 325 | "" \ 326 | "-export([start_link/0])." \ 327 | "-export([init/1])." \ 328 | "" \ 329 | "start_link() ->" \ 330 | " supervisor:start_link({local, ?MODULE}, ?MODULE, [])." \ 331 | "" \ 332 | "init([]) ->" \ 333 | " Procs = []," \ 334 | " {ok, {{one_for_one, 1, 5}, Procs}}." 335 | tpl_gen_server = "-module($(n))." \ 336 | "-behaviour(gen_server)." \ 337 | "" \ 338 | "%% API." \ 339 | "-export([start_link/0])." \ 340 | "" \ 341 | "%% gen_server." \ 342 | "-export([init/1])." \ 343 | "-export([handle_call/3])." \ 344 | "-export([handle_cast/2])." \ 345 | "-export([handle_info/2])." \ 346 | "-export([terminate/2])." \ 347 | "-export([code_change/3])." \ 348 | "" \ 349 | "-record(state, {" \ 350 | "})." \ 351 | "" \ 352 | "%% API." \ 353 | "" \ 354 | "-spec start_link() -> {ok, pid()}." \ 355 | "start_link() ->" \ 356 | " gen_server:start_link(?MODULE, [], [])." \ 357 | "" \ 358 | "%% gen_server." \ 359 | "" \ 360 | "init([]) ->" \ 361 | " {ok, \#state{}}." \ 362 | "" \ 363 | "handle_call(_Request, _From, State) ->" \ 364 | " {reply, ignored, State}." \ 365 | "" \ 366 | "handle_cast(_Msg, State) ->" \ 367 | " {noreply, State}." \ 368 | "" \ 369 | "handle_info(_Info, State) ->" \ 370 | " {noreply, State}." \ 371 | "" \ 372 | "terminate(_Reason, _State) ->" \ 373 | " ok." \ 374 | "" \ 375 | "code_change(_OldVsn, State, _Extra) ->" \ 376 | " {ok, State}." 377 | tpl_cowboy_http = "-module($(n))." \ 378 | "-behaviour(cowboy_http_handler)." \ 379 | "" \ 380 | "-export([init/3])." \ 381 | "-export([handle/2])." \ 382 | "-export([terminate/3])." \ 383 | "" \ 384 | "-record(state, {" \ 385 | "})." \ 386 | "" \ 387 | "init(_, Req, _Opts) ->" \ 388 | " {ok, Req, \#state{}}." \ 389 | "" \ 390 | "handle(Req, State=\#state{}) ->" \ 391 | " {ok, Req2} = cowboy_req:reply(200, Req)," \ 392 | " {ok, Req2, State}." \ 393 | "" \ 394 | "terminate(_Reason, _Req, _State) ->" \ 395 | " ok." 396 | tpl_cowboy_loop = "-module($(n))." \ 397 | "-behaviour(cowboy_loop_handler)." \ 398 | "" \ 399 | "-export([init/3])." \ 400 | "-export([info/3])." \ 401 | "-export([terminate/3])." \ 402 | "" \ 403 | "-record(state, {" \ 404 | "})." \ 405 | "" \ 406 | "init(_, Req, _Opts) ->" \ 407 | " {loop, Req, \#state{}, 5000, hibernate}." \ 408 | "" \ 409 | "info(_Info, Req, State) ->" \ 410 | " {loop, Req, State, hibernate}." \ 411 | "" \ 412 | "terminate(_Reason, _Req, _State) ->" \ 413 | " ok." 414 | tpl_cowboy_rest = "-module($(n))." \ 415 | "" \ 416 | "-export([init/3])." \ 417 | "-export([content_types_provided/2])." \ 418 | "-export([get_html/2])." \ 419 | "" \ 420 | "init(_, _Req, _Opts) ->" \ 421 | " {upgrade, protocol, cowboy_rest}." \ 422 | "" \ 423 | "content_types_provided(Req, State) ->" \ 424 | " {[{{<<\"text\">>, <<\"html\">>, '*'}, get_html}], Req, State}." \ 425 | "" \ 426 | "get_html(Req, State) ->" \ 427 | " {<<\"This is REST!\">>, Req, State}." 428 | tpl_cowboy_ws = "-module($(n))." \ 429 | "-behaviour(cowboy_websocket_handler)." \ 430 | "" \ 431 | "-export([init/3])." \ 432 | "-export([websocket_init/3])." \ 433 | "-export([websocket_handle/3])." \ 434 | "-export([websocket_info/3])." \ 435 | "-export([websocket_terminate/3])." \ 436 | "" \ 437 | "-record(state, {" \ 438 | "})." \ 439 | "" \ 440 | "init(_, _, _) ->" \ 441 | " {upgrade, protocol, cowboy_websocket}." \ 442 | "" \ 443 | "websocket_init(_, Req, _Opts) ->" \ 444 | " Req2 = cowboy_req:compact(Req)," \ 445 | " {ok, Req2, \#state{}}." \ 446 | "" \ 447 | "websocket_handle({text, Data}, Req, State) ->" \ 448 | " {reply, {text, Data}, Req, State};" \ 449 | "websocket_handle({binary, Data}, Req, State) ->" \ 450 | " {reply, {binary, Data}, Req, State};" \ 451 | "websocket_handle(_Frame, Req, State) ->" \ 452 | " {ok, Req, State}." \ 453 | "" \ 454 | "websocket_info(_Info, Req, State) ->" \ 455 | " {ok, Req, State}." \ 456 | "" \ 457 | "websocket_terminate(_Reason, _Req, _State) ->" \ 458 | " ok." 459 | tpl_ranch_protocol = "-module($(n))." \ 460 | "-behaviour(ranch_protocol)." \ 461 | "" \ 462 | "-export([start_link/4])." \ 463 | "-export([init/4])." \ 464 | "" \ 465 | "-type opts() :: []." \ 466 | "-export_type([opts/0])." \ 467 | "" \ 468 | "-record(state, {" \ 469 | " socket :: inet:socket()," \ 470 | " transport :: module()" \ 471 | "})." \ 472 | "" \ 473 | "start_link(Ref, Socket, Transport, Opts) ->" \ 474 | " Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts])," \ 475 | " {ok, Pid}." \ 476 | "" \ 477 | "-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok." \ 478 | "init(Ref, Socket, Transport, _Opts) ->" \ 479 | " ok = ranch:accept_ack(Ref)," \ 480 | " loop(\#state{socket=Socket, transport=Transport})." \ 481 | "" \ 482 | "loop(State) ->" \ 483 | " loop(State)." 484 | 485 | # Plugin-specific targets. 486 | 487 | bootstrap: 488 | ifneq ($(wildcard src/),) 489 | $(error Error: src/ directory already exists) 490 | endif 491 | @printf "%s\n" $(bs_Makefile) > Makefile 492 | @mkdir src/ 493 | @printf "%s\n" $(bs_appsrc) > src/$(PROJECT).app.src 494 | @printf "%s\n" $(bs_app) > src/$(PROJECT)_app.erl 495 | $(eval n := $(PROJECT)_sup) 496 | @printf "%s\n" $(tpl_supervisor) > src/$(PROJECT)_sup.erl 497 | 498 | bootstrap-lib: 499 | ifneq ($(wildcard src/),) 500 | $(error Error: src/ directory already exists) 501 | endif 502 | @printf "%s\n" $(bs_Makefile) > Makefile 503 | @mkdir src/ 504 | @printf "%s\n" $(bs_appsrc_lib) > src/$(PROJECT).app.src 505 | 506 | bootstrap-rel: 507 | ifneq ($(wildcard relx.config),) 508 | $(error Error: relx.config already exists) 509 | endif 510 | ifneq ($(wildcard rel/),) 511 | $(error Error: rel/ directory already exists) 512 | endif 513 | @printf "%s\n" $(bs_relx_config) > relx.config 514 | @mkdir rel/ 515 | @printf "%s\n" $(bs_sys_config) > rel/sys.config 516 | @printf "%s\n" $(bs_vm_args) > rel/vm.args 517 | 518 | new: 519 | ifeq ($(wildcard src/),) 520 | $(error Error: src/ directory does not exist) 521 | endif 522 | ifndef t 523 | $(error Usage: make new t=TEMPLATE n=NAME) 524 | endif 525 | ifndef tpl_$(t) 526 | $(error Unknown template) 527 | endif 528 | ifndef n 529 | $(error Usage: make new t=TEMPLATE n=NAME) 530 | endif 531 | @printf "%s\n" $(tpl_$(t)) > src/$(n).erl 532 | 533 | list-templates: 534 | @echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES)))) 535 | 536 | # Copyright (c) 2013-2014, Loïc Hoguin 537 | # This file is part of erlang.mk and subject to the terms of the ISC License. 538 | 539 | .PHONY: build-ct-deps build-ct-suites tests-ct clean-ct distclean-ct 540 | 541 | # Configuration. 542 | 543 | CT_OPTS ?= 544 | ifneq ($(wildcard test/),) 545 | CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(shell find test -type f -name \*_SUITE.erl -exec basename {} \;))) 546 | else 547 | CT_SUITES ?= 548 | endif 549 | 550 | TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard 551 | TEST_ERLC_OPTS += -DTEST=1 -DEXTRA=1 +'{parse_transform, eunit_autoexport}' 552 | 553 | # Core targets. 554 | 555 | tests:: tests-ct 556 | 557 | clean:: clean-ct 558 | 559 | distclean:: distclean-ct 560 | 561 | help:: 562 | @printf "%s\n" "" \ 563 | "All your common_test suites have their associated targets." \ 564 | "A suite named http_SUITE can be ran using the ct-http target." 565 | 566 | # Plugin-specific targets. 567 | 568 | ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS)) 569 | 570 | CT_RUN = ct_run \ 571 | -no_auto_compile \ 572 | -noshell \ 573 | -pa $(realpath ebin) $(DEPS_DIR)/*/ebin \ 574 | -dir test \ 575 | -logdir logs 576 | 577 | $(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep)))) 578 | 579 | build-ct-deps: $(ALL_TEST_DEPS_DIRS) 580 | @for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done 581 | 582 | build-ct-suites: build-ct-deps 583 | $(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -o test/ \ 584 | $(wildcard test/*.erl test/*/*.erl) -pa ebin/ 585 | 586 | tests-ct: ERLC_OPTS = $(TEST_ERLC_OPTS) 587 | tests-ct: clean deps app build-ct-suites 588 | @if [ -d "test" ] ; \ 589 | then \ 590 | mkdir -p logs/ ; \ 591 | $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS) ; \ 592 | fi 593 | $(gen_verbose) rm -f test/*.beam 594 | 595 | define ct_suite_target 596 | ct-$(1): ERLC_OPTS = $(TEST_ERLC_OPTS) 597 | ct-$(1): clean deps app build-ct-suites 598 | @if [ -d "test" ] ; \ 599 | then \ 600 | mkdir -p logs/ ; \ 601 | $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) $(CT_OPTS) ; \ 602 | fi 603 | $(gen_verbose) rm -f test/*.beam 604 | endef 605 | 606 | $(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test)))) 607 | 608 | clean-ct: 609 | $(gen_verbose) rm -rf test/*.beam 610 | 611 | distclean-ct: 612 | $(gen_verbose) rm -rf logs/ 613 | 614 | # Copyright (c) 2013-2014, Loïc Hoguin 615 | # This file is part of erlang.mk and subject to the terms of the ISC License. 616 | 617 | .PHONY: plt distclean-plt dialyze 618 | 619 | # Configuration. 620 | 621 | DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt 622 | export DIALYZER_PLT 623 | 624 | PLT_APPS ?= 625 | DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \ 626 | -Wunmatched_returns # -Wunderspecs 627 | 628 | # Core targets. 629 | 630 | distclean:: distclean-plt 631 | 632 | help:: 633 | @printf "%s\n" "" \ 634 | "Dialyzer targets:" \ 635 | " plt Build a PLT file for this project" \ 636 | " dialyze Analyze the project using Dialyzer" 637 | 638 | # Plugin-specific targets. 639 | 640 | $(DIALYZER_PLT): deps app 641 | @dialyzer --build_plt --apps erts kernel stdlib $(PLT_APPS) $(ALL_DEPS_DIRS) 642 | 643 | plt: $(DIALYZER_PLT) 644 | 645 | distclean-plt: 646 | $(gen_verbose) rm -f $(DIALYZER_PLT) 647 | 648 | ifneq ($(wildcard $(DIALYZER_PLT)),) 649 | dialyze: 650 | else 651 | dialyze: $(DIALYZER_PLT) 652 | endif 653 | @dialyzer --no_native --src -r src $(DIALYZER_OPTS) 654 | 655 | # Copyright (c) 2013-2014, Loïc Hoguin 656 | # This file is part of erlang.mk and subject to the terms of the ISC License. 657 | 658 | # Verbosity. 659 | 660 | dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); 661 | dtl_verbose = $(dtl_verbose_$(V)) 662 | 663 | # Core targets. 664 | 665 | define compile_erlydtl 666 | $(dtl_verbose) erl -noshell -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \ 667 | Compile = fun(F) -> \ 668 | Module = list_to_atom( \ 669 | string:to_lower(filename:basename(F, ".dtl")) ++ "_dtl"), \ 670 | erlydtl:compile(F, Module, [{out_dir, "ebin/"}]) \ 671 | end, \ 672 | _ = [Compile(F) || F <- string:tokens("$(1)", " ")], \ 673 | init:stop()' 674 | endef 675 | 676 | ifneq ($(wildcard src/),) 677 | ebin/$(PROJECT).app:: $(shell find templates -type f -name \*.dtl 2>/dev/null) 678 | $(if $(strip $?),$(call compile_erlydtl,$?)) 679 | endif 680 | 681 | # Copyright (c) 2013-2014, Loïc Hoguin 682 | # This file is part of erlang.mk and subject to the terms of the ISC License. 683 | 684 | .PHONY: distclean-edoc 685 | 686 | # Configuration. 687 | 688 | EDOC_OPTS ?= 689 | 690 | # Core targets. 691 | 692 | docs:: distclean-edoc 693 | $(gen_verbose) erl -noshell \ 694 | -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), init:stop().' 695 | 696 | distclean:: distclean-edoc 697 | 698 | # Plugin-specific targets. 699 | 700 | distclean-edoc: 701 | $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info 702 | 703 | # Copyright (c) 2013-2014, Loïc Hoguin 704 | # This file is part of erlang.mk and subject to the terms of the ISC License. 705 | 706 | .PHONY: relx-rel distclean-relx-rel distclean-relx 707 | 708 | # Configuration. 709 | 710 | RELX_CONFIG ?= $(CURDIR)/relx.config 711 | 712 | RELX ?= $(CURDIR)/relx 713 | export RELX 714 | 715 | RELX_URL ?= https://github.com/erlware/relx/releases/download/v1.0.2/relx 716 | RELX_OPTS ?= 717 | RELX_OUTPUT_DIR ?= _rel 718 | 719 | ifeq ($(firstword $(RELX_OPTS)),-o) 720 | RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS)) 721 | else 722 | RELX_OPTS += -o $(RELX_OUTPUT_DIR) 723 | endif 724 | 725 | # Core targets. 726 | 727 | ifneq ($(wildcard $(RELX_CONFIG)),) 728 | rel:: distclean-relx-rel relx-rel 729 | endif 730 | 731 | distclean:: distclean-relx-rel distclean-relx 732 | 733 | # Plugin-specific targets. 734 | 735 | define relx_fetch 736 | $(call core_http_get,$(RELX),$(RELX_URL)) 737 | chmod +x $(RELX) 738 | endef 739 | 740 | $(RELX): 741 | @$(call relx_fetch) 742 | 743 | relx-rel: $(RELX) 744 | @$(RELX) -c $(RELX_CONFIG) $(RELX_OPTS) 745 | 746 | distclean-relx-rel: 747 | $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR) 748 | 749 | distclean-relx: 750 | $(gen_verbose) rm -rf $(RELX) 751 | 752 | # Copyright (c) 2014, M Robert Martin 753 | # This file is contributed to erlang.mk and subject to the terms of the ISC License. 754 | 755 | .PHONY: shell 756 | 757 | # Configuration. 758 | 759 | SHELL_PATH ?= -pa ../$(PROJECT)/ebin $(DEPS_DIR)/*/ebin 760 | SHELL_OPTS ?= 761 | 762 | ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS)) 763 | 764 | # Core targets 765 | 766 | help:: 767 | @printf "%s\n" "" \ 768 | "Shell targets:" \ 769 | " shell Run an erlang shell with SHELL_OPTS or reasonable default" 770 | 771 | # Plugin-specific targets. 772 | 773 | $(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep)))) 774 | 775 | build-shell-deps: $(ALL_SHELL_DEPS_DIRS) 776 | @for dep in $(ALL_SHELL_DEPS_DIRS) ; do $(MAKE) -C $$dep ; done 777 | 778 | shell: build-shell-deps 779 | $(gen_verbose) erl $(SHELL_PATH) $(SHELL_OPTS) 780 | --------------------------------------------------------------------------------