4
votes

I followed the Getting Started instructions for Cowboy, and I've got Cowboy running and listening on port 8080, and I got the Hello Erlang! response when I entered http://localhost:8080 in my browser. Now, how do I use Gun to connect to Cowboy?

I read the Gun docs, and it says to add "Gun as an erlang.mk dependency". So I downloaded erlang.mk:

~/erlang_programs/my_gun$ curl -O https://erlang.mk/erlang.mk

and following the Erlang.mk User Guide, I created an application:

~/erlang_programs/my_gun$ gmake -f erlang.mk bootstrap

Then I added gun as a dependency to the Makefile:

PROJECT = my_gun
PROJECT_DESCRIPTION = New project
PROJECT_VERSION = 0.1.0

DEPS = gun

include erlang.mk

Then I compiled:

~/erlang_programs/my_gun$ gmake
gmake[1]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/gun'
gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib'
 ERLC   cow_cookie.erl cow_date.erl cow_hpack.erl cow_http.erl cow_http2.erl cow_http_hd.erl cow_http_te.erl cow_mimetypes.erl cow_multipart.erl cow_qs.erl cow_spdy.erl cow_sse.erl cow_uri.erl cow_ws.erl
 APP    cowlib
gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib'
gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/ranch'
 DEPEND ranch.d
 ERLC   ranch.erl ranch_acceptor.erl ranch_acceptors_sup.erl ranch_app.erl ranch_conns_sup.erl ranch_listener_sup.erl ranch_protocol.erl ranch_server.erl ranch_ssl.erl ranch_sup.erl ranch_tcp.erl ranch_transport.erl
 APP    ranch
gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/ranch'
 DEPEND gun.d
 ERLC   gun.erl gun_app.erl gun_content_handler.erl gun_data.erl gun_http.erl gun_http2.erl gun_spdy.erl gun_sse.erl gun_sup.erl gun_ws.erl gun_ws_handler.erl
 APP    gun
 GEN    rebar.config
gmake[1]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/gun'
 DEPEND my_gun.d
 ERLC   my_gun_app.erl my_gun_sup.erl
 APP    my_gun
 GEN    /Users/7stud/erlang_programs/my_gun/.erlang.mk/relx
===> Starting relx build process ...
===> Resolving OTP Applications from directories:
          /Users/7stud/erlang_programs/my_gun/ebin
          /Users/7stud/erlang_programs/my_gun/deps
          /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang/lib
          /Users/7stud/erlang_programs/my_gun/apps
===> Resolved my_gun_release-1
===> rendering builtin_hook_status hook to "/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/bin/hooks/builtin/status"
===> Including Erts from /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang
===> release successfully created!
===> tarball /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/my_gun_release-1.tar.gz successfully created!

But when I switch to the erlang shell and try to start gun, I get an error:

~/erlang_programs/my_gun$ erl
Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] 

Eshell V8.2  (abort with ^G)

1> application:ensure_all_started(gun).
{error,{gun,{"no such file or directory","gun.app"}}}

Can someone post a simple example of how to use Gun (or any other http client with websocket support) to connect to Cowboy?


Okay, I made some progress. I deleted the my_gun directory, recreated the directory, redownloaded erlang.mk, and created a release with the following command:

~/erlang_programs/my_gun$ gmake -f erlang.mk bootstrap-lib bootstrap-rel

Then I added the gun dependency to the Makefile (as described above). Then I did:

 ~/erlang_programs/my_gun$ gmake run

If there are no errors in the code, then an erlang shell will launch. In the erlang shell, I entered the following code (see the tip below to avoid having to type all the code in the shell):

([email protected])1> application:ensure_all_started(gun).
{ok,[]}

([email protected])2> {ok, ConnPid} = gun:open("localhost", 8080).  

=PROGRESS REPORT==== 10-Jul-2017::05:21:58 ===
          supervisor: {local,inet_gethost_native_sup}
             started: [{pid,<0.366.0>},{mfa,{inet_gethost_native,init,[[]]}}]
{ok,<0.364.0>}

=PROGRESS REPORT==== 10-Jul-2017::05:21:58 ===
          supervisor: {local,kernel_safe_sup}
             started: [{pid,<0.365.0>},
                       {id,inet_gethost_native_sup},
                       {mfargs,{inet_gethost_native,start_link,[]}},
                       {restart_type,temporary},
                       {shutdown,1000},
                       {child_type,worker}]

