Skip to content

Commit

Permalink
Update docs for new Handler types
Browse files Browse the repository at this point in the history
  • Loading branch information
quinnj committed Aug 29, 2017
1 parent 6cfc617 commit 2197c13
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 29 deletions.
7 changes: 6 additions & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ HTTP.Client
HTTP.Connection
```

## Server
## Server / Handlers
```@docs
HTTP.serve
HTTP.Server
HTTP.Handler
HTTP.HandlerFunction
HTTP.Router
HTTP.register!
HTTP.FourOhFour
```

## HTTP Types
Expand Down
89 changes: 61 additions & 28 deletions src/handlers.jl
Original file line number Diff line number Diff line change
@@ -1,40 +1,59 @@
module Handlers

function val(v)
@static if VERSION < v"0.7.0-DEV.1395"
Val{v}()
else
Val(v)
end
end

export handle, Handler, HandlerFunction, Router, register!

using HTTP

"""
handle(handler::Handler, request, response) => Response
Function used to dispatch to a Handler. Called from the core HTTP.serve method with the
initial Handler passed to `HTTP.serve(handler=handler)`.
"""
function handle end
handle(handler, req, resp, vals...) = handle(handler, req, resp)

"""
Abstract type representing an object that knows how to "handle" a server request.
Types of handlers include `HandlerFunction` (a julia function of the form `f(request, response`) and
`Router` (which pattern matches request url paths to other specific `Handler` types).
"""
abstract type Handler end

"""
HandlerFunction(f::Function)
A Function-wrapper type that is a subtype of `Handler`. Takes a single Function as an argument.
The provided argument should be of the form `f(request, response) => Response`, i.e. it accepts
both a `Request` and `Response` and returns a `Response`.
"""
struct HandlerFunction{F <: Function} <: Handler
func::F # func(req, resp)
end

handle(h::HandlerFunction, req, resp) = h.func(req, resp)

"A default 404 Handler"
const FourOhFour = HandlerFunction((req, resp) -> Response(404))

"""
A Router maps urls/paths to Handlers.
/ f(m, s, h, args...) # default mapping, matches all requests, returns 404
/api f(::Val{:api}, args...)
/api/social f(::Val{:api}, ::Val{:social}, args...)
/api/social/v4
/api/social/v4/alerts
/api/social/v4/alerts/* f(::Val{:api}, ::Val{:social}, ::Val{:v4}, ::Val{:alerts}, ::Any)
/api/social/v4/alerts/1/evaluate
/test f(::Val{:test})
/test/{var::String} f(::Val{:test}, ::Any)
/test/sarv/ghotra f(::Val{:test}, ::Val{:sarv}, ::Val{:ghotra})
scheme, subdomain, host, method, path
Router(h::Handler)
Router(f::Function)
Router()
An `HTTP.Handler` type that supports mapping request url paths to other `HTTP.Handler` types.
Can accept a default `Handler` or `Function` that will be used in case no other handlers match; by
default, a 404 response handler is used.
Paths can be mapped to a handler via `HTTP.register!(r::Router, path, handler)`, see `?HTTP.register!` for more details.
"""
struct Router <: Handler
segments::Dict{String, Val}
Expand All @@ -52,17 +71,31 @@ struct Router <: Handler
end
end

if VERSION < v"0.7.0-DEV.1395"
Val(v) = Val{v}()
end

const SCHEMES = Dict{String, Val}("http" => Val(:http), "https" => Val(:https))
const SCHEMES = Dict{String, Val}("http" => val(:http), "https" => val(:https))
const METHODS = Dict{String, Val}()
for m in instances(HTTP.Method)
METHODS[string(m)] = Val(Symbol(m))
METHODS[string(m)] = val(Symbol(m))
end
const EMPTYVAL = Val(())
const EMPTYVAL = val(())

"""
HTTP.register!(r::Router, url, handler)
HTTP.register!(r::Router, m::Union{Method, String}, url, handler)
Function to map request urls matching `url` and an optional method `m` to another `handler::HTTP.Handler`.
URLs are registered one at a time, and multiple urls can map to the same handler.
Methods can be passed as a string `"GET"` or enum object directly `HTTP.GET`.
The URL can be passed as a String or `HTTP.URI` object directly. Requests can be routed based on: method, scheme,
hostname, or path.
The following examples show how various urls will direct how a request is routed by a server:
- `"http://*"`: match all HTTP requests, regardless of path
- `"https://*"`: match all HTTPS requests, regardless of path
- `"google"`: regardless of scheme, match requests to the hostname "google"
- `"google/gmail"`: match requests to hostname "google", and path starting with "gmail"
- `"/gmail"`: regardless of scheme or host, match any request with a path starting with "gmail"
- `"/gmail/userId/*/inbox`: match any request matching the path pattern, "*" is used as a wildcard that matches any value between the two "/"
"""
register!(r::Router, url, handler) = register!(r, "", url, handler)
register!(r::Router, m::Method, url, handler) = register!(r, string(m), url, handler)

Expand All @@ -71,7 +104,7 @@ function register!(r::Router, method::String, url, handler)
# get scheme, host, split path into strings & vals
uri = url isa String ? HTTP.URI(url) : url
s = HTTP.scheme(uri)
sch = HTTP.hasscheme(uri) ? typeof(get!(SCHEMES, s, Val(s))) : Any
sch = HTTP.hasscheme(uri) ? typeof(get!(SCHEMES, s, val(s))) : Any
h = HTTP.hashostname(uri) ? Val{Symbol(HTTP.hostname(uri))} : Any
hand = handler isa Function ? HandleFunction(handler) : handler
register!(r, m, sch, h, HTTP.path(uri), hand)
Expand All @@ -83,7 +116,7 @@ function splitsegments(r::Router, h::Handler, segments)
if s == "*" #TODO: or variable, keep track of variable types and store in handler
T = Any
else
v = Val(Symbol(s))
v = val(Symbol(s))
r.segments[s] = v
T = typeof(v)
end
Expand All @@ -104,11 +137,11 @@ end

function handle(r::Router, req, resp)
# get the url/path of the request
m = Val(HTTP.method(req))
m = val(HTTP.method(req))
uri = HTTP.uri(req)
# get scheme, host, split path into strings and get Vals
s = get(SCHEMES, HTTP.scheme(uri), EMPTYVAL)
h = Val(Symbol(HTTP.hostname(uri)))
h = val(Symbol(HTTP.hostname(uri)))
p = HTTP.path(uri)
segments = split(p, '/'; keep=false)
# dispatch to the most specific handler, given the path
Expand Down

0 comments on commit 2197c13

Please sign in to comment.