Rack provides a minimal interface between webservers supporting Ruby and Ruby frameworks. Ruby web frameworks,(Rails, Sinatra, etc.), are built on top of Rack.
RubyOnRails has the concept of Middleware which is a way of intercepting and processing an HTTP Request/Request. All Middleware are Rack apps.
By the end of this, students should be able to:
- Understand URL encoding, i.e. using special codes for some characters in a URL.
- Understand how a HTML Form and it's query string are processed and converted into a params hash.
- Understand how a HTTP Request is routed using it's resource path.
- Introduce Model View Controller.
This mimimal app will show the current time. That's all
To use Rack, provide an "app": an object that responds to the call method, taking the environment hash as a parameter, and returning an Array with three elements:
- The HTTP response code
- A Hash of HTTP Response headers
- The response body, which must respond to each
We'll use a lambda here that will return an array with three entries, the entries will be:
- A HTTP Status Code of 200. 200 is the code for OK.
- A Ruby hash that contains the HTTP Response Header fields.
- The body of HTTP Request.
This Rack app will run the WEBrick server on port 1234 listening for Requests.
touch rack/simplest.rb
Add the below code:
require 'rubygems'
require 'rack'
app = lambda{ |env| [200, {'Content-Type' => 'text/plain'}, ["The time is #{Time.now}"] ] }
Rack::Handler::WEBrick.run app, Port: 1234
ruby rack/simplest.rb
curl -v http://localhost:1234
Open the Chrome Inspector and go to the Network tab. Refresh and look at the HTTP Request/Response.
- Create a rack app that will return HTML with your name and current form of transportation,(auto, MBTA, ...).
- Use a browser and curl to request this page.
Rack puts all the Request Headers, and other info, inside the env argument that is passed to the lambda. This env argument is a Ruby Hash.
Now we can implement all kinds of business logic that can depend on the HTTP Request's path, the Query String, etc.
In this case we're just going to build a string from some of this info found in the env hash. Then we will send it back to the client as a HTTP Repsonse.
touch rack/show_http_headers.rb
Add this code.
require 'rubygems'
require 'rack'
# Return the contents of the env passed to this rack app.
app = lambda do |env|
# HTTP Request Header
response = []
response << "Method: #{env['REQUEST_METHOD']}"
response << "Request URI: #{env['REQUEST_URI']}"
response << "Query String: #{env['QUERY_STRING']}"
response << "Request Path: #{env['REQUEST_PATH']}"
response << "Accept: #{env['HTTP_ACCEPT']}"
response << "Accept Language: #{env['HTTP_ACCEPT_LANGUAGE']}"
response << "User Agent: #{env['HTTP_USER_AGENT']}"
response << "URL Scheme: #{env['rack.url_scheme']}\n"
[200, {'Content-Type' => 'text/plain'}, [response.join("\n")] ]
end
Rack::Handler::WEBrick.run app, Port: 1234
curl -i http://localhost:1234/this/is/my/path?name=jack&age=33
In Chrome.
http://localhost:1234/this/ia/my/path?name=jack&age=33
Query Strings are a way to send data to the back end server over HTTP. For example:
http://localhost:1234/this/ia/my/path?name=jack&age=33
The query string is 'name=jack&age=33'
It's made up of key value pairs where:
- The entire query string is separated, delimited, by the question character, '?'.
- Each key value pair is seperated by the ampersand character, '&'
- The key value are seperated by the equals sign, '='.
- Some characters, such as spaces, are URL Encoded. URL Encoding
##Lab Create a Rack app that will print to standard output, yes use puts, each key value pair in the query string.
name is jack
age is 33
And it will return an HTML page showing the name and age.
On the command line:
curl -v 'http://localhost:1234?name=jack&age=33'
The query string can be used with either a GET or POST Request.
When using a GET request the query string will be appended to the end of the URL. Careful using this, there are limits on the length of a URL
curl -v 'http://localhost:1234/some/path/here?name=tom&age=57&height=74'
When using a POST request the query string will typically reflect the contents of an HTML Form. And it will be send to the server in the body of the request.
curl -v --data 'name=tom&age=57&height=74' 'http://localhost:1234/some/path/here'
Create a rack app that returns a HTML form asking for a name and age at the URL http://localhost:1234/users/new.
touch rack users_app.rb
require 'rubygems'
require 'rack'
require 'pry-byebug'
# HTML Snippets
@header = <<-END.gsub(/^ {2}/, '')
<html>
<head>
</head>
<body>
END
@footer = <<-END.gsub(/^ {2}/, '')
</body>
</html>
END
@new_user_form_html = <<-END.gsub(/^ {2}/, '')
<form action="/users" method='POST'>
Name: <input type="text" name="name"><br>
Age: <input type="text" age="age"><br>
<input type="submit" value="Submit">
</form>
END
app = lambda do |env|
body = @header
status = 500
if '/users/new' == env['REQUEST_PATH']
# show the form to create a new user
body << @new_user_form_html
status = 200
else
body = "<h1>Unknown route</h1>"
end
body << @footer
[status, {'Content-Type' => 'text/html'}, [body] ]
end
Rack::Handler::WEBrick.run app, Port: 1234
When the form is submitted a HTTP POST will be sent to localhost:1234/user.
The Query String that contains the submitted fields will be in the body of the HTTP Request. So we need a way to get these fields.
We are going to put these submitted fields in a params hash
In Rails we will be dealing a lot with something called a params hash. This is just a Ruby Hash that reflects the contents of a query string.
Lets create a method that will create a params hash from a POST and GET.
Update the rack/users_app.rb
# get the query string for GET and POST requests
def query_str(env)
http_method = env['REQUEST_METHOD']
http_method = env['REQUEST_METHOD']
if http_method == 'GET'
env['QUERY_STRING']
elsif http_method == 'POST'
env['rack.input'].gets
end
end
# Given a URL query string create a hash of key value pairs
def create_params(env)
params = {}
query_str(env).split('&').each do |str_entry|
key, value = str_entry.split('=')
params[key.to_sym] = value
end
params
end
app = lambda do |env|
body = @header
status = 500
params = create_params(env)
Handle the Form POST.
- Add a condition to handle a POST to /users. This is a route
- Create a User class with a name and age attribute.
- Create an instance of this class using the form fields.
- Return HTML that shows this user's name and age.
# handle the POST Request from the form!
elsif '/users' == env['REQUEST_PATH'] && 'POST' == env['REQUEST_METHOD']
# create a new user
user = User.new(params[:name], params[:age])
body = ...
status = 200
Write a simple Rack app, based on the above, that will fill in the blanks in the below text from URL params. The filled in text will be returned by the HTTP Response. The words in bold are going to be replaced by URL params.
"Who arrested name for crime earlier this week, and according to a report by the source, the incident stemmed from a dispute over a object."
Example:
"Police arrested a Main Street resident for illegal possession of a handgun and ammunition earlier this week, and according to a report in the Lowell Sun, the incident stemmed from a dispute over a frozen cheese pizza."
curl -v 'http://localhost:1234/?who=The Police&name=a Main Street resident&crime=possesion of a handgun&source=in the Lowell Sun&object=frozen cheess pizza'
Use BOTH a HTTP POST and GET.
Look thru the rack/mini_mvc.rb and understand the router and controller code.