([email protected])3> {ok, Protocol} = gun:await_up(ConnPid).
{ok,http}

([email protected])4> gun:ws_upgrade(ConnPid, "/websocket").
#Ref<0.0.3.244>

([email protected])5> receive                                          
([email protected])5> {gun_ws_upgrade, ConnPid, ok, Headers} ->        
([email protected])5> upgrade_success(ConnPid);
([email protected])5> {gun_response, ConnPid, _, _, Status, Headers} ->
([email protected])5> exit({ws_upgrade_failed, Status, Headers});
([email protected])5> {gun_error, ConnPid, StreamRef, Reason} ->
([email protected])5> exit({ws_upgrade_failed, Reason})
([email protected])5> after 1000 ->
([email protected])5> exit(timeout)
([email protected])5> end.

=CRASH REPORT==== 10-Jul-2017::05:25:17 ===
  crasher:
    initial call: gun:proc_lib_hack/5
    pid: <0.364.0>
    registered_name: []
    exception exit: {{owner_gone,normal},
                     [{gun,loop,1,[{file,"src/gun.erl"},{line,706}]},
                      {gun,proc_lib_hack,5,[{file,"src/gun.erl"},{line,535}]},
                      {proc_lib,init_p_do_apply,3,
                                [{file,"proc_lib.erl"},{line,247}]}]}
      in function  gun:proc_lib_hack/5 (src/gun.erl, line 540)
    ancestors: [gun_sup,<0.343.0>]
    messages: []
    links: [<0.344.0>]
    dictionary: []
    trap_exit: false
    status: running
    heap_size: 2586
    stack_size: 27
    reductions: 10857
  neighbours:

=SUPERVISOR REPORT==== 10-Jul-2017::05:25:17 ===
     Supervisor: {local,gun_sup}
     Context:    child_terminated
     Reason:     {{owner_gone,normal},
                  [{gun,loop,1,[{file,"src/gun.erl"},{line,706}]},
                   {gun,proc_lib_hack,5,[{file,"src/gun.erl"},{line,535}]},
                   {proc_lib,init_p_do_apply,3,
                             [{file,"proc_lib.erl"},{line,247}]}]}
     Offender:   [{pid,<0.364.0>},
                  {id,gun},
                  {mfargs,{gun,start_link,undefined}},
                  {restart_type,temporary},
                  {shutdown,5000},
                  {child_type,worker}]

** exception exit: {ws_upgrade_failed,404,
                                      [{<<"content-length">>,<<"0">>},
                                       {<<"date">>,
                                        <<"Mon, 10 Jul 2017 11:22:38 GMT">>},
                                       {<<"server">>,<<"Cowboy">>}]}
([email protected])6> 

Now, I'm getting a 404 error. Cowboy is running, and when I enter http://localhost:8080 in my browser, I see a response message. Why is Gun giving me a 404 error?


Next I tried using the instructions in the Gun docs to make a GET request:

StreamRef = gun:get(ConnPid, "/").

case gun:await(ConnPid, StreamRef) of
    {response, fin, Status, Headers} ->
        no_data;
    {response, nofin, Status, Headers} ->
        {ok, Body} = gun:await_body(ConnPid, StreamRef),
        io:format("~s~n", [Body])
end.

and that was successful:

=PROGRESS REPORT==== 10-Jul-2017::06:36:14 ===
          supervisor: {local,inet_gethost_native_sup}
             started: [{pid,<0.367.0>},{mfa,{inet_gethost_native,init,[[]]}}]

=PROGRESS REPORT==== 10-Jul-2017::06:36:14 ===
          supervisor: {local,kernel_safe_sup}
             started: [{pid,<0.366.0>},
                       {id,inet_gethost_native_sup},
                       {mfargs,{inet_gethost_native,start_link,[]}},
                       {restart_type,temporary},
                       {shutdown,1000},
                       {child_type,worker}]
Hello Erlang!
ok
([email protected])2> 

The response means that I was able to use Gun to interact with a Cowboy server--but I want to use websockets. Any ideas what I am doing wrong?


tip:

To avoid having to type all that code in the gun shell, I created the file ~/erlang_programs/my_gun/src/my.erl:

-module(my).
-compile(export_all).

