Hi! I'm having real trouble persuading a web-server component to start. I'm using httpkit, which has a run-server
function. When I run mount/start
, mount starts the web server component, but it doesn't seem to actually start the web server. I must be doing something wrong with the :start
function but I can't figure out what. Very grateful for any help.
I've got a sample project that demonstrates the problem here: https://github.com/conan/mount-test/blob/master/src/mount_test/core.clj
on startup I get {:started ["#'mount-test.core/web-server"]}
shown, and another call to mount/start
doesn't start anything, so mount has definitely done its job.
http-kit's run-server
function takes a map of options. you need to pass port
as a value in this map:
(defn start
[port]
(println "Starting on port " port)
(server/run-server (app)
{:port port}))
oh yes, that is true. it's correct in my real application, but it does at least show that my sample project is not a reproduction of the issue
is there some difference between including the code for :start
inside the defstate
macro and putting it in a separate function?
I can start the server that does listen on the port you provided once I add {:port port}
. there is no difference between including the code and calling a function name with the code
so in the above example, doing :start (server/run-server (app) {:port 4321})
as opposed to calling the function
:start (server/run-server (app) {:port 4321})
yep, that's fine. I usually just call a function from :start
/ :stop
since it is easier to work with this function from the REPL
this is really helpful though
so here's my real app. this doesn't work:
(defn start
"Httpkit's run-server returns a function which stops the server <http://http-kit.org/migration.html>"
[{{port :config.web/port :as web-config} :config/web}]
(when-let [config-error (s/explain-data :config/web web-config)]
(throw (ex-info "Failed to start web server, invalid config" config-error)))
(log/info "Starting web server on port:" port)
(server/run-server (app) {:port port}))
(defstate web-server
:start (start config)
:stop (web-server))
but, i've just discovered thanks to your help, this does:
(defstate web-server
:start (let [{{port :config.web/port :as web-config} :config/web} config]
(when-let [config-error (s/explain-data :config/web web-config)]
(throw (ex-info "Failed to start web server, invalid config" config-error)))
(log/info "Starting web server on port:" port)
(server/run-server (app) {:port port}))
:stop (web-server))
it should all be the same, but calling out to a separate function means the run-server
never gets run. i assume i've done something really silly, but i can't spot it
you are calling (start 8888)
, but the function takes a map of params: [{{port :config.web/port :as web-config} :config/web}]
sorry typo
also I see that you doing prn
manually to log when states start / stop. take a look at mount-up
: https://github.com/tolitius/mount-up it'll do logging for you
oh nice, i wondered where those log lines came from
so this:
(defn start
"Httpkit's run-server returns a function which stops the server <http://http-kit.org/migration.html>"
[{{port :config.web/port :as web-config} :config/web}]
(when-let [config-error (s/explain-data :config/web web-config)]
(throw (ex-info "Failed to start web server, invalid config" config-error)))
(log/info "Starting web server on port:" port)
(server/run-server (app) {:port port}))
(s/fdef start
:args (s/cat :config :ef/config)
:ret (s/fspec :args (s/cat)))
(defstate web-server
:start (start config)
:stop (web-server))
yields this when I run mount/start
:
18-05-24 16:02:15 EF-XPS INFO [hanabi.config:16] - Loading config
18-05-24 16:02:15 EF-XPS INFO [hanabi.db:43] - Creating database at: datomic:<dev://localhost:4334/hanabi>
18-05-24 16:02:17 EF-XPS INFO [hanabi.db:34] - Migrating database
18-05-24 16:02:17 EF-XPS INFO [hanabi.s3:21] - Using s3 in region: eu-west-2
18-05-24 16:02:17 EF-XPS INFO [hanabi.greenhouse:250] - Starting job-posts schedule
18-05-24 16:02:17 EF-XPS INFO [hanabi.index:25] - Pre-caching index.html
18-05-24 16:02:17 EF-XPS INFO [hanabi.web:213] - Starting web server on port: 8888
=>
{:started ["#'hanabi.config/config"
"#'hanabi.db/db-conn"
"#'hanabi.s3/s3-config"
"#'hanabi.greenhouse/greenhouse"
"#'hanabi.index/page"
"#'hanabi.web/web-server"]}
(http/get "<http://localhost:8888/favicon.png>")
ConnectException Connection refused: connect java.net.DualStackPlainSocketImpl.connect0 (DualStackPlainSocketImpl.java:-2)
The log line you spotted runs, but the web server does not start. It's clearly a problem like the ones you've been (very kindly) pointing out, because mount has found and started the component.
might have to do with your config
's, since if you fix port
(i.e. {:port port}
) in your sample app, the server starts fine, and routes work. this is directly from your sample app:
=> (-> (http/get "<http://localhost:4321/>") :body)
"hi there"
the mystery remains. thanks so much for your help!
ok, the problem may stem from referring to another mount component (`config`) inside the defstate
macro
or at least, calling out to my start
function works if i do not pass the config
to it
(i.e. it refers to config
directly as a side cause)
maybe there's some kind of interaction when referring to other mount components inside defstate
so this works:
(defn start
"Httpkit's run-server returns a function which stops the server <http://http-kit.org/migration.html>"
[]
(let [{{port :config.web/port :as web-config} :config/web} config]
(when-let [config-error (s/explain-data :config/web web-config)]
(throw (ex-info "Failed to start web server, invalid config" config-error)))
(log/info "Starting web server on port:" port)
(server/run-server (app) {:port port})))
(defstate web-server
:start (start)
:stop (web-server))
but this does not:
(defn start
"Httpkit's run-server returns a function which stops the server <http://http-kit.org/migration.html>"
[{{port :config.web/port :as web-config} :config/web}]
(when-let [config-error (s/explain-data :config/web web-config)]
(throw (ex-info "Failed to start web server, invalid config" config-error)))
(log/info "Starting web server on port:" port)
(server/run-server (app) {:port port}))
(defstate web-server
:start (start config)
:stop (web-server))
config
is a mount state defined in another namespace
correction: the problem is not caused by referring to the config
it's some specs i have for these functions
if i have a :ret
spec on the :start
function then it doesn't work
🤯
so i have this:
(s/fdef start
:args (s/cat :config :ef/config)
:ret (s/fspec :args (s/cat)))
if i comment out the :ret
spec in there then it works. i'm fairly sure the spec is correct because of this:
(def web-server (server/run-server (web/app) {:port 8889}))
=> #'user/web-server
(s/valid? (s/fspec :args (s/cat)) web-server)
=> true
i'm using expound and orchestra, maybe they are interacting with defstate
somehow
:ret any?
works as well
I've updated the sample project with a spec and now i can reproduce
I only use a little of spec and just for validation so I am not sure how spec affects this function.
mount does not do anything to a :start
/ :stop
references. it just calls them whenever (mount/start)
or (mount/stop)
are called.