code-server: [Bug]: Port forwarding with suburl makes the forwarded links corrupted .

Is there an existing issue for this?

OS/Web Information

  • Web Browser: Chrome (Version 98.0.4758.102) & Firefox (Version 102.3.0esr )
  • Local OS: Debian
  • Remote OS: Ubuntu
  • Remote Architecture: arm64
  • code-server --version: 4.8.3 977b853a1e162ab583aed64b1322d1515c57728c with Code 1.72.1

Steps to Reproduce

  1. Open code-server
  2. Run jupyter notebook in terminal , and forward the port (should be forwarded automatically)
  3. Open the forwarded url

Expected

Jupyter notebook shows up a correct UI and works without problems.

Actual

The jupyter notebook showed up with a unformatted UI (because css and js files are requested with a corrupted URL, only the https://192.168.0.114:28080/proxy/8888/login?next=%2Ftree is requested correctly) if logging in to the notebook it redirected me to https://192.168.0.114:28080/?next=/tree , which is obviously wrong (code-server is serving there) eg , for the logo.png , it requested for https://192.168.0.114:28080/static/base/images/logo.png?v=<a long string> , but it should request for https://192.168.0.114:28080/proxy/8888/static/base/images/logo.png?v=<a long string>

Logs

Logs
[IPC Library: Pty Host] DEBUG CommandDetectionCapability#handleCommandExecuted 0 1
[IPC Library: Pty Host] DEBUG CommandDetectionCapability#setCommandLine jupyter notebook
[IPC Library: Pty Host] TRACE IPty#onData [I 15:06:55.184 NotebookApp] Serving notebooks from local directory: /home/ubuntu/workspace

[IPC Library: Pty Host] TRACE IPty#onData [I 15:06:55.184 NotebookApp] Jupyter Notebook 6.5.2 is running at:
[I 15:06:55.184 NotebookApp] http://localhost:8888/?token=d5e6bd5bb2af22923a05edf7e2bb29171e26a5ea21988ac8
[I 15:06:55.184 NotebookApp]  or http://127.0.0.1:8888/?token=d5e6bd5bb2af22923a05edf7e2bb29171e26a5ea21988ac8
[I 15:06:55.184 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).

[IPC Library: Pty Host] TRACE IPty#onData [C 15:06:55.197 NotebookApp] 

    To access the notebook, open this file in a browser:
        file:///home/ubuntu/.local/share/jupyter/runtime/nbserver-13720-open.html
    Or copy and paste one of these URLs:
        http://localhost:8888/?token=d5e6bd5bb2af22923a05edf7e2bb29171e26a5ea21988ac8
     or http://127.0.0.1:8888/?token=d5e6bd5bb2af22923a05edf7e2bb29171e26a5ea21988ac8

[IPC Library: Pty Host] DEBUG ChildProcessMonitor: Has child processes changed true
[IPC Library: Pty Host] TRACE IPty#onData [I 15:07:03.317 NotebookApp] 302 GET / (127.0.0.1) 1.440000ms

[IPC Library: Pty Host] TRACE IPty#onData [I 15:07:03.407 NotebookApp] 302 GET /tree (127.0.0.1) 1.770000ms

[15:07:15] [<unknown>][29df24bf][ManagementConnection] The client has disconnected, will wait for reconnection 3h before disposing...
[15:07:17] [<unknown>][29df24bf][ManagementConnection] Another client has connected, will shorten the wait for reconnection 5m before disposing...
[15:07:17] [<unknown>][3473a0f6][ExtensionHostConnection] - startParams language: en
[15:07:17] [<unknown>][3473a0f6][ExtensionHostConnection] - startParams env: {"VSCODE_PROXY_URI":"https://192.168.0.114:28080/proxy/{{port}}/"}
[15:07:17] [<unknown>][3473a0f6][ExtensionHostConnection] The client has reconnected.
[15:07:17] [<unknown>][29df24bf][ManagementConnection] The client has reconnected.
[2022-11-18T15:07:21.474Z] debug 2 active connections
Trace: [2022-11-18T15:07:21.477Z] trace heartbeat
    at doLog (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:57:28)
    at ServerFormatter.doWrite (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:200:20)
    at ServerFormatter.write (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:119:14)
    at Logger.handle (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:339:25)
    at Logger.trace (/home/ubuntu/code/code-server-4.8.3-linux-arm64/node_modules/@coder/logger/out/logger.js:268:14)
    at Heart.<anonymous> (/home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:41:29)
    at Generator.next (<anonymous>)
    at /home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:4:12)
    at Heart.beat (/home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:37:16)
    at /home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:76:17
    at Generator.next (<anonymous>)
    at fulfilled (/home/ubuntu/code/code-server-4.8.3-linux-arm64/out/node/heart.js:5:58)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

