From 93c88fdc541c3f8a4ebbae3699bee90d00dc637f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 4 Jun 2018 08:40:31 +0200 Subject: Remove the dependency on Ranch We instead of two new modules, gun_tcp and gun_tls. They only have 6 functions so far, much less than what Ranch provided before. Also renames ssl to tls where applicable. It's still possible to use the ssl transport option but it's now undocumented. --- Makefile | 3 +-- doc/src/guide/connect.asciidoc | 8 +++---- doc/src/guide/start.asciidoc | 2 +- doc/src/manual/gun.asciidoc | 12 +++++----- doc/src/manual/gun_app.asciidoc | 1 - ebin/gun.app | 4 ++-- rebar.config | 2 +- src/gun.erl | 19 ++++++++++------ src/gun_http.erl | 2 +- src/gun_http2.erl | 30 ++++++++++++------------- src/gun_tcp.erl | 49 +++++++++++++++++++++++++++++++++++++++++ src/gun_tls.erl | 49 +++++++++++++++++++++++++++++++++++++++++ 12 files changed, 141 insertions(+), 40 deletions(-) create mode 100644 src/gun_tcp.erl create mode 100644 src/gun_tls.erl diff --git a/Makefile b/Makefile index 6cedb2b..afa900f 100644 --- a/Makefile +++ b/Makefile @@ -12,9 +12,8 @@ CT_OPTS += -pa test -ct_hooks gun_ct_hook [] # -boot start_sasl LOCAL_DEPS = ssl -DEPS = cowlib ranch +DEPS = cowlib dep_cowlib = git https://github.com/ninenines/cowlib master -dep_ranch = git https://github.com/ninenines/ranch master TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) ct_helper dep_ct_helper = git https://github.com/extend/ct_helper.git master diff --git a/doc/src/guide/connect.asciidoc b/doc/src/guide/connect.asciidoc index e0b09d6..f7983bc 100644 --- a/doc/src/guide/connect.asciidoc +++ b/doc/src/guide/connect.asciidoc @@ -34,9 +34,9 @@ The `gun:open/{2,3}` function must be used to open a connection. {ok, ConnPid} = gun:open("example.org", 443). If the port given is 443, Gun will attempt to connect using -SSL. The protocol will be selected automatically using the +TLS. The protocol will be selected automatically using the ALPN extension for TLS. By default Gun supports HTTP/2 -and HTTP/1.1 when connecting using SSL. +and HTTP/1.1 when connecting using TLS. For any other port, Gun will attempt to connect using TCP and will use the HTTP/1.1 protocol. @@ -47,10 +47,10 @@ options. The manual documents all available options. Options can be provided as a third argument, and take the form of a map. -.Opening an SSL connection to example.org on port 8443 +.Opening a TLS connection to example.org on port 8443 [source,erlang] -{ok, ConnPid} = gun:open("example.org", 8443, #{transport=>ssl}). +{ok, ConnPid} = gun:open("example.org", 8443, #{transport => tls}). === Waiting for the connection to be established diff --git a/doc/src/guide/start.asciidoc b/doc/src/guide/start.asciidoc index 6d93e2e..17cbe87 100644 --- a/doc/src/guide/start.asciidoc +++ b/doc/src/guide/start.asciidoc @@ -27,7 +27,7 @@ use it. [source,erlang] ---- 1> application:ensure_all_started(gun). -{ok,[ranch,crypto,cowlib,asn1,public_key,ssl,gun]} +{ok,[crypto,cowlib,asn1,public_key,ssl,gun]} ---- === Stopping diff --git a/doc/src/manual/gun.asciidoc b/doc/src/manual/gun.asciidoc index b5ed039..b15ce09 100644 --- a/doc/src/manual/gun.asciidoc +++ b/doc/src/manual/gun.asciidoc @@ -149,7 +149,7 @@ opts() :: #{ retry => non_neg_integer(), retry_timeout => pos_integer(), trace => boolean(), - transport => tcp | ssl, + transport => tcp | tls, transport_opts => [gen_tcp:connect_option()] | [ssl:connect_option()], ws_opts => ws_opts() } @@ -175,11 +175,11 @@ protocols - see below:: Ordered list of preferred protocols. When the transport is `tcp`, this list must contain exactly one protocol. When the transport -is `ssl`, this list must contain at least one protocol and will be +is `tls`, this list must contain at least one protocol and will be used to negotiate a protocol via ALPN. When the server does not support ALPN then `http` will always be used. Defaults to `[http]` when the transport is `tcp`, and `[http2, http]` when the -transport is `ssl`. +transport is `tls`. retry (5):: @@ -196,12 +196,12 @@ only be used during debugging. transport - see below:: -Whether to use SSL or plain TCP. The default varies depending on the -port used. Port 443 defaults to `ssl`. All other ports default to `tcp`. +Whether to use TLS or plain TCP. The default varies depending on the +port used. Port 443 defaults to `tls`. All other ports default to `tcp`. transport_opts ([]):: -Transport options. They are TCP options or SSL options depending on +Transport options. They are TCP options or TLS options depending on the selected transport. ws_opts (#{}):: diff --git a/doc/src/manual/gun_app.asciidoc b/doc/src/manual/gun_app.asciidoc index 606d9fb..c369095 100644 --- a/doc/src/manual/gun_app.asciidoc +++ b/doc/src/manual/gun_app.asciidoc @@ -19,7 +19,6 @@ to the server and reconnects automatically when necessary. == Dependencies -// @todo I do not want a dependency on Ranch, remove it * link:man:cowlib(7)[cowlib(7)] - Support library for manipulating Web protocols * ssl - Secure communication over sockets diff --git a/ebin/gun.app b/ebin/gun.app index f40aefd..374fb93 100644 --- a/ebin/gun.app +++ b/ebin/gun.app @@ -1,9 +1,9 @@ {application, 'gun', [ {description, "HTTP/1.1, HTTP/2 and Websocket client for Erlang/OTP."}, {vsn, "1.0.0-pre.5"}, - {modules, ['gun','gun_app','gun_content_handler','gun_data_h','gun_http','gun_http2','gun_sse_h','gun_sup','gun_ws','gun_ws_h']}, + {modules, ['gun','gun_app','gun_content_handler','gun_data_h','gun_http','gun_http2','gun_sse_h','gun_sup','gun_tcp','gun_tls','gun_ws','gun_ws_h']}, {registered, [gun_sup]}, - {applications, [kernel,stdlib,ssl,cowlib,ranch]}, + {applications, [kernel,stdlib,ssl,cowlib]}, {mod, {gun_app, []}}, {env, []} ]}. \ No newline at end of file diff --git a/rebar.config b/rebar.config index 21a0435..348f53e 100644 --- a/rebar.config +++ b/rebar.config @@ -1,4 +1,4 @@ {deps, [ -{cowlib,".*",{git,"https://github.com/ninenines/cowlib","master"}},{ranch,".*",{git,"https://github.com/ninenines/ranch","master"}} +{cowlib,".*",{git,"https://github.com/ninenines/cowlib","master"}} ]}. {erl_opts, [debug_info,warn_export_vars,warn_shadow_vars,warn_obsolete_guard]}. diff --git a/src/gun.erl b/src/gun.erl index 1224c82..8156015 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -99,7 +99,7 @@ retry => non_neg_integer(), retry_timeout => pos_integer(), trace => boolean(), - transport => tcp | ssl, + transport => tcp | tls | ssl, transport_opts => [gen_tcp:connect_option()] | [ssl:connect_option()], ws_opts => ws_opts() }. @@ -161,7 +161,12 @@ open(Host, Port, Opts) when is_list(Host); is_atom(Host); is_tuple(Host) -> open_unix(SocketPath, Opts) -> do_open({local, SocketPath}, 0, Opts). -do_open(Host, Port, Opts) -> +do_open(Host, Port, Opts0) -> + %% We accept both ssl and tls but only use tls in the code. + Opts = case Opts0 of + #{transport := ssl} -> Opts0#{transport => tls}; + _ -> Opts0 + end, case check_options(maps:to_list(Opts)) of ok -> case supervisor:start_child(gun_sup, [self(), Host, Port, Opts]) of @@ -215,7 +220,7 @@ check_options([{retry_timeout, T}|Opts]) when is_integer(T), T >= 0 -> check_options(Opts); check_options([{trace, B}|Opts]) when B =:= true; B =:= false -> check_options(Opts); -check_options([{transport, T}|Opts]) when T =:= tcp; T =:= ssl -> +check_options([{transport, T}|Opts]) when T =:= tcp; T =:= tls -> check_options(Opts); check_options([{transport_opts, L}|Opts]) when is_list(L) -> check_options(Opts); @@ -588,17 +593,17 @@ init(Parent, Owner, Host, Port, Opts) -> ok = proc_lib:init_ack(Parent, {ok, self()}), Retry = maps:get(retry, Opts, 5), Transport = case maps:get(transport, Opts, default_transport(Port)) of - tcp -> ranch_tcp; - ssl -> ranch_ssl + tcp -> gun_tcp; + tls -> gun_tls end, OwnerRef = monitor(process, Owner), connect(#state{parent=Parent, owner=Owner, owner_ref=OwnerRef, host=Host, port=Port, opts=Opts, transport=Transport}, Retry). -default_transport(443) -> ssl; +default_transport(443) -> tls; default_transport(_) -> tcp. -connect(State=#state{host=Host, port=Port, opts=Opts, transport=Transport=ranch_ssl}, Retries) -> +connect(State=#state{host=Host, port=Port, opts=Opts, transport=Transport=gun_tls}, Retries) -> Protocols = [case P of http -> <<"http/1.1">>; http2 -> <<"h2">> diff --git a/src/gun_http.erl b/src/gun_http.erl index 2fd0aac..61409fb 100644 --- a/src/gun_http.erl +++ b/src/gun_http.erl @@ -500,7 +500,7 @@ ws_upgrade(State=#http_state{socket=Socket, transport=Transport, owner=Owner, ou {<<"sec-websocket-key">>, Key} |Headers2 ], - IsSecure = Transport:secure(), + IsSecure = Transport =:= gun_tls, Headers = case lists:keymember(<<"host">>, 1, Headers0) of true -> Headers3; false when Port =:= 80, not IsSecure -> [{<<"host">>, Host}|Headers3]; diff --git a/src/gun_http2.erl b/src/gun_http2.erl index 1f25e83..cf83970 100644 --- a/src/gun_http2.erl +++ b/src/gun_http2.erl @@ -370,9 +370,9 @@ prepare_headers(EncodeState, Transport, Method, Host0, Port, Path, Headers0) -> lists:keydelete(<<"upgrade">>, 1, Headers0)))))), Headers = [ {<<":method">>, Method}, - {<<":scheme">>, case Transport:secure() of - true -> <<"https">>; - false -> <<"http">> + {<<":scheme">>, case Transport of + gun_tls -> <<"https">>; + gun_tcp -> <<"http">> end}, {<<":authority">>, Authority}, {<<":path">>, Path} @@ -465,17 +465,17 @@ send_data(State=#http2_state{socket=Socket, transport=Transport, opts=Opts, min(RemoteMaxFrameSize, ConfiguredMaxFrameSize) ), case Data of - {sendfile, Offset, Bytes, Path} when Bytes =< MaxSendSize -> - Transport:send(Socket, cow_http2:data_header(StreamID, IsFin, Bytes)), - Transport:sendfile(Socket, Path, Offset, Bytes), - {State#http2_state{local_window=ConnWindow - Bytes}, - Stream#stream{local=IsFin, local_window=StreamWindow - Bytes}}; - {sendfile, Offset, Bytes, Path} -> - Transport:send(Socket, cow_http2:data_header(StreamID, nofin, MaxSendSize)), - Transport:sendfile(Socket, Path, Offset, MaxSendSize), - send_data(State#http2_state{local_window=ConnWindow - MaxSendSize}, - Stream#stream{local_window=StreamWindow - MaxSendSize}, - IsFin, {sendfile, Offset + MaxSendSize, Bytes - MaxSendSize, Path}, In); +% {sendfile, Offset, Bytes, Path} when Bytes =< MaxSendSize -> +% Transport:send(Socket, cow_http2:data_header(StreamID, IsFin, Bytes)), +% Transport:sendfile(Socket, Path, Offset, Bytes), +% {State#http2_state{local_window=ConnWindow - Bytes}, +% Stream#stream{local=IsFin, local_window=StreamWindow - Bytes}}; +% {sendfile, Offset, Bytes, Path} -> +% Transport:send(Socket, cow_http2:data_header(StreamID, nofin, MaxSendSize)), +% Transport:sendfile(Socket, Path, Offset, MaxSendSize), +% send_data(State#http2_state{local_window=ConnWindow - MaxSendSize}, +% Stream#stream{local_window=StreamWindow - MaxSendSize}, +% IsFin, {sendfile, Offset + MaxSendSize, Bytes - MaxSendSize, Path}, In); Iolist0 -> IolistSize = iolist_size(Iolist0), if @@ -500,7 +500,7 @@ send_trailers(State=#http2_state{socket=Socket, transport=Transport, encode_stat queue_data(Stream=#stream{local_buffer=Q0, local_buffer_size=Size0}, IsFin, Data, In) -> DataSize = case Data of - {sendfile, _, Bytes, _} -> Bytes; +% {sendfile, _, Bytes, _} -> Bytes; Iolist -> iolist_size(Iolist) end, Q = queue:In({IsFin, DataSize, Data}, Q0), diff --git a/src/gun_tcp.erl b/src/gun_tcp.erl new file mode 100644 index 0000000..80bc45b --- /dev/null +++ b/src/gun_tcp.erl @@ -0,0 +1,49 @@ +%% Copyright (c) 2011-2018, Loïc Hoguin +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(gun_tcp). + +-export([messages/0]). +-export([connect/4]). +-export([send/2]). +-export([setopts/2]). +-export([sockname/1]). +-export([close/1]). + +messages() -> {tcp, tcp_closed, tcp_error}. + +-spec connect(inet:ip_address() | inet:hostname(), + inet:port_number(), any(), timeout()) + -> {ok, inet:socket()} | {error, atom()}. +connect(Host, Port, Opts, Timeout) when is_integer(Port) -> + gen_tcp:connect(Host, Port, + Opts ++ [binary, {active, false}, {packet, raw}], + Timeout). + +-spec send(inet:socket(), iodata()) -> ok | {error, atom()}. +send(Socket, Packet) -> + gen_tcp:send(Socket, Packet). + +-spec setopts(inet:socket(), list()) -> ok | {error, atom()}. +setopts(Socket, Opts) -> + inet:setopts(Socket, Opts). + +-spec sockname(inet:socket()) + -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. +sockname(Socket) -> + inet:sockname(Socket). + +-spec close(inet:socket()) -> ok. +close(Socket) -> + gen_tcp:close(Socket). diff --git a/src/gun_tls.erl b/src/gun_tls.erl new file mode 100644 index 0000000..8ce46c3 --- /dev/null +++ b/src/gun_tls.erl @@ -0,0 +1,49 @@ +%% Copyright (c) 2011-2018, Loïc Hoguin +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(gun_tls). + +-export([messages/0]). +-export([connect/4]). +-export([send/2]). +-export([setopts/2]). +-export([sockname/1]). +-export([close/1]). + +messages() -> {ssl, ssl_closed, ssl_error}. + +-spec connect(inet:ip_address() | inet:hostname(), + inet:port_number(), any(), timeout()) + -> {ok, inet:socket()} | {error, atom()}. +connect(Host, Port, Opts, Timeout) when is_integer(Port) -> + ssl:connect(Host, Port, + Opts ++ [binary, {active, false}, {packet, raw}], + Timeout). + +-spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}. +send(Socket, Packet) -> + ssl:send(Socket, Packet). + +-spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}. +setopts(Socket, Opts) -> + ssl:setopts(Socket, Opts). + +-spec sockname(ssl:sslsocket()) + -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. +sockname(Socket) -> + ssl:sockname(Socket). + +-spec close(ssl:sslsocket()) -> ok. +close(Socket) -> + ssl:close(Socket). -- cgit v1.2.3