get() ->
    {ok, _} = application:ensure_all_started(gun),
    {ok, ConnPid} = gun:open("localhost", 8080),
    {ok, _Protocol} = gun:await_up(ConnPid),

    StreamRef = gun:get(ConnPid, "/"),

    case gun:await(ConnPid, StreamRef) of
        {response, fin, _Status, _Headers} ->
            no_data;
        {response, nofin, _Status, _Headers} ->
            {ok, Body} = gun:await_body(ConnPid, StreamRef),
            io:format("~s~n", [Body])
    end.

ws() ->

    {ok, _} = application:ensure_all_started(gun),
    {ok, ConnPid} = gun:open("localhost", 8080),
    {ok, _Protocol} = gun:await_up(ConnPid),

    gun:ws_upgrade(ConnPid, "/websocket"),

    receive
    {gun_ws_upgrade, ConnPid, ok, Headers} ->
            upgrade_success(ConnPid, Headers);
    {gun_response, ConnPid, _, _, Status, Headers} ->
            exit({ws_upgrade_failed, Status, Headers});
    {gun_error, _ConnPid, _StreamRef, Reason} ->
            exit({ws_upgrade_failed, Reason})
    %% More clauses here as needed.
    after 1000 ->
        exit(timeout)
    end,

    gun:shutdown(ConnPid).


upgrade_success(ConnPid, Headers) ->
    io:format("Upgraded ~w. Success!~nHeaders:~n~p~n", 
              [ConnPid, Headers]).

Then the make (or gmake) command:

 ~/erlang_programs/my_gun$ gmake run

will compile everything in the src/ directory and alert you to any errors. Once the gun shell successfully launches in response to gmake run, you can for instance do:

([email protected])1> my:get().

=PROGRESS REPORT==== 10-Jul-2017::06:36:14 ===
          supervisor: {local,inet_gethost_native_sup}
             started: [{pid,<0.367.0>},{mfa,{inet_gethost_native,init,[[]]}}]

=PROGRESS REPORT==== 10-Jul-2017::06:36:14 ===
          supervisor: {local,kernel_safe_sup}
             started: [{pid,<0.366.0>},
                       {id,inet_gethost_native_sup},
                       {mfargs,{inet_gethost_native,start_link,[]}},
                       {restart_type,temporary},
                       {shutdown,1000},
                       {child_type,worker}]
Hello Erlang!
ok
([email protected])2> 

Response to comment:

Since you're getting a 404, I guess you don't have a websocket handler defined in the cowboy routes.

You are right. I only had the handler shown in the Cowboy Getting Started guide. Now, I've added the websocket setup code and a websocket handler to cowboy. I now have the routes:

hello_erlang_app.erl:

-module(hello_erlang_app).
-behaviour(application).

-export([start/2]).
-export([stop/1]).

start(_Type, _Args) ->
    Dispatch = cowboy_router:compile([
        {'_', [{"/", hello_handler, []}] },
        {'_', [{"/websocket", myws_handler, []}] }
    ]),

    {ok, _} = cowboy:start_clear(my_http_listener,
        [{port, 8080}],
        #{env => #{dispatch => Dispatch} }
    ),

    hello_erlang_sup:start_link().

stop(_State) ->
    ok.

Here's my handler:

-module(myws_handler).
-compile(export_all).

init(Req, State) ->
    {cowboy_websocket, Req, State}.  %Perform websocket setup

websocket_handle({text, Msg}, State) ->
    {
     reply, 
     {text, <<"Server received: ", Msg/binary>>, State}  %%Error in format here, too!
    };
websocket_handle(_Data, State) ->
    {ok, State}.

But I'm still getting a 404 error when I execute my:ws() in the gun shell:

-module(my).
-compile(export_all).

get() ->
    {ok, _} = application:ensure_all_started(gun),
    {ok, ConnPid} = gun:open("localhost", 8080),
    {ok, _Protocol} = gun:await_up(ConnPid),

    StreamRef = gun:get(ConnPid, "/"),

    case gun:await(ConnPid, StreamRef) of
        {response, fin, _Status, _Headers} ->
            no_data;
        {response, nofin, _Status, _Headers} ->
            {ok, Body} = gun:await_body(ConnPid, StreamRef),
            io:format("~s~n", [Body])
    end.