Screenshot/Video

Behavior

Run jupyter: image Forward port: image Access it: image

Browser console output

image

Explaination

The correct URL: image The corrupted URL: image

Does this issue happen in VS Code or GitHub Codespaces?

  • I cannot reproduce this in VS Code.
  • I cannot reproduce this in GitHub Codespaces.

Are you accessing code-server over HTTPS?

  • I am using HTTPS.

Notes

Code-server is running on a lxc container , it exposed the port 8888 to the server host , and I used iptables(iptables -t nat -A PREROUTING -p tcp --dport 28080 -j DNAT --to-destination 10.154.46.4:8080 , where 28080 is the server exposed port , and 10.154.46.4:8080 is the container exposed port). It is using a self-signed certificate with cert and cert-key params in the config file (not serving through nginx/caddy)

Also , this bug makes the pdf viewer from LaTeX workshop not working properly.

Is it because of relative path ? But jupyter and LaTeX workshop aren’t my project , i don’t know where I can make changes. Subdomains may work , but I don’t have a domain for my server.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 40 (15 by maintainers)

Most upvoted comments

@code-asher @jsjoeio Well , I tried a new way today . And , it worked !

Here is the nginx configuration.

Configuration
server {
    listen 28081 ssl;
    server_name 192.168.0.114;
    server_name raspberrypi;


    ssl_certificate "/path/to/mycert.crt";
    ssl_certificate_key "/path/to/mycert.key";

    client_max_body_size 50M;
    location ~* ^/proxy/(?<proxy_port>[^\n\r\/]+)(?<proxy_path>.*)$ {
	default_type text/html;
    	if ($proxy_port !~* ^([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-5][0-9][0-9][0-9][0-9]|6[0-5][0-5][0-3][0-5])$) {
	    add_header Content-Type 'text/html; charset=utf-8';
	    return 501 'Illegal port ${proxy_port}';
	}
	if ($proxy_path = ''){
	    set $proxy_path '/'; #force to use relative path
	}
	add_header Set-Cookie 'proxy_port=$proxy_port; Path=/';
	proxy_http_version 1.1;
        #proxy_ssl_server_name on;
        proxy_pass http://127.0.0.1:$proxy_port$proxy_path;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Accept-Encoding "";
        proxy_buffering off;
        #websocket
	set $to_upgrade "";
	set $to_connection "";
	if ($http_upgrade = "websocket"){
	    set $to_upgrade "websocket";
	    set $to_connection "Upgrade";
	}
	proxy_set_header Upgrade $to_upgrade;
	proxy_set_header Connection $to_connection;
	proxy_read_timeout 86400;
    }
    location / {
    	default_type text/html;
	if ($cookie_proxy_port = ''){
	    return 404 'Nothing here';
	}
	proxy_http_version 1.1;
	proxy_pass http://127.0.0.1:$cookie_proxy_port;
	proxy_set_header Host $http_host;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header X-Real_IP $remote_addr;
	proxy_set_header User-Agent $http_user_agent;
	proxy_set_header Accept-Encoding "";
	proxy_buffering off;
	#websocket
	set $to_upgrade "";
	set $to_connection "";
	if ($http_upgrade = "websocket"){
	    set $to_upgrade "websocket";
	    set $to_connection "Upgrade";
	}
	proxy_set_header Upgrade $to_upgrade;
	proxy_set_header Connection $to_connection;
	proxy_read_timeout 86400;
    }
}

When accessing https://server-host.com/proxy/{port} nginx will set proxy_port={port} to the client cookies. And if the client requests for https://server-host.com/path/to/the/resource with the cookie (proxy_port={port}), nginx will proxy_pass it to http://127.0.0.1:{port}/path/to/the/resource so the resource can be loaded correctly. Also nginx will check if the connection is a websocket , and if it is nginx will automatically configure it . (So it’s able to connect to jupyter kernel through nginx)

