Is it possible to use Revise.jl to reload webserver on file save?

In Compile time: we can't find any impls for this multi dispatch fn? - #7 by zeroexcuses , I described a shell script which uses inotify to restart a webserver every time www/server.jl is modified.

I was informed the “correct” way to do this is to use Revise.jl but not the details of how to do it.

Question: using Revise.jl is there a way to restart/reload a webserver on file save to www/server.jl ?

what are you using in server.jl, as far as I can tell, HTTP.jl works with Revise, and Genie.jl includes Revise.jl as part of it

server.jl starts a HTTP server on 8080 for handling GET / POST requests.

I make some changes to server.jl (add/del a route) save the file, then the bash script restarts the HTTP server. How would you achieve this with Revise.jl ?

There is an example in the Revise docs of the basic workflow for running some webserver with some assets with automatic reloading.

In my understanding (not tested this) the idea would be to have a main file that you don’t change, and this loads revise as well as the package/file that contains the code to start your server and runs the main entrypoint to the server in the way defined in the revise example workflow I linked. Now it should automatically update the julia runtime with any updated code/assets if they are monitored by revise, so you don’t need to restart the server on update.

Are you referring to

entr(["file.js", "assets"], [MyWebCode]) do
    build_webpages(args...)
end

This is about as useful as inotify / watch.

What can we do with this? If we use kill -9 + julia server.jl, it’s not much of a improvement; we’ve merely moved “watching” to julia.

If there is some other way here of killing+ restarting the http server when src/server.jl is changed, (which I think many are hinting at), please let me know how.

I was just answering the question of how I think this would be done with revise which is what I though you were asking for. I think this is what other people hinted at as well but I might be wrong.

What do you mean by other way of killing + restarting the app, why do you want more ways? If you are happy with your way go with it, otherwise give revise a try. Or are you looking for some functionality that neither of them provide? In that case I might have misunderstood exactly what you want and need a clearer explanation.

To me it seems like a cleaner approach to use revise compared to the bash kill+restart, it is not “just” moved to julia IMO, though having it all in julia is nice by itself.
It as well allows for not restarting the julia session since revise just injects any updates into the running session IIUC. This seems like it should be more efficient, though I have not tested any of this so I might be completely off.

As stated in Compile time: we can't find any impls for this multi dispatch fn? - #23 by tim.holy, I think you’re failing to understand that you don’t have to kill it just because src/server.jl changed. (IIUC, Scala can’t recompile code within the same session when the code changes. But Julia most definitely can.) If you’re using Revise, you can update the running session, and all you need to do is regenerate the web pages.

1 Like

With Revise.jl, you don’t need to kill the Julia process, just refresh the webpage in your browser and you should see change taking effect. Let us know if it doesn’t work

I understand that many of you are claiming that I am using HTTP.jl incorrectly and do not understand the full power of Revise.jl.

I am asking this: can any of you point me to sample code for how to correctly setup HTTP.jl with Revise.jl ?

using Revise
using HTTP

using YourModule

Oh, you don’t have a module?

using Revise
using HTTP

includet("yourscript.jl")

Did you test your ‘solution’ before posting ?

Here are my expriments:

// inner.jl
using HTTP

const HOST = "127.0.0.1"
const PORT = 8080


println("Server Started")

router_8080 = HTTP.Router();


function do_8080(_::HTTP.Request)
    local r = HTTP.Response(200, "Hello World");
    return r;
end

HTTP.register!(router_8080, "GET", "/", do_8080)
HTTP.register!(router_8080, "GET", "**", do_8080)

HTTP.serve(router_8080, "127.0.0.1", 8080)
HTTP.serve(router_8001, "127.0.0.1", 8001)
// outer.jl
using Revise
using HTTP

include("inner.jl")
  1. I run the code via “julia outer.jl” ; I load the page, “Hello World”

  2. I modify inner.jl to say “Hello World 2”

  3. I save.

  4. I reload browser, still says “Hello World” (even with disable cache in dev console)

Back to the original question: What is the ‘correct’ way to setup HTTP.jl with Revise.jl for “auto reload after save” ?

@mkitti used includet that is include with a t at the end of it. You are using include without the t. That may be the reason you are not seeing what is expected.

here is a link to the documentation on includet: Revise usage: a cookbook · Revise.jl

  1. Good catch.

  2. I tried it with:

// server.jl
using Revise
using HTTP

includet("inner.jl")
  1. It still does not work.

  2. Reading the docs you linked, I think the difference is this:

in the docs you linked, the included file returns (after defining the function)

in my case, the included file never returns – it starts a http server and waits

ok here’s work seems to work for me, remove this line from the file:

HTTP.serve(router_8080, "127.0.0.1", 8080)

in your REPL, do using Revise, includet("./inner"), and then manually run something like

a = @async HTTP.serve(router_8080, "127.0.0.1", 8080)

when you update the content of inner.jl, you want to run something in REPL, for example:

julia> a
Task (runnable) @0x00007fbad6977850

this will trigger Revise, and now refresh the website you should see the effect taking place

@quinnj really could use some documentation or quality of life for using HTTP.jl and Revise.jl together

1 Like
  1. Thanks, I can verify this works.

  2. Can you explain why we have to retype ‘a’ in the REPL to trigger the revise/reload ?

I.e.

update inner.jl; reload Chrome; get old value
update inner.jl; type “a” in REPL; reload Chrome; get new value

I’m wondering what magic is happening when we press “a”; because it’s clearly doing more than just looking up a value in a hashmap

Revise has 2 modes of operation, the default mode would identify changes and recompile stuff on demand when you actually run something, and the other mode (polling) is usually not recommended unless you’re on a network mounted system where “file change” is not an observable of filesystem.

You could try polling but in that case you have a fixed latency so to speak, depends on how often Revise check for changes

A different approach could be: set your response Hello world to a variable. Set up another route that loads the text from a file and assigns to that variable. This way, it isn’t always reading the text from a file, but there is a way to refresh the information.

Here is a Revise.entr-based solution that should avoid the need to enter anything in the REPL to trigger Revise (it can actually run without a REPL at all)

using HTTP

const HOST = "127.0.0.1"
const PORT = 8080

function do_8080(_::HTTP.Request)
    local r = HTTP.Response(200, "Hello World 1");
    return r;
end

function start_server()
    server_task = @async try
        println("Server Started!")

        router_8080 = HTTP.Router();

        HTTP.register!(router_8080, "GET", "/", do_8080)
        HTTP.register!(router_8080, "GET", "**", do_8080)

        HTTP.serve(router_8080, "127.0.0.1", 8080)
    catch e
        e === :stop || rethrow()
    end
    errormonitor(server_task)
end

function stop_server(task)
    schedule(task, :stop, error=true)
    wait(task)
end
#outer.jl
using Revise
includet("inner.jl")

server_task = Ref(start_server())

entr(["inner.jl"], [], postpone=true) do
    stop_server(server_task[])
    server_task[] = start_server()
end
8 Likes

@ffevotte : Thanks. This is strictly superior to my old bash-script solution.