ws() ->

    {ok, _} = application:ensure_all_started(gun),
    {ok, ConnPid} = gun:open("localhost", 8080),
    {ok, _Protocol} = gun:await_up(ConnPid),

    gun:ws_upgrade(ConnPid, "/websocket"),

    receive
        {gun_ws_upgrade, ConnPid, ok, Headers} ->
            upgrade_success(ConnPid, Headers);
        {gun_response, ConnPid, _, _, Status, Headers} ->
            exit({ws_upgrade_failed, Status, Headers});
        {gun_error, _ConnPid, _StreamRef, Reason} ->
            exit({ws_upgrade_failed, Reason})
    %% More clauses here as needed.
    after 1000 ->
        exit(timeout)
    end,

    gun:shutdown(ConnPid).


upgrade_success(ConnPid, Headers) ->
    io:format("Upgraded ~w. Success!~nHeaders:~n~w~n", 
              [ConnPid, Headers]).

Here's the output:

~/erlang_programs/my_gun$ gmake run
gmake[1]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/gun'
gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib'
gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib'
gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/ranch'
gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/ranch'
 GEN    rebar.config
gmake[1]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/gun'
 DEPEND my_gun.d
 ERLC   my.erl
 APP    my_gun
===> Starting relx build process ...
===> Resolving OTP Applications from directories:
          /Users/7stud/erlang_programs/my_gun/ebin
          /Users/7stud/erlang_programs/my_gun/deps
          /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang/lib
          /Users/7stud/erlang_programs/my_gun/apps
          /Users/7stud/erlang_programs/my_gun/_rel
===> Resolved my_gun_release-1
===> rendering builtin_hook_status hook to "/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/bin/hooks/builtin/status"
===> Including Erts from /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang
===> release successfully created!
===> tarball /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/my_gun_release-1.tar.gz successfully created!
Exec: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/erts-8.2/bin/erlexec -boot /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/my_gun_release -mode embedded -boot_var ERTS_LIB_DIR /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/lib -config /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/sys.config -args_file /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/vm.args -pa -- console
Root: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release
/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release
heart_beat_kill_pid = 32843
Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]


=PROGRESS REPORT==== 10-Jul-2017::16:26:05 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.353.0>},
                       {id,alarm_handler},
                       {mfargs,{alarm_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 10-Jul-2017::16:26:05 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.352.0>},
                       {id,sasl_safe_sup},
                       {mfargs,
                           {supervisor,start_link,
                               [{local,sasl_safe_sup},sasl,safe]}},
                       {restart_type,permanent},
                       {shutdown,infinity},
                       {child_type,supervisor}]

