Basic Routing in Crystal HTTP Server
Crystal official website offers a simple example on how to initiate an http server. This post brings it a little further by implementing basic routing functionality.
Fetching Request Path
The idea of routing is based on the requested path. Thus we can make use of request
parameter in the block to fetch the parsed request object.
server = HTTP::Server.new(8080) do |request|
puts request.path
HTTP::Response.ok("text/plain","Hello World!")
end
server.listen
We start the server ,visit /
, and get a string:
"/"
If we visit /app
then we get another string:
"/app"
Adding Routes
Now we know that visiting different path will pass in the corresponding pathname, so a Hash
would be good to store the path and responding content.
@routes = {} of String => String
@routes["/"] = "This is index"
@routes["/app"] = "This is app"
The syxtax is quite self-expressive. We visit /
and will get "This is index"
string.
Then we check the incoming request path and respond with the content.
server = HTTP::Server.new(port) do |request|
if @routes.has_key?(request.path.to_s)
HTTP::Response.ok("text/plain",@routes[request.path.to_s])
else
HTTP::Response.not_found
end
end
And we're done!
Sinatra Style Routing
Let's bring it a little further. If you ever tried any simple web framework like Sintra or Frank, you will be familiar with the following syntax:
get "/" do
@text = "hello world"
end
This can be achieved by using Proc. Check the comments for instructions in the following file.
require "http/server"
module BasicHttp
class Base
def initialize
# allow @routes to save Proc as its value
@routes = {} of String => ( -> String)
end
def run
server = HTTP::Server.new(8080) do |request|
if @routes.has_key?(request.path.to_s)
# add call method to proc when returned
HTTP::Response.ok("text/plain",@routes[request.path.to_s].call)
else
HTTP::Response.not_found
end
end
server.listen
end
# add method to dynamically add routes
def get(route, &block : ( -> String))
@routes[route.to_s] = block
end
end
end
app = BasicHttp::Base.new
# the app will respond with the returned string
app.get "/" do
"hello world"
end
# you can also exec code in block and return a string value
app.get "/app" do
a = "hello"
b = "world"
"#{a} #{b}"
end
app.run
At first sight the file may look a little complicated, but in fact we just add several methods to the original example. The whole idea can be summarized as:
Add a
@routes
variables to store paths and responsesAdd a
get
method to allow dynamic routingWhen responding, use
call
method to execute code block defined byget
methodCreate a new server instance, add routes, and start listening
And we're done!