Starting a small local web server with HTTP.jl?

I’m looking for a way to host a local directory as a small, static, website.

The local web server in Node.js offers exactly what I need (running ws in the terminal starts a local web server in the current directory which I can access at localhost:8000), but it’d be great to do it in Julia.

I imagine this is possible with HttpServer.jl but I don’t know how and I was a bit confused by the doc (apologies, I’m a newbie in the world of web apps). Pointers would be very welcome.

1 Like

You could do something like the following w/ the HTTP.jl package (the actively maintained version of HttpServer.jl):

using HTTP
HTTP.listen() do req
    req.target == "/" && return HTTP.Response(200, read("index.html"))
    file = HTTP.unescapeuri(req.target[2:end]))
    return isfile(file) ? HTTP.Response(200, read(file)) : HTTP.Response(404)
end

I know it’s not the most terse thing, but it should get the job done. We should really make something like that the default handler in HTTP.jl.

Hey @quinnj, thanks a lot for your answer, I imagine it’s a small thing but when trying your code I got:

I- Listening on: 127.0.0.1:8081
I- Accept:  🔗    0↑     0↓    0s 127.0.0.1:8081:8081 ≣16
E- ErrorException("type Stream has no field target")

Could you briefly explain what the lines do? thanks!

Oops, my bad. It should be

HTTP.listen() do req::HTTP.Request
    req.target == "/" && return HTTP.Response(200, read("index.html"))
    file = HTTP.unescapeuri(req.target[2:end]))
    return isfile(file) ? HTTP.Response(200, read(file)) : HTTP.Response(404)
end

req.target == "/" && return HTTP.Response(200, read("index.html")) is a default handler where you navigate to 127.0.0.1:8081, typically in that case, servers return the “default” webpage, usually named “index.html”.
file = HTTP.unescapeuri(req.target[2:end])), takes any path and converts it into a file, so if you visited 127.0.0.1:8081/path/to/file it would return path/to/file; the call to unescapeuri just ensures that any % encoded characters are converted back from the url encoding.

The last line just checks if the path refers to an actual file: if so, it reads and returns it, otherwise, a 404 response is returned.

1 Like

that’s great, thanks, it would be a nice simple example to add to the doc maybe!

Ps: if you see this and end up copy-pasting the code, make sure to remove the extra closing bracket on the file = HTTP … line.

It might be worth noting that the default http.server in Python only serves files from within the current directory, whereas this one will happily serve up http://localhost:8081//etc/passwd.

3 Likes

You could providing static sites easily with Bukdu.jl (which based on HTTP.jl and Julia 0.7)

using Bukdu
plug(Plug.Static, at="/", from=normpath(@__DIR__, "public"))
Bukdu.start(8000)

visit the following repository for details (see examples/sevenstars.jl)

Hey thanks, Bukdu looks interesting,

I tried that in the repl with

using Bukdu
plug(plug.Static, at="/", from=normpath("."))
Bukdu.start(8000)

and got

ERROR  GET /                                   
 MethodError(convert, (Bukdu.Octo.Assoc, Dict("Connection"=>"keep-alive","DNT"=>"1","Upgrade-Insecure-Requests"=>"1","http_minor"=>"1","Keep-Alive"=>"1","User-Agent"=>"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:61.0) Gecko/20100101 Firefox/61.0","Accept-Encoding"=>"gzip, deflate","Host"=>"localhost:8000","http_major"=>"1","Accept"=>"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language"=>"en-GB,en;q=0.5")), 0x000000000000555c) 
handler(::Type{Bukdu.Endpoint}, ::Int64, ::HttpCommon.Request, ::HttpCommon.Response) at handler.jl:46
(::Bukdu.##64#68{Bukdu.Endpoint,Int64})(::HttpCommon.Request, ::HttpCommon.Response) at server.jl:88
(::HttpServer.#on_message_complete#14{HttpServer.Server,HttpServer.Client{TCPSocket},Bool})(::HttpCommon.Request) at HttpServer.jl:427
on_message_complete(::Ptr{HttpParser.Parser}) at RequestParser.jl:113
http_parser_execute(::HttpParser.Parser, ::HttpParser.ParserSettings, ::Array{UInt8,1}) at HttpParser.jl:115
process_client(::HttpServer.Server, ::HttpServer.Client{TCPSocket}, ::Bool) at HttpServer.jl:389
(::HttpServer.##7#8{HttpServer.Server,Bool})() at task.jl:335

If you are working with Julia 0.6, try get the master branch.

julia> Pkg.checkout("Bukdu", "master")

Thanks, after checkout, I must still be doing something wrong because this is what it lead me to:

48

using Bukdu
plug(Plug.Static, at="/", from=normpath("."))
Bukdu.start(8000)

ah. there’s an inconsistency between Bukdu branches. (master branch with Julia 0.6, sevenstars branch with Julia 0.7)

try this (master branch with Julia 0.6).

using Bukdu
Endpoint() do
    plug(Plug.Static, at="/", from=normpath("."))
end
Bukdu.start(8000)

I would recommend to use Bukdu sevenstars branch with Julia 0.7 for now.

1 Like

Hello @wookyoung,
I’m starting to write a small web app with Bukdu. Unfortunately I couldn’t figure out how to use this static plug, it seems like this changed. I couldn’t find any documentation or a working example.

Could you give me a hint?

hi. it’s been changed a bit than above code. there’s a test file for it. https://github.com/wookay/Bukdu.jl/blob/master/test/bukdu/plugs/static.jl

1 Like

Thank you @wookyoung!! Now it works :slight_smile:

My new lightweight web framework Dance.jl supports this easily:

using Dance.Router

static_dir("/static", "files")

where first param is route prefix of how to serve (can be / too) and second param is name of the static files folder in web project dir to serve content from.

You would have to copy the static files dir to root of newly created web project dir, as for security dance.jl does not allow going higher than the project root.

Nice! given I was the OP I should probably also mention here that we ended up building LiveServer.jl (a bit similar to python’s http.server and node’s browser-sync) for the purpose mentioned in the question. LiveServer works with Documenter, Literate and also is the backend of Franklin.jl so it’s pretty robust though quite simple.

Glad to see that there are more things coming in this space, I haven’t tried Dance yet but will have a look!

1 Like

NP.

Yes I read about Franklin here, definitely promising for easy blogging.

1 Like

Nice, Franklin is definitely an interesting option. Anyone seen a Jekyll migration tool for it yet?

No tool yet, but the transition is fairly straightforward (I think), happy to help if you give it a shot (eg on the #franklin slack channel)