What I have tested :

  • Jupyter notebook . (Work)
  • LaTeX-workshop view pdf in browser . (Work)
Screenshots

image image

Yeah I think for advanced proxy configuration it might be better to set up NGINX or Caddy instead of using code-server’s built-in.

Also if all you are looking for is to avoid stripping the base path then we have another proxy endpoint /absproxy for that so you could set VSCODE_PROXY_URI=https://my-domain.com/absproxy/{{port}}. It would be cool to have a way to choose between /proxy and /absproxy from the UI.

Or something like a “help me” button that triggers a notification or opens documentation?

I like that! We could add a doc and then in the message that pops up about the forwarded port, add a link below and link to the docs.

I’m having the same browser output when I try using jupyter-lab as well. I’m using code-server in a docker container behind a reverse proxy (caddy). I noticed when I prepend the path of the 404 requests with the path ‘/proxy/8888/’ it fetches the desired resource successfully. In my case it looks like because code-server’s proxy strips the ‘/proxy/<port>/’ portion of the path it fails.

I opened an issue: https://github.com/coder/code-server/issues/6770

Not sure I can get to it any time soon, but happy to take pull requests.

Does this mean code-server doesn’t support any params like base_url […] because it probably uses relative URLs instead of absolute URLs

Yup, exactly this.

Are you able to host at a subdomain instead? Something like code-server.domain.tld.

many famous apps (including jupyter server, dash server, etc) do not use relative URLs (which itself is probably not a good implementation IMHO)

100% agree.

I think it would be reasonable to add a --base-path flag that will be prepended to all absproxy requests.

@jsjoeio thanks for the reply.

I did set the base as base: "/absproxy/{port}/", in the vite.config.js. at the moment i manually add the /absproxy/{port}/ into the url for it to work.

For the authorization, i try your suggestion.

@qianchd Just tested. Here’s my nginx configuration

Configuration
server {
    listen 28081 ssl;
    server_name 192.168.0.114;
    server_name raspberrypi;


    ssl_certificate "/path/to/certificate.crt";
    ssl_certificate_key "/path/to/certificate.key";

    client_max_body_size 50M;
    location /code-server/ {
    	proxy_pass http://localhost:8080/;
	proxy_set_header Host $host;
	proxy_set_header Upgrade $http_upgrade;
	proxy_set_header Connection upgrade;
	proxy_set_header Accept-Encoding gzip;

    }
    location ~* ^/proxy/(?<proxy_port>[^\n\r\/]+)(?<proxy_path>.*)$ {
	default_type text/html;
    	if ($proxy_port !~* ^([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-5][0-9][0-9][0-9][0-9]|6[0-5][0-5][0-3][0-5])$) {
	    add_header Content-Type 'text/html; charset=utf-8';
	    return 501 'Illegal port ${proxy_port}';
	}
	if ($proxy_path = ''){
	    set $proxy_path '/'; #force to use relative path
	}
	add_header Set-Cookie 'proxy_port=$proxy_port; Path=/';
	proxy_http_version 1.1;
        #proxy_ssl_server_name on;
        proxy_pass http://127.0.0.1:$proxy_port$proxy_path;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header User-Agent $http_user_agent;
        proxy_set_header Accept-Encoding "";
        proxy_buffering off;
        #websocket
	set $to_upgrade "";
	set $to_connection "";
	if ($http_upgrade = "websocket"){
	    set $to_upgrade "websocket";
	    set $to_connection "Upgrade";
	}
	proxy_set_header Upgrade $to_upgrade;
	proxy_set_header Connection $to_connection;
	proxy_read_timeout 86400;
    }
    location / {
    	default_type text/html;
	if ($cookie_proxy_port = ''){
	    return 404 'Nothing here';
	}
	proxy_http_version 1.1;
	proxy_pass http://127.0.0.1:$cookie_proxy_port;
	proxy_set_header Host $http_host;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header X-Real_IP $remote_addr;
	proxy_set_header User-Agent $http_user_agent;
	proxy_set_header Accept-Encoding "";
	proxy_buffering off;
	#websocket
	set $to_upgrade "";
	set $to_connection "";
	if ($http_upgrade = "websocket"){
	    set $to_upgrade "websocket";
	    set $to_connection "Upgrade";
	}
	proxy_set_header Upgrade $to_upgrade;
	proxy_set_header Connection $to_connection;
	proxy_read_timeout 86400;
    }
}

