Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rfc7230: Port number in Host header when not std. #132

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
293e4f8
Add tcp_options
Dec 20, 2021
8f89622
Rename tcp_options to socket_options
Jan 3, 2022
44792a8
Remove @tag :skip on the SSL tests
Jan 3, 2022
a528bf8
When host is IP address string, :gen_tcp.open_socket will parse it in…
Jul 4, 2022
571ffbf
Update the echo server URL
al2o3cr Nov 20, 2022
1e4785e
Merge pull request #1 from henkoch/master
haljin Nov 21, 2022
4cd1af0
Merge pull request #2 from henkoch/adding_tcp_options
haljin Nov 21, 2022
d8f1411
Merge branch 'Azolo:master' into master
haljin Jan 18, 2023
08c250e
Rebase properly
haljin Jan 18, 2023
f5408ae
Remove then to conform to older Elixir
haljin Jan 18, 2023
a649899
convert bxor references to Bitwise library
Feb 20, 2023
6072d49
Update README.md
marpo60 Jun 26, 2023
0a474b0
Update dependencies
wkirschbaum Jul 6, 2024
7bc5e48
Update charlists to use sigils
wkirschbaum Jul 6, 2024
63e15e4
Fix deprecated Cowboy usage
wkirschbaum Jul 6, 2024
cbab09c
Remove unused mix deps
wkirschbaum Jul 6, 2024
e6c58d8
Require Elixir 1.11+
wkirschbaum Jul 6, 2024
dea4d5c
rfc7230: Port number in Host header when not std.
dominicletz Nov 7, 2024
ace82e6
Remove travis
dominicletz Dec 30, 2024
e05f8e6
Add github ci
dominicletz Dec 30, 2024
4b4c719
Add ci badge to readme
dominicletz Dec 30, 2024
b2f9dc4
mix format --migrate
dominicletz Dec 30, 2024
f5b850b
Remove mix.lock
dominicletz Dec 30, 2024
56f8ad4
Add mix.lint alias
dominicletz Dec 30, 2024
7cd57f8
Lint in ci
dominicletz Dec 30, 2024
60ee57a
Added dialyzer to linting
dominicletz Dec 30, 2024
7738113
Change package name to websockex_wt
dominicletz Dec 30, 2024
5b858f8
v0.4.4
dominicletz Dec 30, 2024
6503919
Fixup readme
dominicletz Dec 30, 2024
6b56597
Merge remote-tracking branch 'wkirschbaum/master'
dominicletz Dec 30, 2024
50336de
Merge remote-tracking branch 'marpo60/patch-1'
dominicletz Dec 30, 2024
6007df8
Merge remote-tracking branch 'cortfritz/feature/reference-Bitwise'
dominicletz Dec 30, 2024
54fb940
Removed unused import
dominicletz Dec 30, 2024
2a05221
Merge remote-tracking branch 'haljin/refurbish'
dominicletz Dec 30, 2024
1ac1c10
Fix unused option
dominicletz Dec 30, 2024
c6e45d9
Use warnings as errors in lint compilation
dominicletz Dec 30, 2024
57cd27d
Merge remote-tracking branch 'al2o3cr/patch-1'
dominicletz Dec 30, 2024
4dc6b5b
v0.5.0
dominicletz Dec 30, 2024
f36d391
Updated version
dominicletz Dec 30, 2024
23b1b8a
Removed note about elixir 1.4
dominicletz Dec 30, 2024
dc71504
Updated docs link
dominicletz Dec 30, 2024
3b1c664
Fixed deps() example
dominicletz Dec 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# .credo.exs
%{
configs: [
%{
name: "default",
checks: %{
disabled: [
# this means that `TabsOrSpaces` will not run
{Credo.Check.Warning.SpecWithStruct, []},
]
}
# files etc.
}
]
}
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: "CI"
on: ["push", "pull_request"]

jobs:
lint_and_test:
name: "Lint and test"
runs-on: "ubuntu-latest"
steps:
- uses: erlef/setup-beam@v1
with:
otp-version: latest
elixir-version: latest

