djblue / portal

A clojure tool to navigate through your data.
https://djblue.github.io/portal/
MIT License
881 stars 82 forks source link

Portal cannot connect to IntelliJ from nREPL inside container #227

Closed devurandom closed 3 months ago

devurandom commented 4 months ago

My application runs inside a container (compose). When I open the Portal tab in IntelliJ, a .portal/intellij.edn file is created in the project source directory (which is mounted into the container, so it is available at $PWD/.portal/intellij.edn). The content is similar to {:host "localhost", :port 37011}. Since my nREPL is inside a container and cannot talk to the host's localhost interface, I have to replace localhost with host.containers.internal every time I start IntelliJ.

It would be nice if the IntelliJ plugin would offer a settings option to configure a custom host, as an alternative to the default of localhost.

The Portal already listens on all interfaces (:: / 0.0.0.0) by default / without any configuration, so this does not have to be changed.

devurandom commented 4 months ago

Replacing localhost with host.containers.internal does not actually work. It allows (p/open {:launcher :intellij}) to succeed, but the tap is not usable afterwards.

Logs when starting IntelliJ:

2024-05-23 17:14:01,202 [   5660]   INFO - #portal.extensions.intellij.factory.PortalLogger - Getting window
2024-05-23 17:14:01,203 [   5661]   INFO - #portal.extensions.intellij.factory.PortalLogger - Starting Portal plugin
2024-05-23 17:14:01,248 [   5706]   INFO - #portal.extensions.intellij.factory.PortalLogger - Initializing browser

Now I replace localhost with host.containers.internal in .portal/intellij.edn.

Then I connect with Cursive to the nREPL server and execute:

(require '[portal.api :as p])
(def p (p/open {:launcher :intellij}))

Immediately I see this in the IntelliJ's idea.log:

2024-05-23 17:17:01,607 [ 186065]   INFO - #portal.extensions.intellij.factory.PortalLogger - Request: /open
2024-05-23 17:17:01,611 [ 186069]   INFO - #portal.extensions.intellij.factory.PortalLogger - Opening http://localhost:38173?98238d01-ca3d-4751-ba8b-8e7a13e60f95
2024-05-23 17:17:01,659 [ 186117]   INFO - STDERR - clojure.lang.ExceptionInfo: ERR_CONNECTION_REFUSED {:errorCode #object[org.cef.handler.CefLoadHandler$ErrorCode 0x5b26ed1a "ERR_CONNECTION_REFUSED"], :errorText "ERR_CONNECTION_REFUSED", :failedUrl "http://localhost:38173/?98238d01-ca3d-4751-ba8b-8e7a13e60f95"}
2024-05-23 17:17:01,659 [ 186117]   INFO - STDERR -     at portal.extensions.intellij.factory$setup_load_handler$reify__476.onLoadError(factory.clj:128)
2024-05-23 17:17:01,659 [ 186117]   INFO - STDERR -     at com.intellij.ui.jcef.JBCefClient$11.lambda$onLoadError$3(JBCefClient.java:558)
2024-05-23 17:17:01,659 [ 186117]   INFO - STDERR -     at com.intellij.ui.jcef.JBCefClient$HandlerSupport.lambda$handle$1(JBCefClient.java:749)
2024-05-23 17:17:01,659 [ 186117]   INFO - STDERR -     at java.base/java.lang.Iterable.forEach(Iterable.java:75)
2024-05-23 17:17:01,659 [ 186117]   INFO - STDERR -     at java.base/java.util.Collections$SynchronizedCollection.forEach(Collections.java:2131)
2024-05-23 17:17:01,659 [ 186117]   INFO - STDERR -     at com.intellij.ui.jcef.JBCefClient$HandlerSupport.handle(JBCefClient.java:749)
2024-05-23 17:17:01,659 [ 186117]   INFO - STDERR -     at com.intellij.ui.jcef.JBCefClient$11.onLoadError(JBCefClient.java:557)
2024-05-23 17:17:01,659 [ 186117]   INFO - STDERR -     at jcef/org.cef.CefClient.onLoadError(CefClient.java:764)
2024-05-23 17:17:01,668 [ 186126]   INFO - #portal.extensions.intellij.factory.PortalLogger - Patching options
2024-05-23 17:17:01,672 [ 186130]   INFO - #portal.extensions.intellij.factory.PortalLogger - Running portal.ui.options.patch("{:theme :portal.extensions.intellij.factory/theme, :themes {:portal.extensions.intellij.factory/theme {:portal.colors/string \"rgb(6,125,23)\", :portal.colors/keyword \"rgb(135,16,148)\", :portal.colors/exception \"rgb(178,50,71)\", :font-size 13, :portal.colors/text \"rgb(0,0,0)\", :portal.colors/background2 \"rgb(245,248,254)\", :portal.colors/boolean \"rgb(135,16,148)\", :portal.colors/tag \"rgb(0,98,122)\", :portal.colors/uri \"rgb(69,133,190)\", :portal.colors/namespace \"rgb(0,51,179)\", :portal.colors/diff-add \"rgb(6,125,23)\", :portal.colors/package \"rgb(0,98,122)\", :portal.colors/border \"rgb(218,230,253)\", :portal.colors/symbol \"rgb(0,0,0)\", :portal.colors/diff-remove \"rgb(178,50,71)\", :portal.colors/number \"rgb(23,80,235)\", :portal.colors/background \"rgb(255,255,255)\", :font-family \"\\\"Fira Code\\\", monospace\"}}}")

The Portal tab / window stays blank grey.

The part of Portal that runs inside my Clojure process appears to open another HTTP server on a random port, to which the part of Portal that runs in IntelliJ then connects.

Thus I choose a port and expose it to the host by patching the following into my compose.yaml:

services:
  app:
    ports:
      - published: 17888
        target: 17888

I restart the compose environment, connect the nREPL again, and execute:

(require '[portal.api :as p])
(def p (p/open {:launcher :intellij :host "localhost" :port 17888}))

This time I get following output in idea.log:

2024-05-23 17:25:13,020 [ 677478]   INFO - #portal.extensions.intellij.factory.PortalLogger - Request: /open
2024-05-23 17:25:13,021 [ 677479]   INFO - #portal.extensions.intellij.factory.PortalLogger - Opening http://localhost:17888?6322e15d-72d1-4958-b3d2-b4dbaa1d2ccd
2024-05-23 17:25:13,081 [ 677539]   INFO - #portal.extensions.intellij.factory.PortalLogger - Starting loading
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR - clojure.lang.ExceptionInfo: JavaScript error: Error: Failed to construct 'WebSocket': The URL 'ws://localhost:NaN/rpc?6322e15d-72d1-4958-b3d2-b4dbaa1d2ccd' is invalid.
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at http://localhost:17888/main.js:4975:138
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at new Promise (<anonymous>)
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at h1.j (http://localhost:17888/main.js:4975:106)
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at h1.I (http://localhost:17888/main.js:4974:203)
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at c1 (http://localhost:17888/main.js:4976:363)
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at http://localhost:17888/main.js:4968:278
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at new Promise (<anonymous>)
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at AQa (http://localhost:17888/main.js:4968:157)
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at d1 (http://localhost:17888/main.js:4969:93)
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at CNa (http://localhost:17888/main.js:4737:395) {}
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at portal.extensions.intellij.factory$setup_java_error_handler$fn__472.invoke(factory.clj:99)
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at portal.extensions.intellij.factory$as_function$reify__467.apply(factory.clj:95)
2024-05-23 17:25:13,375 [ 677833]   INFO - STDERR -     at com.intellij.ui.jcef.JBCefJSQuery$1.onQuery(JBCefJSQuery.java:123)
2024-05-23 17:25:13,376 [ 677834]   INFO - #portal.extensions.intellij.factory.PortalLogger - Patching options
2024-05-23 17:25:13,377 [ 677835]   INFO - #portal.extensions.intellij.factory.PortalLogger - Running portal.ui.options.patch("{:theme :portal.extensions.intellij.factory/theme, :themes {:portal.extensions.intellij.factory/theme {:portal.colors/string \"rgb(6,125,23)\", :portal.colors/keyword \"rgb(135,16,148)\", :portal.colors/exception \"rgb(178,50,71)\", :font-size 13, :portal.colors/text \"rgb(0,0,0)\", :portal.colors/background2 \"rgb(245,248,254)\", :portal.colors/boolean \"rgb(135,16,148)\", :portal.colors/tag \"rgb(0,98,122)\", :portal.colors/uri \"rgb(69,133,190)\", :portal.colors/namespace \"rgb(0,51,179)\", :portal.colors/diff-add \"rgb(6,125,23)\", :portal.colors/package \"rgb(0,98,122)\", :portal.colors/border \"rgb(218,230,253)\", :portal.colors/symbol \"rgb(0,0,0)\", :portal.colors/diff-remove \"rgb(178,50,71)\", :portal.colors/number \"rgb(23,80,235)\", :portal.colors/background \"rgb(255,255,255)\", :font-family \"\\\"Fira Code\\\", monospace\"}}}")

Note how it tries to connect to localhost:NaN.

devurandom commented 4 months ago

Opening http://localhost:17888?26db2619-b57c-4c4e-a666-63de18a6c44e in a browser works (the URL is corrected by Firefox to http://localhost:17888/?26db2619-b57c-4c4e-a666-63de18a6c44e, note the slash after the port). Opening it connects to ws://localhost:17888/rpc?26db2619-b57c-4c4e-a666-63de18a6c44e= (checked in the "network" browser console tab).

djblue commented 4 months ago

Hi @devurandom, thanks for the detailed description of your issue.

I think the last issue you were seeing might be related to this fix https://github.com/djblue/portal/pull/228. So it should be fixed after the next release, the other option is to remove the :host from the open options:

(require '[portal.api :as p])
(def p (p/open {:launcher :intellij :port 17888}))

If that ends up working for you, I think getting a guide together of how to get editor extensions working in the context of a docker-ized repl would be great. Let me know if you have any interest in building out the guide 🙏

devurandom commented 4 months ago

Thanks for looking into this! I wanted to try the version containing the fix, but it seems to have not yet made it into the IntelliJ marketplace: https://plugins.jetbrains.com/plugin/18467-portal-inspector/versions

P.S. The update just arrived.

devurandom commented 4 months ago

I think the last issue you were seeing might be related to this fix #228. So it should be fixed after the next release, the other option is to remove the :host from the open options:

(require '[portal.api :as p])
(def p (p/open {:launcher :intellij :port 17888}))

Thanks! This helped.

With :host "localhost", I could open http://localhost:17888 and see the taps, but the IntelliJ plugin (v0.56.0) stayed blank. The error in the logs was the same:

2024-06-04 19:55:10,034 [ 336844]   INFO - #portal.extensions.intellij.factory.PortalLogger - Request: /open
2024-06-04 19:55:10,038 [ 336848]   INFO - #portal.extensions.intellij.factory.PortalLogger - Opening http://localhost:17888?2152fa23-8439-47bb-b67d-2ca9545095de
2024-06-04 19:55:10,134 [ 336944]   INFO - #portal.extensions.intellij.factory.PortalLogger - Starting loading
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR - clojure.lang.ExceptionInfo: JavaScript error: Error: Failed to construct 'WebSocket': The URL 'ws://localhost:NaN/rpc?2152fa23-8439-47bb-b67d-2ca9545095de' is invalid.
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at http://localhost:17888/main.js:4975:138
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at new Promise (<anonymous>)
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at h1.j (http://localhost:17888/main.js:4975:106)
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at h1.I (http://localhost:17888/main.js:4974:203)
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at c1 (http://localhost:17888/main.js:4976:363)
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at http://localhost:17888/main.js:4968:278
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at new Promise (<anonymous>)
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at AQa (http://localhost:17888/main.js:4968:157)
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at d1 (http://localhost:17888/main.js:4969:93)
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at CNa (http://localhost:17888/main.js:4737:395) {}
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at portal.extensions.intellij.factory$setup_java_error_handler$fn__472.invoke(factory.clj:99)
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at portal.extensions.intellij.factory$as_function$reify__467.apply(factory.clj:95)
2024-06-04 19:55:10,566 [ 337376]   INFO - STDERR -     at com.intellij.ui.jcef.JBCefJSQuery$1.onQuery(JBCefJSQuery.java:123)
2024-06-04 19:55:10,568 [ 337378]   INFO - #portal.extensions.intellij.factory.PortalLogger - Patching options
2024-06-04 19:55:10,573 [ 337383]   INFO - #portal.extensions.intellij.factory.PortalLogger - Running portal.ui.options.patch("{:theme :portal.extensions.intellij.factory/theme, :themes {:portal.extensions.intellij.factory/theme {:portal.colors/string \"rgb(6,125,23)\", :portal.colors/keyword \"rgb(135,16,148)\", :portal.colors/exception \"rgb(178,50,71)\", :font-size 13, :portal.colors/text \"rgb(0,0,0)\", :portal.colors/background2 \"rgb(245,248,254)\", :portal.colors/boolean \"rgb(135,16,148)\", :portal.colors/tag \"rgb(0,98,122)\", :portal.colors/uri \"rgb(69,133,190)\", :portal.colors/namespace \"rgb(0,51,179)\", :portal.colors/diff-add \"rgb(6,125,23)\", :portal.colors/package \"rgb(0,98,122)\", :portal.colors/border \"rgb(218,230,253)\", :portal.colors/symbol \"rgb(0,0,0)\", :portal.colors/diff-remove \"rgb(178,50,71)\", :portal.colors/number \"rgb(23,80,235)\", :portal.colors/background \"rgb(255,255,255)\", :font-family \"\\\"Fira Code\\\", monospace\"}}}")

Without :host, accessing the portal through a browser still works, but now the tab of the IntelliJ plugin also works.

If that ends up working for you, I think getting a guide together of how to get editor extensions working in the context of a docker-ized repl would be great. Let me know if you have any interest in building out the guide 🙏

I would like to write that, but it might take some time until I get around to do it. If you or anyone else would do it in the meantime, please don't wait for me.

devurandom commented 4 months ago

Without replacing localhost with host.containers.internal in $PWD/.portal/intellij.edn (cf. https://github.com/djblue/portal/issues/227#issue-2313045975) I still get:

clojure.lang.ExceptionInfo: Unable to open extension: Please ensure extension is installed and Portal tab is open. {:options {:launcher :intellij, :port 17888}, :config {:host "localhost", :port 41857}, :response {}}
    at portal.runtime.jvm.launcher$eval12200$fn__12201.invoke(launcher.clj:53)
    at clojure.lang.MultiFn.invoke(MultiFn.java:229)
    at portal.runtime.browser$open.invokeStatic(browser.cljc:140)
    at portal.runtime.browser$open.invoke(browser.cljc:133)
    at portal.runtime.jvm.launcher$open.invokeStatic(launcher.clj:102)
    at portal.runtime.jvm.launcher$open.invoke(launcher.clj:97)
    at portal.api$open.invokeStatic(api.cljc:98)
    at portal.api$open.invoke(api.cljc:78)
    at portal.api$open.invokeStatic(api.cljc:96)
    at portal.api$open.invoke(api.cljc:78)

Is it possible to make the hostname configurable?

djblue commented 4 months ago

That file currently gets written by portal.extensions.intellij.factory/write-config. Feels like that might be a bit harder to make configurable 🤔 Another option would be to automatically swap it out on read when trying to communicate to the extension here.

I assume the main issue you are having is that file is automatically created/deleted by the extension so you can't just hardcode the :host value which you may have already tried.

djblue commented 4 months ago

Do you mind trying https://github.com/djblue/portal/pull/230 locally?

(require '[portal.api :as p])
(def p (p/open {:port 17888 :launcher :intellij :launcher-config {:host "host.containers.internal"}}))
devurandom commented 4 months ago

I checked out the launcher-config branch, set {:deps {djblue/portal {:local/root "../../clojure/portal"}}} in deps.edn, and ran the following function in my nREPL:

(defn portal []
  #_(require '[portal.api :as p])
  (add-tap #'p/submit)
  (def p (p/open {:port            17888
                  :launcher        :intellij
                  :launcher-config {:host "host.containers.internal"}}))
  (tap> :hello))

true was returned and :hello appeared in the IntelliJ Portal tab.

Thanks!

devurandom commented 3 months ago

Released as part of https://github.com/djblue/portal/releases/tag/0.57.0. Thanks! :)