Starting a small local web server with HTTP.jl?

question
web

#1

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.


#2

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.


#3

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!


#4

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.


#5

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.


#6

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.


#7

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)


#8

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

#9

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

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

#10

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)

#11

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.