- uses: actions/checkout@v1
- run: |
mix local.hex --force
mix deps.get
mix lint
mix test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# This is a library don't include the mix.lock
mix.lock
# The directory Mix will write compiled artifacts to.
/_build

Expand Down
14 changes: 0 additions & 14 deletions .travis.yml

This file was deleted.

18 changes: 4 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# WebSockex

[![Build Status](https://travis-ci.org/Azolo/websockex.svg?branch=master)](https://travis-ci.org/Azolo/websockex)
![Build](https://github.com/dominicletz/websockex/actions/workflows/ci.yml/badge.svg)

An Elixir Websocket Client.

Expand Down Expand Up @@ -34,17 +34,7 @@ Add `websockex` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[{:websockex, "~> 0.4.3"}]
end
```

### With Elixir releases prior to version 1.4

Ensure `websockex` is started before your application:

```elixir
def application do
[applications: [:websockex]]
[{:websockex, "~> 0.5.0", hex: :websockex_wt}]
end
```

Expand Down Expand Up @@ -126,7 +116,7 @@ For all these events, the measurements is `%{time: System.system_time()}` and th
Usually you'll want to negotiate and handle any abnormal close event or error leading to it, as per WS Spec, but there might be cases where you simply want the socket to exit as if it was a normal event, even if it was abruptly closed or another exception was raised. In those cases you can define the terminate callback and return `exit(:normal)` from it.
```elixir
def terminate(reason, state) do
IO.puts(\nSocket Terminating:\n#{inspect reason}\n\n#{inspect state}\n")
IO.puts("\nSocket Terminating:\n#{inspect reason}\n\n#{inspect state}\n")
exit(:normal)
end
```
Expand Down Expand Up @@ -173,4 +163,4 @@ iex> EchoClient.echo(pid, "Hi")
```

[special_process]: http://erlang.org/doc/design_principles/spec_proc.html
[docs]: https://hexdocs.pm/websockex
[docs]: https://hexdocs.pm/websockex_wt
2 changes: 1 addition & 1 deletion examples/echo_client.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule EchoClient do
require Logger

def start_link(opts \\ []) do
WebSockex.start_link("wss://echo.websocket.org/?encoding=text", __MODULE__, :fake_state, opts)
WebSockex.start_link("wss://echo.websocket.events/?encoding=text", __MODULE__, :fake_state, opts)
end

@spec echo(pid, String.t) :: :ok
Expand Down
2 changes: 1 addition & 1 deletion lib/websockex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ defmodule WebSockex do
module_misc = module_status(opt, state.module, pdict, state.module_state)

[
{:header, 'Status for WebSockex process #{inspect(self())}'},
{:header, ~c"Status for WebSockex process #{inspect(self())}"},
{:data,
[
{"Status", sys_state},
Expand Down
4 changes: 2 additions & 2 deletions lib/websockex/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ defmodule WebSockex.Application do
Sets the `ws` and `wss` default ports for the `URI` module.
"""
def start(_type, _args) do
unless URI.default_port("ws"), do: URI.default_port("ws", 80)
unless URI.default_port("wss"), do: URI.default_port("wss", 443)
if !URI.default_port("ws"), do: URI.default_port("ws", 80)
if !URI.default_port("wss"), do: URI.default_port("wss", 443)

# Start an empty supervisor for OTP
Supervisor.start_link([], strategy: :one_for_one, name: WebSockex.Supervisor)
Expand Down
63 changes: 56 additions & 7 deletions lib/websockex/conn.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ defmodule WebSockex.Conn do
cacerts: nil,
insecure: true,
resp_headers: [],
ssl_options: nil
ssl_options: nil,
socket_options: nil

@type socket :: :gen_tcp.socket() | :ssl.sslsocket()
@type header :: {field :: String.t(), value :: String.t()}
Expand All @@ -47,6 +48,7 @@ defmodule WebSockex.Conn do
- `:socket_recv_timeout` - Timeout in ms for receiving a HTTP response header
from socket, default #{@socket_recv_timeout_default} ms.
- `:ssl_options` - extra options for an SSL connection
- `:socket_options` - extra options for the TCP part of the connection

[public_key]: http://erlang.org/doc/apps/public_key/using_public_key.html
"""
Expand All @@ -57,6 +59,7 @@ defmodule WebSockex.Conn do
| {:socket_connect_timeout, non_neg_integer}
| {:socket_recv_timeout, non_neg_integer}
| {:ssl_options, [:ssl.tls_client_option()]}
| {:socket_options, [:gen_tcp.option()]}

@type t :: %__MODULE__{
conn_mod: :gen_tcp | :ssl,
Expand Down Expand Up @@ -95,7 +98,8 @@ defmodule WebSockex.Conn do
socket_connect_timeout:
Keyword.get(opts, :socket_connect_timeout, @socket_connect_timeout_default),
socket_recv_timeout: Keyword.get(opts, :socket_recv_timeout, @socket_recv_timeout_default),
ssl_options: Keyword.get(opts, :ssl_options, nil)
ssl_options: Keyword.get(opts, :ssl_options, nil),
socket_options: Keyword.get(opts, :socket_options, nil)
}
end

Expand Down Expand Up @@ -132,6 +136,26 @@ defmodule WebSockex.Conn do
end
end

@doc """
Parses a URI host
Host can be "x.y.z.t" or "some.name.domain". If "x.y.z.t", the function
will return a valid :inet.ip_address() which __MODULE__.open_socket
accepts. This will prevent extra DNS operations which can time out
in some contexts
"""
@spec parse_host(String.t()) :: charlist() | :inet.ip_address()
def parse_host(host) do
parsed =
host
|> to_charlist()
|> :inet.parse_address()

case parsed do
{:error, :einval} -> to_charlist(host)
{:ok, addr} -> addr
end
end

@doc """
Sends data using the `conn_mod` module.
"""
Expand All @@ -151,9 +175,9 @@ defmodule WebSockex.Conn do

def open_socket(%{conn_mod: :gen_tcp} = conn) do
case :gen_tcp.connect(
String.to_charlist(conn.host),
parse_host(conn.host),
conn.port,
[:binary, active: false, packet: 0],
socket_connection_options(conn),
conn.socket_connect_timeout
) do
{:ok, socket} ->
Expand All @@ -166,7 +190,7 @@ defmodule WebSockex.Conn do

def open_socket(%{conn_mod: :ssl} = conn) do
case :ssl.connect(
String.to_charlist(conn.host),
parse_host(conn.host),
conn.port,
ssl_connection_options(conn),
conn.socket_connect_timeout
Expand Down Expand Up @@ -201,9 +225,16 @@ defmodule WebSockex.Conn do
"""
@spec build_request(__MODULE__.t(), key :: String.t()) :: {:ok, String.t()}
def build_request(conn, key) do
host =
if conn.port in [80, 443] do
conn.host
else
"#{conn.host}:#{conn.port}"
end

headers =
([
{"Host", conn.host},
{"Host", host},
{"Connection", "Upgrade"},
{"Upgrade", "websocket"},
{"Sec-WebSocket-Version", "13"},
Expand Down Expand Up @@ -231,7 +262,7 @@ defmodule WebSockex.Conn do
with {:ok, buffer} <- wait_for_response(conn),
{:ok, headers, buffer} <- decode_response(buffer) do
# Send excess buffer back to the process
unless buffer == "" do
if buffer != "" do
send(owner_pid, {transport(conn.conn_mod), conn.socket, buffer})
end

Expand Down Expand Up @@ -317,6 +348,24 @@ defmodule WebSockex.Conn do
end
end

defp minimal_socket_connection_options() do
[
mode: :binary,
active: false,
packet: 0
]
end

defp socket_connection_options(%{socket_options: socket_options})
when not is_nil(socket_options) do
minimal_socket_connection_options()
|> Keyword.merge(socket_options)
end

defp socket_connection_options(%{socket_options: _socket_options}) do
minimal_socket_connection_options()
end

# Crazy SSL Stuff (It will be normal SSL stuff when I figure out Erlang's ssl)

defp ssl_connection_options(%{ssl_options: ssl_options}) when not is_nil(ssl_options) do
Expand Down
20 changes: 5 additions & 15 deletions lib/websockex/errors.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ defmodule WebSockex.RequestError do
defexception [:code, :message]

def message(%__MODULE__{code: code, message: message}) do
"Didn't get a proper response from the server. The response was: #{inspect(code)} #{
inspect(message)
}"
"Didn't get a proper response from the server. The response was: #{inspect(code)} #{inspect(message)}"
end
end

Expand Down Expand Up @@ -56,9 +54,7 @@ defmodule WebSockex.BadResponseError do
defexception [:response, :module, :function, :args]

def message(%__MODULE__{} = error) do
"Bad Response: Got #{inspect(error.response)} from #{
inspect(Exception.format_mfa(error.module, error.function, error.args))
}"
"Bad Response: Got #{inspect(error.response)} from #{inspect(Exception.format_mfa(error.module, error.function, error.args))}"
end
end

Expand All @@ -70,19 +66,15 @@ defmodule WebSockex.FrameError do
end

def message(%__MODULE__{reason: :control_frame_too_large} = exception) do
"Control Frame Too Large: Control Frames Can't Be Larger Than 125 Bytes\nbuffer: #{
exception.buffer
}"
"Control Frame Too Large: Control Frames Can't Be Larger Than 125 Bytes\nbuffer: #{exception.buffer}"
end

def message(%__MODULE__{reason: :invalid_utf8} = exception) do
"Invalid UTF-8: Text and Close frames must have UTF-8 payloads.\nbuffer: #{exception.buffer}"
end

def message(%__MODULE__{reason: :invalid_close_code} = exception) do
"Invalid Close Code: Close Codes must be in range of 1000 through 4999\nbuffer: #{
exception.buffer
}"
"Invalid Close Code: Close Codes must be in range of 1000 through 4999\nbuffer: #{exception.buffer}"
end

def message(%__MODULE__{} = exception) do
Expand All @@ -103,9 +95,7 @@ defmodule WebSockex.FrameEncodeError do
def message(%__MODULE__{reason: :close_code_out_of_range} = error) do
"""
Close Code Out of Range: Close code must be between 1000-4999.
Frame: {#{inspect(error.frame_type)}, #{inspect(error.close_code)}, #{
inspect(error.frame_payload)
}}
Frame: {#{inspect(error.frame_type)}, #{inspect(error.close_code)}, #{inspect(error.frame_payload)}}
"""
end
end
Expand Down
6 changes: 2 additions & 4 deletions lib/websockex/frame.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ defmodule WebSockex.Frame do
Functions for parsing and encoding frames.
"""

import Bitwise

@type opcode :: :text | :binary | :close | :ping | :pong
@type close_code :: 1000..4999

Expand Down Expand Up @@ -391,13 +389,13 @@ defmodule WebSockex.Frame do

for x <- 1..3 do
defp mask(<<key::8*unquote(x), _::binary>>, <<part::8*unquote(x)>>, acc) do
masked = bxor(part, key)
masked = Bitwise.bxor(part, key)
<<acc::binary, masked::8*unquote(x)>>
end
end

defp mask(<<key::32>> = key_bin, <<part::8*4, rest::binary>>, acc) do
masked = bxor(part, key)
masked = Bitwise.bxor(part, key)
mask(key_bin, rest, <<acc::binary, masked::8*4>>)
end
end
2 changes: 1 addition & 1 deletion lib/websockex/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ defmodule WebSockex.Utils do
:sys.debug_options(opts)
catch
_, _ ->
:error_logger.format('~p: ignoring bad debug options ~p~n', [name, opts])
:error_logger.format(~c"~p: ignoring bad debug options ~p~n", [name, opts])
[]
end

Expand Down
Loading