A Julia library defining the specification and providing the data-types for HTTP servers. It also provides a rudimentary BasicServer that can respond to simple HTTP requests. The spec and the server are in very active development and some breaking changes should be expected along the way.
Besides the spec and server it provides the Ocean library for more easily building your own apps on top of the HTTP.jl spec (ie. Ocean apps can be run on BasicServer with just one line of code).
Pkg.add("HTTP")
The first component of HTTP.jl is the HTTP
module. This provides the base Request
, Response
, and Cookie
types as well as some basic helper methods for working with those types (however, actually parsing requests into Requests
s is left to server implementations). Check out HTTP.jl for the actual code; it's quite readable.
HTTP.Util (in HTTP/Util.jl) also provides some helper methods for escaping and unescaping data.
See the BasicServer example to get an idea of the function API BasicServer expects (it's a lot like Rack).
The current spec is heavily inspired by Ruby's Rack specification. The parser parts of the basic server are based off of the WEBrick Ruby HTTP server.
Ocean is a Sinatra-like library for creating apps that run on the HTTP.jl API (they can currently only be served using BasicServer).
This will create a basic server on port 8000 that responds with "Hello World" to requests at /
.
require("HTTP/Ocean")
using Ocean
app = new_app()
get(app, "/", function(req, res, _)
return "Hello World"
end)
BasicServer.bind(8000, binding(app), true)
You can also use Ocean without mucking up your scope with using
:
require("HTTP/Ocean")
app = Ocean.app()
Ocean.get(app, "/", function(req, res, _)
return "Hello World"
end)
BasicServer.bind(8000, Ocean.binding(app), true)
To capture route parameters format your route as a regular expression with capture groups. For example:
Ocean.get(app, r"^/(.+)", function(req, res, _)
return _.params[1]
end)
A GET request to /test
would give the response test
.
If the route path is a string instead of a regex then _.params
will be false
.
A new (and possibly buggy) feature is parameterized routes. You can create a parameterized route path by calling Ocean.pr
(or Ocean.param_route
) or just doing pr"/object/:id"
(this macro-string version is currently only available if you do using Ocean
). Example:
# No-using version
Ocean.get(app, Ocean.pr("/:test"), function(req, res, _)
return _.params["test"]
end)
# Using version
get(app, pr"/:test", function(req, res, _)
return _.params["test"]
end)
Let's say we have the following POST handler:
Ocean.post(app, "/", function(req, res, _)
println(req.data)
return "foobar"
end)
POSTing the data test=testing
would result in something like {"test"=>["testing"]}
being printed to output. Of note is that the value is an array. This is because any key could have multiple values (such as the data test=testing1&test=testing2
), so for consistency any key in req.data
will map to an array of values.
Ocean provides the shorthand method gs
(for get_single
). To get the first value of the key "test"
in the data dictionary you would call v = gs(req.data, "test")
(it will return false
if the key does not exist). To access this and other utility methods just do using Ocean.Util
(look at Ocean/Util.jl to see what exactly Ocean.Util provides).
Plain multipart data (without a filename and content-type) will be accessible as a regular array of string(s) in the req.data
dict. Multipart data with a filename and content-type is stored as Multipart objects (see type definition) in the req.data
dict.
Also provided by Ocean.Util (using Ocean.Util
) is the redirect
method. This will set the Location
header in the response headers and the response status to 302 (default).
using Ocean.Util
Ocean.get(app, ..., function(req, res, _)
return redirect(res, "/")
# To do a 301 redirect:
# return redirect(res, "/", 301)
end)
Redirects are also exposed through Ocean.Extra (see Using templates below):
Ocean.get(app, ..., function(req, res, _)
return _.redirect("/")
end)
The third parameter to the request handler (assumed to be _
) is a Ocean.Extra (type definition here) object that provides some data and functions for common tasks.
The _.file(path::String)
method will return a string of the file's contents or throw an error. If the file starts with /
then it will open it as an absolute path; if it starts with any other string it will open it relative to the directory of the app file. The contents of files read are cached in app.cache
with the key _file:$path
. You can disable caching by making the second parameter false
(eg. _.file(path, false)
).
Ocean provides a method in Extra to easily access ejl (embedded Julia) and Mustache template files (for Mustache you must require the Mustache package yourself with require("Mustache")
or using Mustache
). It uses the _.file
method internally to read the path to the file; it also caches the compiled template data in app.cache
(with the keys _ejl:$path
and _mustache:$path
) so that the templates don't have to be recompiled every request.
Rendering an ejl template:
_.template(:ejl, "view.ejl", {"value" => value})
Rendering a Mustache template:
_.template(:mustache, "view.mustache", {"value" => value})
These are basic handlers to get and set cookies:
require("HTTP/Ocean")
using Calendar
using Ocean.Util
Ocean.get(app, "/", function(req, res, _)
v = gs(req.cookies, "test")
if v != false
return "Cookie: " * v
else
return "Cookie not set"
end
end)
# Expects POST data like "test=..." and assigns it to the cookie "test".
Ocean.post(app, "/", function(req, res, _)
postdata = gs(req.data, "test")
cookie = HTTP.new_cookie("test", postdata, {:expires => Calendar.now() + Calendar.years(10)})
HTTP.set_cookie(res, cookie)
return redirect(res, "/")
end)
Fork, commit, pull request! Tweet/email me or open an issue if you have any problems or ideas.
On the drawing board:
- Ocean: Better routing system (
allow for fancy routes like "/model/:id"and such) - Ocean: Improve the ejl template system
- HTTP/BasicServer/Ocean: Improve cookie system
Split up cookie system for complete and appropriate separation of concerns- Improve/clear up cookie system to make it more straightforward and consistent
- HTTP:
Middleware API- HTTP:
Session middleware
- HTTP:
- BasicServer:
File/binary upload - Client for performing HTTP requests
- Cookie and session middleware
- Simple (currently blocking) client for performing HTTP requests
- Multipart handling
- New template system
- Basic middleware system
- Add Mustache.jl template hooks
- Add cookie handling
- Add
file
,template
, andredirect
scoped methods toExtra
objects - Add
memo
method toUtil
for easy memoization of data through an associative cache - Switch to new evented socket API (and add event-loop-free version of
bind
)
- Fix error reporting
- Add cookie creation functionality
- Make
ref
for RegexMatch inOcean.Util
work properly - Add
Extra
object for route handlers
- Make
any
shortcut only create a special"ANY"
route (instead of separate routes for each request method)
- Initial version