Remember to change the listen 28081 to your port , server name to your domain (or ip) , the certificate path to your certificate file.

Screenshot

image

It works normally . And the reverse proxy still works , no conflicts.

Actually , since nginx is already forwarding ports at /proxy/{port} , you can directly access code-server with https://host.com/proxy/8080/ (while 8080 is the port code-server is using)

  • Note : If the browser reported The site redirected you too many times , you can directly access https://host.com/ after accessing https://host.com/proxy/{port}/.

As you are already using nginx and serving code-server behind subpath , I think you can merge the configuration files . I think…just make your ‘location /code-server’ block at top so the other configurations wont affect it .So I think merging them won’t cause conflicts.

I have tons of homework to do now and I can’t test , I’ll test it this weekend and reply here. Sorry for keep you waiting ~

I wonder if there is a way we can surface help around this, maybe via a tooltip or text underneath the forwarded port? Or something like a “help me” button that triggers a notification or opens documentation?

Yeah many applications use absolute URLs unfortunately and default to / so they will need to be made aware of the base /proxy/{port}/ path, including Jupyter.

The sub-domain proxy method is superior since it does not have the same issue but it needs to be enabled via the --proxy-domain flag. I think it would look like this:

VSCODE_PROXY_URI=https://{{port}}.raspberrypi:28080 code-server --proxy-domain *.raspberrypi

(Alternatively the proxy could be implemented via a separate reverse proxy.)

Does this need to include the full url? i.e c.NotebookApp.base_url = 'https://192.168.0.114:28080/proxy/8888/?

It doens’t need to include the full url , there are descriptions in jupyter’s docs

For example, if you prefer that the notebook dashboard be located with a sub-directory that contains other ipython files, e.g. http://localhost:8888/ipython/, you can do so with configuration options like the following (see above for instructions about modifying jupyter_notebook_config.py): c.NotebookApp.base_url = '/ipython/'

@jsjoeio Yes , correct .

And , I found this https://jupyter-notebook.readthedocs.io/en/stable/public_server.html#running-the-notebook-with-a-customized-url-prefix, and I followed it. I added c.NotebookApp.base_url = '/proxy/8888/' to/home/ubuntu(my user name)/.jupyter/jupyter_notebook_config.py and launched it . This time when I access it , the jupyter icon loaded successfully but the site returned a 404

Screenshot

image

And jupyter notebook output

