This library wraps gen_tcp, gen_udp and gen_sctp, ssl and implements
websockets and socks.
Sockets2 is a fork from the zombie project at: https://github.com/meh/elixir-socket updated for OTP20+ and with PRs from other forks merged.
In your mix.exs file
defp deps do
[
# ...
{:socket2, "~> 2.1.3"},
# ...
]
endThen run mix deps.get to install
defmodule HTTP do
def get(uri) when is_binary(uri) or is_list(uri) do
get(URI.parse(uri))
end
def get(%URI{host: host, port: port, path: path}) do
sock = Socket.TCP.connect!(host, port, packet: :line)
sock |> Socket.Stream.send!("GET #{path || "/"} HTTP/1.1\r\nHost: #{host}\r\n\r\n")
[_, code, text] = Regex.run(~r"HTTP/1.1 (.*?) (.*?)\s*$", sock |> Socket.Stream.recv!())
headers = headers([], sock) |> Enum.into(%{})
sock |> Socket.packet!(:raw)
body = sock |> Socket.Stream.recv!(String.to_integer(headers["Content-Length"]))
{{String.to_integer(code), text}, headers, body}
end
defp headers(acc, sock) do
case sock |> Socket.Stream.recv!() do
"\r\n" ->
acc
line ->
[_, name, value] = Regex.run(~r/^(.*?):\s*(.*?)\s*$/, line)
headers([{name, value} | acc], sock)
end
end
endYou will need to generate certificates to make it work. Here are some command lines using openssl
$ mkdir certs
$ cd certs
$ openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:4096
$ openssl req -new -x509 -key key.pem -out cert.pem -days 1000 -sha384Then here is a simple example of connecting to an SSL server.
ipaddr = {1, 2, 3, 5}
port = 5061
ssl_options = [
cert: [path: "certs/certificate.pem"],
key: [ path: "certs/private_key.pem" ],
verify: false, # Deactivate cert verification
versions: [:"tlsv1.2"], # Force TLS 1.2
ciphers: [~c"AES256-GCM-SHA384"] # Add secure cipher
]
sock = Socket.SSL.connect!(ipaddr, port, ssl_options)
Socket.Stream.send(sock, "Hello World!")socket = Socket.Web.connect!("echo.websocket.org")
socket |> Socket.Web.send!({ :text, "test" })
socket |> Socket.Web.recv!() # => {:text, "test"}In order to connect to a TLS websocket, use the secure: true option:
socket = Socket.Web.connect!("echo.websocket.org", secure: true)Make sure that you also pass SSL options required to setup a proper SSL connection along the secure: true option. See SSL client connection above.
The connect! function also accepts other parameters, most notably the path parameter, which is used when the websocket server endpoint exists on a path below the domain ie. "example.com/websocket":
socket = Socket.Web.connect!("example.com", path: "/websocket")Note that websocket servers send ping messages. A pong reply from your client tells the server to keep the connection open and to send more data. If your client doesn't send a pong reply then the server will close the connection. Here's an example of how to get get both the data you want and reply to a server's pings:
socket = Socket.Web.connect!("echo.websocket.org")
case socket |> Socket.Web.recv!() do
{:text, data} ->
# process data
{:ping, _ } ->
socket |> Socket.Web.send!({:pong, ""})
endNow WebSocket also supports an active mode when you can receive data as Erlang messages. It also handles keepalive automatically.
Data is received as { :web, socket, data } messages and socket disconnection notification as : { :web_closed, socket, reason }
In order to use this function, pass the mode: active option. You can also specify the PID of the process receiving the messages using the process: <pid> option.
socket = Socket.Web.connect!("echo.websocket.org", [ mode: :active, process: self() ] )
Socket.Web.send!(socket, { :text, "Hello" })
receive do
{ :web, _socket, data } -> IO.puts("Received: " <> data)
{ :web_closed, _socket, reason } -> IO.puts("Active Web socket has closed: #{inspect(reason)}")
endThe max_size option bounds, in bytes, what the peer may send in one message. It is checked against the length the peer announces, before the payload is read. It can be given to connect!, to listen! (every accepted client inherits it), to accept!, or to a single recv call.
socket = Socket.Web.connect!("echo.websocket.org", max_size: 1_048_576)A message over the limit fails the connection: the peer is sent a close frame with status 1009, the socket is closed, and recv returns {:error, :message_too_big}. In active mode the owner receives { :web_closed, socket, :message_too_big } instead, and the limit binds the whole reassembled message, fragments included.
server = Socket.Web.listen!(80)
client = server |> Socket.Web.accept!()
# here you can verify if you want to accept the request or not, call
# `Socket.Web.close!` if you don't want to accept it, or else call
# `Socket.Web.accept!`
client |> Socket.Web.accept!()
# echo the first message
client |> Socket.Web.send!(client |> Socket.Web.recv!())accept! answers a request that is not a websocket upgrade with a 400, one that asks for another protocol version with a 426, and one with more than 100 header lines with a 431, then closes the socket. A header line over 8 KiB closes the socket without a status. A timeout option bounds each step of the handshake.