Crystal comes with a module HTTP::Handler, which is used as middleware for your HTTP Server stack.
These are classes you create that include the HTTP::Handler
module to process incoming requests and return responses.
Lucky comes with some built-in handlers that you can see in your src/app_server.cr
file under the middleware
method.
The order of this middleware stack is the order Lucky will send a web request with the first in the stack getting the initial request, and the last finishing out the stack.
By default when you generate a full application, your middleware stack will look like this:
def middleware : Array(HTTP::Handler)
[
Lucky::ForceSSLHandler.new,
Lucky::HttpMethodOverrideHandler.new,
Lucky::LogHandler.new,
Lucky::ErrorHandler.new(action: Errors::Show),
Lucky::RemoteIpHandler.new,
Lucky::RouteHandler.new,
Lucky::StaticCompressionHandler.new("./public", file_ext: "gz", content_encoding: "gzip"),
Lucky::StaticFileHandler.new("./public", fallthrough: false, directory_listing: false),
Lucky::RouteNotFoundHandler.new,
] of HTTP::Handler
end
The request
object will start in the Lucky::ForceSSLHandler
, do some processing, then move on to the Lucky::LogHandler
, and so on.
In the Lucky::RouteHandler
it will look for a Lucky::Action
that matches the request, run any pipes, then run the action. If no action is found,
we check to see if there’s a static file in Lucky::StaticFileHandler
. Finally, if no route or file matches the request, we run the Lucky::RouteNotFoundHandler
.
Your application may have special requirements like routing legacy URLs, sending bug reporting, CORS, or even doing HTTP Basic auth while your app is in beta. Whatever your use case, creating a custom handler is really easy!
First we start off by creating a new directory where we can place all of our custom handlers. Create a src/handlers/
directory and be sure to require it src/app.cr
.
Next, your handler needs 3 things:
HTTP::Handler
call
method with an argument of context : HTTP::Server::Context
call_next(context)
to go to the next handler in the stack# src/handlers/legacy_redirect_handler.cr
class LegacyRedirectHandler
include HTTP::Handler
LEGACY_ROUTES = {"/old-path" => "/new-path"}
def call(context : HTTP::Server::Context)
if new_path = LEGACY_ROUTES[context.request.path]?
context.response.status_code = 301
context.response.headers["Location"] = new_path
else
# Go to the next handler in the stack
call_next(context)
end
end
end
Lastly, we need to make sure our new custom handler is in our stack. Open up src/app_server.cr
and place a new instance in the stack!
# src/app_server.cr
#...
class App < Lucky::BaseAppServer
def middleware
[
Lucky::HttpMethodOverrideHandler.new,
Lucky::LogHandler.new,
LegacyRedirectHandler.new, # Add this line
Lucky::ErrorHandler.new(action: Errors::Show),
Lucky::RouteHandler.new,
Lucky::StaticFileHandler.new("./public", false),
Lucky::RouteNotFoundHandler.new,
]
end
end
Note: The order of custom handlers will be completely up to you, but keep in mind the order Lucky placed the built-in handlers.