Logs
ubuntu@UbuntuCode:~/workspace$ jupyter notebook
[I 12:38:22.275 NotebookApp] Serving notebooks from local directory: /home/ubuntu/workspace
[I 12:38:22.275 NotebookApp] Jupyter Notebook 6.5.2 is running at:
[I 12:38:22.275 NotebookApp] http://localhost:8888/proxy/8888/?token=f56b2a3c705274d2ea73102a64fd811057f09797df2d5617
[I 12:38:22.275 NotebookApp]  or http://127.0.0.1:8888/proxy/8888/?token=f56b2a3c705274d2ea73102a64fd811057f09797df2d5617
[I 12:38:22.275 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 12:38:22.287 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///home/ubuntu/.local/share/jupyter/runtime/nbserver-8769-open.html
    Or copy and paste one of these URLs:
        http://localhost:8888/proxy/8888/?token=f56b2a3c705274d2ea73102a64fd811057f09797df2d5617
     or http://127.0.0.1:8888/proxy/8888/?token=f56b2a3c705274d2ea73102a64fd811057f09797df2d5617
[W 12:38:25.247 NotebookApp] 404 GET / (127.0.0.1) 64.800000ms referer=https://192.168.0.114:28080/?folder=/home/ubuntu/workspace
[W 12:38:25.559 NotebookApp] 404 GET /static/style/style.min.css?v=e1ab1c38b672063a6541baf468c83345cd0f509729783ec9b7ccb64073004f5f056110c82c28aefbf3dbf32e0e040f05b8f0420bc411b669ed3d4f07511812ca (127.0.0.1) 8.280000ms referer=https://192.168.0.114:28080/proxy/8888/
[W 12:38:25.600 NotebookApp] 404 GET /custom/custom.css (127.0.0.1) 6.840000ms referer=https://192.168.0.114:28080/proxy/8888/
[W 12:38:25.610 NotebookApp] 404 GET /static/components/jquery-ui/dist/themes/smoothness/jquery-ui.min.css?v=32f9dcde0cd9843f2b66d34c1c9928b59a5d7ef007ba7a6a6a790b3e78f7857a698444d7a716dfaf8fa834c3b3175efd258bbc07cfc4aabb86769b07e5f358c3 (127.0.0.1) 4.900000ms referer=https://192.168.0.114:28080/proxy/8888/
[W 12:38:25.615 NotebookApp] 404 GET /static/components/es6-promise/promise.min.js?v=bea335d74136a63ae1b5130f5ac9a50c6256a5f435e6e09fef599491a84d834a8b0f011ca3eaaca3b4ab6a2da2d3e1191567a2f171e60da1d10e5b9d52f84184 (127.0.0.1) 3.910000ms referer=https://192.168.0.114:28080/proxy/8888/
[W 12:38:25.622 NotebookApp] 404 GET /static/components/jquery-typeahead/dist/jquery.typeahead.min.css?v=5edf53bf6bb9c3b1ddafd8594825a7e2ed621f19423e569c985162742f63911c09eba2c529f8fb47aebf27fafdfe287d563347f58c1126b278189a18871b6a9a (127.0.0.1) 4.220000ms referer=https://192.168.0.114:28080/proxy/8888/
[W 12:38:25.628 NotebookApp] 404 GET /static/components/react/react.production.min.js?v=9a0aaf84a316c8bedd6c2ff7d5b5e0a13f8f84ec02442346cba0b842c6c81a6bf6176e64f3675c2ebf357cb5bb048e0b527bd39377c95681d22468da3d5de735 (127.0.0.1) 4.240000ms referer=https://192.168.0.114:28080/proxy/8888/
[W 12:38:25.697 NotebookApp] 404 GET /static/components/react/react-dom.production.min.js?v=6fc58c1c4736868ff84f57bd8b85f2bdb985993a9392718f3b4af4bfa10fb4efba2b4ddd68644bd2a8daf0619a3844944c9c43f8528364a1aa6fc01ec1b8ae84 (127.0.0.1) 6.330000ms referer=https://192.168.0.114:28080/proxy/8888/
[W 12:38:25.723 NotebookApp] 404 GET /static/components/requirejs/require.js?v=d37b48bb2137faa0ab98157e240c084dd5b1b5e74911723aa1d1f04c928c2a03dedf922d049e4815f7e5a369faa2e6b6a1000aae958b7953b5cc60411154f593 (127.0.0.1) 4.920000ms referer=https://192.168.0.114:28080/proxy/8888/
[W 12:38:25.727 NotebookApp] 404 GET /static/components/create-react-class/index.js?v=894ad57246e682b4cfbe7cd5e408dcd6b38d06af4de4f3425991e2676fdc2ef1732cbd19903104198878ae77de12a1996de3e7da3a467fb226bdda8f4618faec (127.0.0.1) 6.840000ms referer=https://192.168.0.114:28080/proxy/8888/
I'll look into the docs later to see if there are something more I should specify.

And for python3 -m http.server , it does work

Screenshots

image image image

UPD: Figured out , seems jupyter messed the urls up.

Screenshots

image

accessing from the internal net : (not through code-server proxy) Screenshot from 2022-12-02 21-25-09

The reverse proxy server used by code-server makes jupyter thinks that you are accessing path / while you are actually accessing /proxy/8888 , but jupyter only works when it thinks you are accessing /proxy/8888 , so in this case you have to double the /proxy/8888 . So accessing https://192.168.0.114:28080/proxy/8888/proxy/8888/login can show the login screen , but nothing else (like the logo or css files) loads because they were requested with https://192.168.0.114:28080/proxy/8888. Pretty confusing 😭