=PROGRESS REPORT==== 10-Jul-2017::16:26:05 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.354.0>},
                       {id,release_handler},
                       {mfargs,{release_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 10-Jul-2017::16:26:05 ===
         application: sasl
          started_at: '[email protected]'

=PROGRESS REPORT==== 10-Jul-2017::16:26:05 ===
          supervisor: {local,runtime_tools_sup}
             started: [{pid,<0.360.0>},
                       {id,ttb_autostart},
                       {mfargs,{ttb_autostart,start_link,[]}},
                       {restart_type,temporary},
                       {shutdown,3000},
                       {child_type,worker}]

=PROGRESS REPORT==== 10-Jul-2017::16:26:05 ===
         application: runtime_tools
          started_at: '[email protected]'
Eshell V8.2  (abort with ^G)

([email protected])1> my:ws().

=PROGRESS REPORT==== 10-Jul-2017::16:26:08 ===
          supervisor: {local,inet_gethost_native_sup}
             started: [{pid,<0.367.0>},{mfa,{inet_gethost_native,init,[[]]}}]

=PROGRESS REPORT==== 10-Jul-2017::16:26:08 ===
          supervisor: {local,kernel_safe_sup}
             started: [{pid,<0.366.0>},
                       {id,inet_gethost_native_sup},
                       {mfargs,{inet_gethost_native,start_link,[]}},
                       {restart_type,temporary},
                       {shutdown,1000},
                       {child_type,worker}]

=CRASH REPORT==== 10-Jul-2017::16:26:08 ===
  crasher:
    initial call: gun:proc_lib_hack/5
    pid: <0.365.0>
    registered_name: []
    exception exit: {{owner_gone,normal},
                     [{gun,loop,1,[{file,"src/gun.erl"},{line,706}]},
                      {gun,proc_lib_hack,5,[{file,"src/gun.erl"},{line,535}]},
                      {proc_lib,init_p_do_apply,3,
                                [{file,"proc_lib.erl"},{line,247}]}]}
      in function  gun:proc_lib_hack/5 (src/gun.erl, line 540)
    ancestors: [gun_sup,<0.345.0>]
    messages: []
    links: [<0.346.0>]
    dictionary: []
    trap_exit: false
    status: running
    heap_size: 610
    stack_size: 27
    reductions: 1042
  neighbours:

=SUPERVISOR REPORT==== 10-Jul-2017::16:26:08 ===
     Supervisor: {local,gun_sup}
     Context:    child_terminated
     Reason:     {{owner_gone,normal},
                  [{gun,loop,1,[{file,"src/gun.erl"},{line,706}]},
                   {gun,proc_lib_hack,5,[{file,"src/gun.erl"},{line,535}]},
                   {proc_lib,init_p_do_apply,3,
                             [{file,"proc_lib.erl"},{line,247}]}]}
     Offender:   [{pid,<0.365.0>},
                  {id,gun},
                  {mfargs,{gun,start_link,undefined}},
                  {restart_type,temporary},
                  {shutdown,5000},
                  {child_type,worker}]

** exception exit: {ws_upgrade_failed,404,
                                      [{<<"content-length">>,<<"0">>},
                                       {<<"date">>,
                                        <<"Mon, 10 Jul 2017 22:26:08 GMT">>},
                                       {<<"server">>,<<"Cowboy">>}]}
     in function  my:ws/0 (src/my.erl, line 30)

([email protected])2> 

I saved all my files and restarted cowboy and gun, so the changes I made to the code are being executed, but I still get the 404 error.


I compared the format of my routes to the routes in the example that spawn_think linked to in the comments, and my format was wrong. Here is what I have now:

-module(hello_erlang_app).
-behaviour(application).

-export([start/2]).
-export([stop/1]).

start(_Type, _Args) ->
    Dispatch = cowboy_router:compile([
        {'_', [
               {"/", hello_handler, []},
               {"/websocket", myws_handler, []} 
        ]}
    ]),

    {ok, _} = cowboy:start_clear(my_http_listener,
        [{port, 8080}],
        #{env => #{dispatch => Dispatch} }
    ),

    hello_erlang_sup:start_link().

stop(_State) ->
    ok.

And after adjusting one of the control sequences in the io:format() statement in my gun client:

-module(my).
-compile(export_all).

get() ->
    ...

ws() ->
    ...

upgrade_success(ConnPid, Headers) ->
    io:format("Upgraded ~w. Success!~nHeaders:~n~p~n",   %% <*** CHANGED ~w to ~p 
              [ConnPid, Headers]).

here is the output:

~/erlang_programs/my_gun$ gmake run
gmake[1]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/gun'
gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib'
gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib'
gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/ranch'
gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/ranch'
 GEN    rebar.config
gmake[1]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/gun'
 DEPEND my_gun.d
 ERLC   my.erl
 APP    my_gun
===> Starting relx build process ...
===> Resolving OTP Applications from directories:
          /Users/7stud/erlang_programs/my_gun/ebin
          /Users/7stud/erlang_programs/my_gun/deps
          /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang/lib
          /Users/7stud/erlang_programs/my_gun/apps
          /Users/7stud/erlang_programs/my_gun/_rel
===> Resolved my_gun_release-1
===> rendering builtin_hook_status hook to "/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/bin/hooks/builtin/status"
===> Including Erts from /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang
===> release successfully created!
===> tarball /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/my_gun_release-1.tar.gz successfully created!
Exec: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/erts-8.2/bin/erlexec -boot /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/my_gun_release -mode embedded -boot_var ERTS_LIB_DIR /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/lib -config /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/sys.config -args_file /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/vm.args -pa -- console
Root: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release
/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release
heart_beat_kill_pid = 34141
Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]


=PROGRESS REPORT==== 10-Jul-2017::16:50:53 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.353.0>},
                       {id,alarm_handler},
                       {mfargs,{alarm_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 10-Jul-2017::16:50:53 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.352.0>},
                       {id,sasl_safe_sup},
                       {mfargs,
                           {supervisor,start_link,
                               [{local,sasl_safe_sup},sasl,safe]}},
                       {restart_type,permanent},
                       {shutdown,infinity},
                       {child_type,supervisor}]

