# Starting a small local web server with HTTP.jl?

**URL:** <https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668>\
**Category:** General Usage\
**Tags:** question, web\
**Created:** [June 14, 2018, 3:39am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668 "2018-06-14T03:39:12Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![tlienart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tlienart/32/7640_2.png) [@tlienart](https://discourse.julialang.org/u/tlienart)\
**Post date:** [June 14, 2018, 3:39am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/1 "2018-06-14T03:39:13Z")

</div>

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

The [local web server](https://github.com/lwsjs/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.

---

<div class="post-metadata">

**Author:** ![quinnj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/quinnj/32/11_2.png) [@quinnj](https://discourse.julialang.org/u/quinnj)\
**Post date:** [June 14, 2018, 4:20am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/2 "2018-06-14T04:20:26Z")

</div>

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

```julia
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.

---

<div class="post-metadata">

**Author:** ![tlienart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tlienart/32/7640_2.png) [@tlienart](https://discourse.julialang.org/u/tlienart)\
**Post date:** [June 14, 2018, 4:42am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/3 "2018-06-14T04:42:04Z")

</div>

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

```julia
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!

---

<div class="post-metadata">

**Author:** ![quinnj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/quinnj/32/11_2.png) [@quinnj](https://discourse.julialang.org/u/quinnj)\
**Post date:** [June 14, 2018, 4:56am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/4 "2018-06-14T04:56:37Z")

</div>

> [@quinnj](#):
>
> 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

Oops, my bad. It should be

```julia
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.

---

<div class="post-metadata">

**Author:** ![tlienart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tlienart/32/7640_2.png) [@tlienart](https://discourse.julialang.org/u/tlienart)\
**Post date:** [June 14, 2018, 4:59am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/5 "2018-06-14T04:59:41Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [June 14, 2018, 4:59am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/6 "2018-06-14T04:59:50Z")

</div>

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`.

---

<div class="post-metadata">

**Author:** ![wookyoung](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wookyoung/32/157_2.png) [@wookyoung](https://discourse.julialang.org/u/wookyoung)\
**Post date:** [June 14, 2018, 6:50am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/7 "2018-06-14T06:50:45Z")

</div>

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

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

```

visit the following repository for details (see examples/sevenstars.jl)  
[http://github.com/wookay/Bukdu.jl](http://github.com/wookay/Bukdu.jl)

---

<div class="post-metadata">

**Author:** ![tlienart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tlienart/32/7640_2.png) [@tlienart](https://discourse.julialang.org/u/tlienart)\
**Post date:** [June 14, 2018, 7:09am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/8 "2018-06-14T07:09:10Z")

</div>

Hey thanks, Bukdu looks interesting,

I tried that in the repl with

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

```

and got

```julia
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

```

---

<div class="post-metadata">

**Author:** ![wookyoung](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wookyoung/32/157_2.png) [@wookyoung](https://discourse.julialang.org/u/wookyoung)\
**Post date:** [June 17, 2018, 2:59am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/9 "2018-06-17T02:59:46Z")

</div>

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

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

```

---

<div class="post-metadata">

**Author:** ![tlienart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tlienart/32/7640_2.png) [@tlienart](https://discourse.julialang.org/u/tlienart)\
**Post date:** [June 17, 2018, 5:20am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/10 "2018-06-17T05:20:32Z")

</div>

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

![48](https://global.discourse-cdn.com/julialang/original/3X/d/f/df53b345971e710a1413536a3cb9e8ca022a14d4.png)

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

```

---

<div class="post-metadata">

**Author:** ![wookyoung](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wookyoung/32/157_2.png) [@wookyoung](https://discourse.julialang.org/u/wookyoung)\
**Post date:** [June 17, 2018, 3:24pm UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/11 "2018-06-17T15:24:46Z")

</div>

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).

```julia
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.

---

<div class="post-metadata">

**Author:** ![sschmidhuber](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sschmidhuber/32/13349_2.png) [@sschmidhuber](https://discourse.julialang.org/u/sschmidhuber)\
**Post date:** [March 11, 2020, 11:38pm UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/12 "2020-03-11T23:38:04Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![wookyoung](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wookyoung/32/157_2.png) [@wookyoung](https://discourse.julialang.org/u/wookyoung)\
**Post date:** [March 12, 2020, 8:16am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/13 "2020-03-12T08:16:54Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![sschmidhuber](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sschmidhuber/32/13349_2.png) [@sschmidhuber](https://discourse.julialang.org/u/sschmidhuber)\
**Post date:** [March 12, 2020, 1:54pm UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/15 "2020-03-12T13:54:53Z")

</div>

Thank you @wookyoung!! Now it works 🙂

---

<div class="post-metadata">

**Author:** ![yoh-meyers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yoh-meyers/32/14700_2.png) [@yoh-meyers](https://discourse.julialang.org/u/yoh-meyers)\
**Post date:** [May 8, 2020, 5:20pm UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/16 "2020-05-08T17:20:25Z")

</div>

My new lightweight web framework [Dance.jl](https://github.com/DanceJL/Dance.jl) supports this easily:

```julia
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.

---

<div class="post-metadata">

**Author:** ![tlienart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tlienart/32/7640_2.png) [@tlienart](https://discourse.julialang.org/u/tlienart)\
**Post date:** [May 8, 2020, 5:26pm UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/17 "2020-05-08T17:26:23Z")

</div>

Nice! given I was the OP I should probably also mention here that we ended up building [LiveServer.jl](https://github.com/asprionj/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](https://github.com/tlienart/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!

---

<div class="post-metadata">

**Author:** ![yoh-meyers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yoh-meyers/32/14700_2.png) [@yoh-meyers](https://discourse.julialang.org/u/yoh-meyers)\
**Post date:** [May 8, 2020, 8:25pm UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/18 "2020-05-08T20:25:50Z")

</div>

NP.

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

---

<div class="post-metadata">

**Author:** ![dcastel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dcastel/32/10692_2.png) [@dcastel](https://discourse.julialang.org/u/dcastel)\
**Post date:** [September 17, 2020, 9:23am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/19 "2020-09-17T09:23:37Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![tlienart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tlienart/32/7640_2.png) [@tlienart](https://discourse.julialang.org/u/tlienart)\
**Post date:** [September 17, 2020, 9:38am UTC](https://discourse.julialang.org/t/starting-a-small-local-web-server-with-http-jl/11668/20 "2020-09-17T09:38:22Z")

</div>

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)
