Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ main
* Require `ember-cli-rails-assets >= 0.9.0`, which serves the
`include_ember_script_tags` startup tags from Vite's development server
when the application is served from it
* Add an `origin` option to `dev_server`, separating the origin the browser
loads assets from, from the address the development server binds to — for
containers, where the server must bind to `0.0.0.0` but browsers refuse
that address as an origin

0.13.0.beta1
------
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,26 @@ If something is already listening on the configured `host` and `port`,
possible to run `npm start` yourself, on a port the initializer names, and have
Rails serve the application from it.

`host` is both the address the development server binds to and — together with
`port` — the origin the browser loads the application's modules from. When the
two differ, configure the browser-facing origin separately with `origin`. The
typical case is Rails running in a container: the development server must bind
to `0.0.0.0` to be reachable through the container's published port, but
browsers refuse to connect to `http://0.0.0.0`:

```rb
EmberCli.configure do |c|
c.app :frontend, dev_server: {
host: "0.0.0.0",
port: 4200,
origin: "http://localhost:4200",
}
end
```

With `host: "0.0.0.0"`, Rails itself reaches the development server through the
loopback interface.

The development server's output is written to
`log/ember-<app>.<environment>.log`.

Expand Down
5 changes: 5 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ following differences in how `ember-cli-rails` treats it:
end
```

When Rails runs in a container and the browser outside of it, bind the
development server to every interface and name the browser-facing origin
separately: `dev_server: { host: "0.0.0.0", port: 4200, origin:
"http://localhost:4200" }`.

To opt out of the development server, disable it:

```rb
Expand Down
22 changes: 19 additions & 3 deletions lib/ember_cli/dev_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ def timeout
@timeout ||= option(:timeout) { DEFAULT_TIMEOUT }.to_f
end

# The origin the browser loads the application's assets from. It defaults
# to the address the development server binds to, but can be configured
# separately for setups where the two differ — a development server bound
# to `0.0.0.0` inside a container, reached by the browser through a
# published port, for instance.
def origin
"http://#{host}:#{port}"
@origin ||= option(:origin) { "http://#{host}:#{port}" }.to_s.chomp("/")
end

# Boots the development server unless something is already listening on
Expand Down Expand Up @@ -96,7 +101,7 @@ def option(key)
def request(request_class, path, headers)
start

uri = URI.join(origin, path)
uri = URI.join("http://#{connect_host}:#{port}", path)
# Pass `nil` as the proxy address so that a `http_proxy` environment
# variable never routes requests for the local server through a proxy.
Net::HTTP.start(uri.hostname, uri.port, nil, read_timeout: timeout) do |http|
Expand All @@ -112,8 +117,19 @@ def request(request_class, path, headers)
MSG
end

# The address Rails connects to the development server on. `0.0.0.0` asks
# the server to listen on every interface, but is not an address to
# connect to, so requests go to the loopback interface instead.
def connect_host
if host == "0.0.0.0"
"127.0.0.1"
else
host
end
end

def listening?
Socket.tcp(host, port, connect_timeout: CONNECT_TIMEOUT, &:close)
Socket.tcp(connect_host, port, connect_timeout: CONNECT_TIMEOUT, &:close)

true
rescue SystemCallError, IOError
Expand Down
49 changes: 49 additions & 0 deletions spec/lib/ember_cli/dev_server_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@

expect(origin).to eq("http://0.0.0.0:1234")
end

it "honors a configured origin" do
dev_server = build_dev_server(
options: { host: "0.0.0.0", port: 4200, origin: "http://localhost:4200" },
)

origin = dev_server.origin

expect(origin).to eq("http://localhost:4200")
end

it "strips a trailing slash from a configured origin" do
dev_server = build_dev_server(options: { origin: "http://localhost:4200/" })

origin = dev_server.origin

expect(origin).to eq("http://localhost:4200")
end
end

describe "#port" do
Expand Down Expand Up @@ -64,6 +82,21 @@
expect(shell.started_with).to be_nil
end

it "connects through the loopback interface when bound to every interface" do
server = null_server
server.listen("127.0.0.1", 0)
shell = FakeShell.new
dev_server = build_dev_server(
shell: shell,
options: { host: "0.0.0.0", port: server.port },
)

started = dev_server.start

expect(started).to be true
expect(shell.started_with).to be_nil
end

it "raises when the development server exits before it listens" do
shell = FakeShell.new(running: false)
dev_server = build_dev_server(shell: shell)
Expand Down Expand Up @@ -120,6 +153,22 @@
expect(server.requested_paths).to eq(["/assets/logo.png?v=1"])
end

it "requests the development server itself, not the configured origin" do
server = null_server
server.listen("127.0.0.1", 0, body: "ok")
dev_server = build_dev_server(
options: {
host: "0.0.0.0",
port: server.port,
origin: "http://origin.invalid:9999",
},
)

response = dev_server.get("/")

expect(response.body).to eq("ok")
end

it "ignores a configured HTTP proxy" do
server = null_server
server.listen("127.0.0.1", 0, body: "ok")
Expand Down
Loading