https://github.com/JuliaWeb/Mux.jl
Middleware for Julia
https://github.com/JuliaWeb/Mux.jl
middleware webserver
Last synced: 2 months ago
JSON representation
Middleware for Julia
- Host: GitHub
- URL: https://github.com/JuliaWeb/Mux.jl
- Owner: JuliaWeb
- License: other
- Created: 2015-03-03T20:25:28.000Z (over 11 years ago)
- Default Branch: master
- Last Pushed: 2024-06-28T14:51:37.000Z (almost 2 years ago)
- Last Synced: 2025-09-27T01:47:07.483Z (9 months ago)
- Topics: middleware, webserver
- Language: Julia
- Size: 188 KB
- Stars: 280
- Watchers: 26
- Forks: 67
- Open Issues: 17
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
- awesome-julia-security - Mux.jl - Middleware framework for Julia web applications. (Web Security / HTTP and Web Frameworks)
README
# Mux.jl
[](https://travis-ci.com/JuliaWeb/Mux.jl)
[](https://ci.appveyor.com/project/shashi/mux-jl/branch/master)
[](https://codecov.io/github/JuliaWeb/Mux.jl?branch=master)
```jl
Pkg.add("Mux")
```
Mux.jl gives your Julia web services some closure. Mux allows you to
define servers in terms of highly modular and composable components
called middleware, with the aim of making both simple and complex
servers as simple as possible to throw together.
For example:
```jl
using Mux
@app test = (
Mux.defaults,
page(respond("
Hello World!
")),
page("/about",
probability(0.1, respond("Boo!
")),
respond("About Me
")),
page("/user/:user", req -> "Hello, $(req[:params][:user])!
"),
Mux.notfound())
serve(test)
```
You can run this demo by entering the successive forms into the Julia
REPL. The code displays a "hello, world" at `localhost:8000`, with an
about page at `/about` and another hello at `/user/[your name]`.
The `@app` macro allows the server to be redefined on the fly, and you
can test this by editing the `hello` text and re-evaluating. (don't
re-evalute `serve(test)`)
Note that `serve(test)` launches an asynchronous `Task` that will not prevent Julia from terminating.
This is good at the REPL, but not always what you want.
If you want Julia to wait for the task to finish, use `wait(serve(test))`.
## Technical Overview
Mux.jl is at heart a control flow library, with a [very small core](https://github.com/one-more-minute/Mux.jl/blob/master/src/Mux.jl#L7-L16). It's not important to understand that code exactly as long as you understand what it achieves.
There are three concepts core to Mux.jl: Middleware (which should be familiar
from the web libraries of other languages), stacking, and branching.
### Apps and Middleware
An *app* or *endpoint* is simply a function of a request which produces
a response:
```jl
function myapp(req)
return "
Hello, $(req[:params][:user])!
"
end
```
In principle this should say "hi" to our lovely user. But we have a
problem – where does the user's name come from? Ideally, our app
function doesn't need to know – it's simply handled at some point up the
chain (just the same as we don't parse the raw HTTP data, for example).
One way to solve this is via *middleware*. Say we get `:user` from a cookie:
```jl
function username(app, req)
req[:params][:user] = req[:cookies][:user]
return app(req) # We could also alter the response, but don't want to here
end
```
Middleware simply takes our request and modifies it appropriately, so
that data needed later on is available. This example is pretty trivial,
but we could equally have middleware which handles authentication and
encryption, processes cookies or file uploads, provides default headers,
and more.
We can then call our new version of the app like this:
```jl
username(myapp, req)
```
In fact, we can generate a whole new version of the app which has username
support built in:
```jl
function app2(req)
return username(myapp, req)
end
```
But if we have a lot of middleware, we're going to end up with a lot of `appX` functions.
For that reason we can use the `mux` function instead, which creates the new app for us:
```jl
mux(username, myapp)
```
This returns a *new* function which is equivalent to `app2` above. We
just didn't have to write it by hand.
### Stacking
Now suppose you have lots of middleware – one to parse the HTTP request into
a dict of properties, one to check user authentication, one to catch errors,
etc. `mux` handles this too – just pass it multiple arguments:
```jl
mux(todict, auth, catch_errors, app)
```
Again, `mux` returns a whole new app (a `request -> response` function)
for us, this time wrapped with the three middlewares we provided.
`todict` will be the first to make changes to the incoming request, and
the last to alter the outgoing response.
Another neat thing we can do is to compose middleware into more middleware:
```jl
mymidware = stack(todict, auth, catch_errors)
mux(mymidware, app)
```
This is effectively equivalent to the `mux` call above, but creating a
new middleware function from independent parts means we're able to
factor out our service to make things more readable. For example, Mux
provides the `Mux.default` middleware which is actually just a stack of
useful tools.
`stack` is self-flattening, i.e.
```jl
stack(a, b, c, d) == stack(a, stack(b, c), d) == stack(stack(a, b, c), d)
```
etc.
### Branching
Mux.jl goes further with middleware, and expresses routing and decisions
as middleware themselves. The key to this is the `branch` function,
which takes
1. a predicate to apply to the incoming request, and
2. an endpoint to run on the request if the predicate returns true.
For example:
```jl
mux(branch(_ -> rand() < 0.1, respond("Hello")),
respond("Hi"))
```
In this example, we ignore the request and simply return true 10% of the time.
You can test this in the repl by calling
```jl
mux(branch(_ -> rand() < 0.1, respond("Hello")),
respond("Hi"))(nothing)
```
(since the request is ignored anyway, it doesn't matter if we set it to `nothing`).
We can also define a function to wrap the branch
```jl
probability(x, app) = branch(_ -> rand() < x, app)
```
### Utilities
Despite the fact that endpoints and middleware are so important in Mux,
it's common to not write them by hand. For example, `respond("hi")`
creates a function `_ -> "hi"` which can be used as an endpoint.
Equally, functions like `status(404)` will create middleware which
applies the given status to the response. Mux.jl's "not found" endpoint
is therefore defined as
```jl
notfound(s = "Not found") = mux(status(404), respond(s))
```
which is a much more declarative approach.
For example:
* `respond(x)` – creates an endpoint that responds with `x`, regardless of the request.
* `route("/path/here", app)` – branches to `app` if the request location matches `"/path/here"`.
* `page("/path/here", app)` – branches to `app` if the request location *exactly* matches `"/path/here"`
## Serving static files from a package
Please use [AssetRegistry.jl](https://github.com/JuliaGizmos/AssetRegistry.jl) to
register an assets directory.
**DEPRECATED**: The `Mux.pkgfiles` middleware (included in `Mux.defaults`) serves static
files under the `assets` directory in any Julia package at `/pkg//`.
## Integrate with WebSocket
You can easily integrate a general HTTP server and a WebSocket server with Mux.
To do so, define two apps, one for regular HTTP requests, and another that will handle WebSocket connections.
Here is a complete example:
```julia
using Mux
using Mux.WebSockets: send
# HTTP Server
@app h = (
Mux.defaults,
page("/", respond("
Hello World!
")),
Mux.notfound());
function websocket_example(x)
sock = x[:socket]
for str in sock
println("client -> server: ", str)
send(sock, "I'm hard of hearing, did you say '$str'?")
end
end
# WebSocket server
@app w = (
Mux.wdefaults,
route("/ws_io", websocket_example),
Mux.wclose,
Mux.notfound());
# Serve both servers on the same port.
serve(h, w, 2333)
```
And finally, run a client, optionally in another process:
```julia
using Mux.WebSockets
WebSockets.open("ws://localhost:2333/ws_io") do ws
send(ws, "Hello World!")
data = receive(ws)
println(stderr, "server -> client: ", data)
end;
```
Now, if you run both programs, you'll see two `Hello World` messages, as the
server sends the same message back to the client.
## Using Mux in Production
While Mux should be perfectly useable in a Production environment, it is not
recommended to use the `Mux.defaults` stack for a Production application. The
`basiccatch` middleware it includes will dump potentially sensitive stacktraces
to the client on error, which is probably not what you want to be serving to
your clients! An alternative `Mux.prod_defaults` stack is available for
Production applications, which is just `Mux.defaults` with a `stderrcatch`
middleware instead (which dumps errors to stderr).