Hugo: Websocket for LiveReload using wrong port if Hugo binds to port 80

Created on 13 Jun 2016  路  13Comments  路  Source: gohugoio/hugo

Let's say I clone this repo:

git clone https://github.com/robertbasic/robertbasic.com-hugo.git

Then start up a docker container on Hugo 0.16:

docker run -ti --rm --name "my-hugo" \
  -p 80:80 -v $(pwd)/robertbasic.com-hugo:/src jojomi/hugo \
  hugo server --watch=true --source="/src" --bind=0.0.0.0 --port=80 --baseURL="/"

Where I have watch on and I'm listening on port 80. When you load the site, you get:

WebSocket connection to 'ws://192.168.51.1:35729/livereload' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED

Which is clearly trying to connect to the hugo server on the wrong port. If you pop the port number up one value:

docker run -ti --rm --name "my-hugo" \
  -p 81:81 -v $(pwd)/robertbasic.com-hugo:/src jojomi/hugo \
  hugo server --watch=true --source="/src" --bind=0.0.0.0 --port=81 --baseURL="/"

It works as expected (from chrome console):

Request URL:ws://192.168.51.1:81/livereload
Request Method:GET
Status Code:101 Switching Protocols

Originally discussed and discovered here: https://discuss.gohugo.io/t/live-reload-unconnectable-in-docker-container/3457/4

Bug

Most helpful comment

@aral I am almost in the same situation as yours. I use Nginx as reverse proxy for Hugo (among others). So, if you or anyone else uses Nginx, this solution works for me with the command hugo serve --liveReloadPort 443. More info on how websockets works in Nginx at https://www.nginx.com/blog/websocket-nginx/ . Here's my Nginx conf...

server {
  listen 443;
  server_name example.com;
    location / {
        proxy_pass http://localhost:1313;
        include proxy.conf;
    }

    location /livereload {
        proxy_pass http://localhost:1313;
        include proxy.conf;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}
# proxy.conf file
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

I hope that helps someone using Nginx as reverse proxy.

All 13 comments

hugo server is a development server. Why do you need it to bind to port 80?

hugo server is a development server. Why do you need it to bind to port 80?

I like typing less stuff in my url bar? "Because."? Does it matter? Why _shouldn't_ I use port 80? There's a config option for it and in certain situations it causes other functionality to fail.

The port I choose for hugo shouldn't matter, but this here bug means it does.


In analogy/joke form:

Patient: Doctor, it hurts every time I lift up my arm!
Doctor: Well don't do that!

Not helpful, doc.

The port I choose for hugo shouldn't matter, but this here bug means it does.

If you could provide a reason other than convenience, the issue might get higher priority, that's all.

This piqued my interest, and I decided to try it with Hugo v0.16 (hugo_0.16-1_armhf.deb) running on my Raspberry Pi 3:

sudo hugo server --bind=0.0.0.0 --baseURL=http://rpi3:80/ --port=80 \
                 --watch=true --theme=shiori --disableLiveReload=false

(I set my home router resolves rpi3 to the IP address of the Ethernet port.) The symptoms are as @chuyskywalker described, i.e. LiveReload not working, and the Chrome console (opened by Ctrl-Shift-J on my notebook running Debian sid (amd64)) shows

WebSocket connection to 'ws://rpi3:35729/livereload' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED

So that answered two questions that I was going to ask @chuyskywalker:

  1. Is it reproducible _outside_ of Docker? Yes.
  2. Is it reproducible with Hugo v0.16? Yes.

I then ran hugo server not on Raspberry Pi 3 but on my own notebook (amd64), and got the same error.

That said, I don't think Hugo ever sets LiveReload to listen at 35729 by default. When hugo server is called without setting any port, it defaults to 1313, and opens only that port AFAIK:

$ sudo netstat -lnp | grep hugo
tcp        0      0 127.0.0.1:1313          0.0.0.0:*               LISTEN      4229/hugo

So, my suspicion would be on the client-side livereload.js. But let's google first, and lo and behold!

Unfortunately, this does not work when using the standard HTTP/HTTPS 80/443 ports because browsers strip the default ports 80/443 from the script element's src attribute such that livereload.js does not see them (tested in Chrome 35 and Firefox 30). Therefore all three following embed methods use the fallback port 35729 instead of 80 or 443:

So, it is Google Chrome and Mozilla Firefox's doing. And it is probably relatively recent too, which explains why it used to work for you, but no longer.

Since it is not Hugo's bug, we can choose to:

  1. Do nothing. (Haha!)
  2. Add a new feature to allow Hugo listen on 35729 port specifically for LiveReload when --port=80 or --port=443 is used.

Probably not too high priority. Any volunteers? :wink:

Actually, based on one of your links, livereload can be told what port to use, ala

<script src="https://localhost:443/livereload.js?port=443"></script>
<script src="https://localhost/livereload.js?port=80"></script>

Seems like it would be sane to inject that into the URL based on the port that Hugo is bound to

I have exactly zero go experience, so this is a WILD shot in the dark, and probably wrong in so very many ways, but...

https://github.com/spf13/hugo/commit/a049d8ee86e64429b8f15fe426812db7521537c5

Actually, based on one of your links, livereload can be told what port to use, ala...

@chuyskywalker Good catch! I missed or misunderstood that, and glad that you came up with a fix! It looks good to me at first glance, though I haven't tested it yet.

Care to make a pull request so more experienced Hugo developers can look at it and eventually merge it? See our https://github.com/spf13/hugo/blob/master/CONTRIBUTING.md guide. Many thanks!

Actually, my code fails. I'm not a go dev and I only have the docker build step to use for testing it -- figured I might get lucky but it didn't work out as expected. I'm pretty sure the logic is something like the above, but I likely won't be the one to fix it.

@dplesca -- I copied https://github.com/dplesca/hugo/commit/8323f694d54a3605a234e5572138025c4ad72491 into my own repo, compiled, and tried it. Tests pass and live reload works on port 80 afterwards, yay!

Thank you @chuyskywalker and @dplesca for your contribution!
Your group effort has been merged as 7e08d23.

Binding LiveReload to the same port Hugo is run from breaks the ability to use a reverse proxy. Ideally, the port we pass to the LiveReload script should be the port that the page is loaded from, not the port that Hugo is run from.

Problem:

  1. Hugo is running on port 1313
  2. There鈥檚 a reverse proxy on 443 that proxies port 1313
  3. Currently, LiveReload is passed port=1313, thereby breaking live reload.

Proposed solution:

Write the port dynamically on the client:

  1. If location.port is set, use that
  2. If not, and loaded via https protocol, pass default port 443
  3. If not, and loaded via http protocol, pass default port 80

Thoughts?

@aral I am almost in the same situation as yours. I use Nginx as reverse proxy for Hugo (among others). So, if you or anyone else uses Nginx, this solution works for me with the command hugo serve --liveReloadPort 443. More info on how websockets works in Nginx at https://www.nginx.com/blog/websocket-nginx/ . Here's my Nginx conf...

server {
  listen 443;
  server_name example.com;
    location / {
        proxy_pass http://localhost:1313;
        include proxy.conf;
    }

    location /livereload {
        proxy_pass http://localhost:1313;
        include proxy.conf;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}
# proxy.conf file
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

I hope that helps someone using Nginx as reverse proxy.

@pothi Thanks for this. I didn鈥檛 know there was a --liveReloadPort option so I鈥檇 added a hack to my proxy server to rewrite the URLs for Hugo. I can now remove the hack. Much appreciated :)

Was this page helpful?
0 / 5 - 0 ratings