=PROGRESS REPORT==== 10-Jul-2017::16:50:53 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.354.0>},
                       {id,release_handler},
                       {mfargs,{release_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 10-Jul-2017::16:50:53 ===
         application: sasl
          started_at: '[email protected]'

=PROGRESS REPORT==== 10-Jul-2017::16:50:53 ===
          supervisor: {local,runtime_tools_sup}
             started: [{pid,<0.360.0>},
                       {id,ttb_autostart},
                       {mfargs,{ttb_autostart,start_link,[]}},
                       {restart_type,temporary},
                       {shutdown,3000},
                       {child_type,worker}]

=PROGRESS REPORT==== 10-Jul-2017::16:50:53 ===
         application: runtime_tools
          started_at: '[email protected]'
Eshell V8.2  (abort with ^G)
([email protected])1> my:ws().

=PROGRESS REPORT==== 10-Jul-2017::16:50:57 ===
          supervisor: {local,inet_gethost_native_sup}
             started: [{pid,<0.367.0>},{mfa,{inet_gethost_native,init,[[]]}}]

=PROGRESS REPORT==== 10-Jul-2017::16:50:57 ===
          supervisor: {local,kernel_safe_sup}
             started: [{pid,<0.366.0>},
                       {id,inet_gethost_native_sup},
                       {mfargs,{inet_gethost_native,start_link,[]}},
                       {restart_type,temporary},
                       {shutdown,1000},
                       {child_type,worker}]
Upgraded <0.365.0>. Success!
Headers:
[{<<"connection">>,<<"Upgrade">>},
 {<<"date">>,<<"Mon, 10 Jul 2017 22:50:56 GMT">>},
 {<<"sec-websocket-accept">>,<<"S1w7rWXToZefi/NOEcDAEDb4yEU=">>},
 {<<"server">>,<<"Cowboy">>},
 {<<"upgrade">>,<<"websocket">>}]
ok
([email protected])2> 

I've reached the forum's limit on text, so see my answer for how I was actually able to send and receive data using a websocket.

2
To start, you need to have the path to beam and .app files discoverable to the shell. You can either do it manually by using -p flag when starting it or you can run the release using make runAhmed Omar
@spawn_think, Thanks for taking a look. I made some progress. Now I'm getting a 404 error. I added what's happening now to the end of my question.7stud
Since you're getting a 404, I guess you don't have a websocket handler defined in the cowboy routes. You can see an example in github.com/ninenines/cowboy/tree/master/examples/websocketAhmed Omar
P.S: it would help if you post the cowboy routes you have defined.Ahmed Omar
@spawn_think, I posted my current problems.7stud

2 Answers

2
votes

A summary of the issues/suggestions from the comments discussion

  1. For running your Erlang app (in this case including gun as a dependency), you need to have the path to beam and .app files discoverable to the shell. When using erlang.mk, the easiest way to do that would be running the release using make run

  2. On Cowboy side, to have Websocket handled correctly, you need to have a handler defined in your cowboy routes. See example in Cowboy Websocket example

See also Gun user guide for more details about handling Websocket in Gun

1
votes

Success!

~/erlang_programs/my_gun/src/my.erl:

-module(my).
-compile(export_all).

get() ->
    ...

ws() ->

    {ok, _} = application:ensure_all_started(gun),
    {ok, ConnPid} = gun:open("localhost", 8080),
    {ok, _Protocol} = gun:await_up(ConnPid),

    gun:ws_upgrade(ConnPid, "/websocket"),

    receive
        {gun_ws_upgrade, ConnPid, ok, Headers} ->
                upgrade_success(ConnPid, Headers);
        {gun_response, ConnPid, _, _, Status, Headers} ->
                exit({ws_upgrade_failed, Status, Headers});
        {gun_error, _ConnPid, _StreamRef, Reason} ->
                exit({ws_upgrade_failed, Reason})
        %% More clauses here as needed.
    after 1000 ->
            exit(timeout)
    end,


upgrade_success(ConnPid, Headers) ->
    io:format("Upgraded ~w. Success!~nHeaders:~n~p~n", 
              [ConnPid, Headers]),

    gun:ws_send(ConnPid, {text, "It's raining!"}),

    receive
        {gun_ws, ConnPid, {text, Msg} } ->
            io:format("~s~n", [Msg])
    end.

On the cowboy side:

-module(myws_handler).
-compile(export_all).

init(Req, State) ->
    {cowboy_websocket, Req, State}.  %Perform websocket setup

websocket_handle({text, Msg}, State) ->
    {
     reply, 
     {text, io_lib:format("Server received: ~s", [Msg]) },
     State
    };
websocket_handle(_Other, State) ->  %Ignore
    {ok, State}. 

Here's the output:

~/erlang_programs/my_gun$ gmake run
gmake[1]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/gun'
gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib'
gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/cowlib'
gmake[2]: Entering directory '/Users/7stud/erlang_programs/my_gun/deps/ranch'
gmake[2]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/ranch'
 GEN    rebar.config
gmake[1]: Leaving directory '/Users/7stud/erlang_programs/my_gun/deps/gun'
 DEPEND my_gun.d
 ERLC   my.erl
 APP    my_gun
===> Starting relx build process ...
===> Resolving OTP Applications from directories:
          /Users/7stud/erlang_programs/my_gun/ebin
          /Users/7stud/erlang_programs/my_gun/deps
          /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang/lib
          /Users/7stud/erlang_programs/my_gun/apps
          /Users/7stud/erlang_programs/my_gun/_rel
===> Resolved my_gun_release-1
===> rendering builtin_hook_status hook to "/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/bin/hooks/builtin/status"
===> Including Erts from /Users/7stud/.evm/erlang_versions/otp_src_19.2/lib/erlang
===> release successfully created!
===> tarball /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/my_gun_release-1.tar.gz successfully created!
Exec: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/erts-8.2/bin/erlexec -boot /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/my_gun_release -mode embedded -boot_var ERTS_LIB_DIR /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/lib -config /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/sys.config -args_file /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release/releases/1/vm.args -pa -- console
Root: /Users/7stud/erlang_programs/my_gun/_rel/my_gun_release
/Users/7stud/erlang_programs/my_gun/_rel/my_gun_release
heart_beat_kill_pid = 38883
Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]


=PROGRESS REPORT==== 10-Jul-2017::18:04:38 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.353.0>},
                       {id,alarm_handler},
                       {mfargs,{alarm_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 10-Jul-2017::18:04:38 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.352.0>},
                       {id,sasl_safe_sup},
                       {mfargs,
                           {supervisor,start_link,
                               [{local,sasl_safe_sup},sasl,safe]}},
                       {restart_type,permanent},
                       {shutdown,infinity},
                       {child_type,supervisor}]

=PROGRESS REPORT==== 10-Jul-2017::18:04:38 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.354.0>},
                       {id,release_handler},
                       {mfargs,{release_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 10-Jul-2017::18:04:38 ===
         application: sasl
          started_at: '[email protected]'

=PROGRESS REPORT==== 10-Jul-2017::18:04:38 ===
          supervisor: {local,runtime_tools_sup}
             started: [{pid,<0.360.0>},
                       {id,ttb_autostart},
                       {mfargs,{ttb_autostart,start_link,[]}},
                       {restart_type,temporary},
                       {shutdown,3000},
                       {child_type,worker}]

=PROGRESS REPORT==== 10-Jul-2017::18:04:38 ===
         application: runtime_tools
          started_at: '[email protected]'
Eshell V8.2  (abort with ^G)

([email protected])1> my:ws().

=PROGRESS REPORT==== 10-Jul-2017::18:04:41 ===
          supervisor: {local,inet_gethost_native_sup}
             started: [{pid,<0.367.0>},{mfa,{inet_gethost_native,init,[[]]}}]

=PROGRESS REPORT==== 10-Jul-2017::18:04:41 ===
          supervisor: {local,kernel_safe_sup}
             started: [{pid,<0.366.0>},
                       {id,inet_gethost_native_sup},
                       {mfargs,{inet_gethost_native,start_link,[]}},
                       {restart_type,temporary},
                       {shutdown,1000},
                       {child_type,worker}]
Upgraded <0.365.0>. Success!
Headers:
[{<<"connection">>,<<"Upgrade">>},
 {<<"date">>,<<"Tue, 11 Jul 2017 00:04:40 GMT">>},
 {<<"sec-websocket-accept">>,<<"pYv8QeeJfzQgaS/x8flZHyrIexk=">>},
 {<<"server">>,<<"Cowboy">>},
 {<<"upgrade">>,<<"websocket">>}]
Server received: It's raining!
ok

([email protected])2>