Exercise 51: Getting Input from a Browser

While it's exciting to see the browser display "Hello World," it's even more exciting to let the user submit text to your application from a form. In this exercise we'll improve our starter web application by using forms and storing information about users into their "sessions."

How the Web Works

Time for some boring stuff. You need to understand a bit more about how the web works before you can make a form. This description isn't complete, but it's accurate and will help you figure out what might be going wrong with your application. Also, creating forms will be easier if you know what they do.

I'll start with a simple diagram that shows you the different parts of a web request and how the information flows:

_static/python/http_flow_graphic.png

I've labeled the lines with letters so I can walk you through a regular request process:

  1. You type in the url http://test.com// into your browser, and it sends the request on line (A) to your computer's network interface.
  2. Your request goes out over the internet on line (B) and then to the remote computer on line (C) where my server accepts the request.
  3. Once my computer accepts it, my web application gets it on line (D), and my Ruby code runs the index.GET handler.
  4. The response comes out of my Ruby server when I return it, and it goes back to your browser over line (D) again.
  5. The server running this site takes the response off line (D), then sends it back over the internet on line (C).
  6. The response from the server then comes off the internet on line (B), and your computer's network interface hands it to your browser on line (A).
  7. Finally, your browser then displays the response.

In this description there are a few terms you should know so that you have a common vocabulary to work with when talking about your web application:

Browser
The software that you're probably using every day. Most people don't know what a browser really does. They just call browsers "the internet." Its job is to take addresses (like http://test.com/) you type into the URL bar, then use that information to make requests to the server at that address.
Address
This is normally a URL (Uniform Resource Locator) like http://test.com// and indicates where a browser should go. The first part, http, indicates the protocol you want to use, in this case "Hyper-Text Transport Protocol." You can also try ftp://ibiblio.org/ to see how "File Transport Protocol" works. The http://test.com/ part is the "hostname," a human readable address you can remember and which maps to a number called an IP address, similar to a telephone number for a computer on the internet. Finally, URLs can have a trailing path like the /book/ part of http://test.com//book/, which indicates a file or some resource on the server to retrieve with a request. There are many other parts, but those are the main ones.
Connection
Once a browser knows what protocol you want to use (http), what server you want to talk to (http://test.com/), and what resource on that server to get, it must make a connection. The browser simply asks your operating system (OS) to open a "port" to the computer, usually port 80. When it works, the OS hands back to your program something that works like a file, but is actually sending and receiving bytes over the network wires between your computer and the other computer at http://test.com/. This is also the same thing that happens with http://localhost:8080/, but in this case you're telling the browser to connect to your own computer (localhost) and use port 8080 rather than the default of 80. You could also do http://test.com:80/ and get the same result, except you're explicitly saying to use port 80 instead of letting it be that by default.
Request
Your browser is connected using the address you gave. Now it needs to ask for the resource it wants (or you want) on the remote server. If you gave /book/ at the end of the URL, then you want the file (resource) at /book/, and most servers will use the real file /book/index.html but pretend it doesn't exist. What the browser does to get this resource is send a request to the server. I won't get into exactly how it does this, but just understand that it has to send something to query the server for the request. The interesting thing is that these "resources" don't have to be files. For instance, when the browser in your application asks for something, the server is returning something your Ruby code generated.
Server
The server is the computer at the end of a browser's connection that knows how to answer your browser's requests for files/resources. Most web servers just send files, and that's actually the majority of traffic. But you're actually building a server in Ruby that knows how to take requests for resources and then return strings that you craft using Ruby. When you do this crafting, you are pretending to be a file to the browser, but really it's just code. As you can see from Exercise 50, it also doesn't take much code to create a response.
Response
This is the HTML (CSS, JavaScript, or images) your server wants to send back to the browser as the answer to the browser's request. In the case of files, it just reads them off the disk and sends them to the browser, but it wraps the contents of the disk in a special "header" so the browser knows what it's getting. In the case of your application, you're still sending the same thing, including the header, but you generate that data on the fly with your Ruby code.

That is the fastest crash course in how a web browser accesses information on servers on the internet. It should work well enough for you to understand this exercise, but if not, read about it as much as you can until you get it. A really good way to do that is to take the diagram and break different parts of the web application you did in Exercise 50. If you can break your web application in predictable ways using the diagram, you'll start to understand how it works.

How Forms Work

The easiest way to see how forms work is to create one. Change your bin/app.rb code to be this now:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
require 'sinatra'

set :port, 8080
set :static, true
set :public_folder, "static"
set :views, "views"

get '/' do
    return 'Hello world'
end

get '/hello/' do
    erb :hello_form
end

post '/hello/' do
    greeting = params[:greeting] || "Hi There"
    name = params[:name] || "Nobody"

    erb :index, :locals => {'greeting' => greeting, 'name' => name}
end

The changes I've made are:

  1. Make the get '/hello/' handler simply return a :hello_form page. We'll make that next.
  2. Create a different hanlder that is post '/hello/' which looks similar to the previous version of this code, except we start with post to indicate we'll be receiving a form, and then we also accept a name parameter. We'll update the index.erb to match this next.

Once you have that, create this views/index.erb file for the post '/hello/' handler to use:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<html>
    <head>
        <title>Gothons Of Planet Percal #25</title>
    </head>
<body>

<p>
I just wanted to say <em style="color: green; font-size: 2em;"><%= greeting %>, <%= name %></em>.
</p>

</body>
</html>

Which only adds the name variable to the end of our greeting. Make sure you see how this name variable is now in the post '/hello/' handler and now passed to the erb :index call there.

The secret to making this work though is the :hello_form (aka views/hello_form.erb) which we'll cover next.

Creating HTML Forms

The last piece of this form puzzle is to make the form in views/hello_form.erb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<html>
    <head>
        <title>Sample Web Form</title>
    </head>
<body>

<h1>Fill Out This Form</h1>

<form action="/hello/" method="POST">
    A Greeting: <input type="text" name="greeting">
    <br/>
    Your Name: <input type="text" name="name">
    <br/>
    <input type="submit">
</form>

</body>
</html>

A form then consists of the following:

  1. The <form> tag starts it off and says where to deliver this form. In this case it's to action="/hello/" which is our post '/hello/' handler, and method="POST" which tells the browser to use this mechanism.
  2. Text like you might put in another HTML tag, but also...
  3. <input> tags give the type of input fields we want, and the parameters to use. In this case we have two, one with name="greeting" for our params[:greeting] parameters, and name="name" for our params[:name] parameter.
  4. These parameters are then mapped in our post '/hello/' code to create the greeting and name variables which get passed as :locals to the erb :index call.
  5. Finally, the file views/index.erb gets these variables and it prints them.

Creating a Layout Template

As programmers we have to find common patterns and try to automate them away. One common pattern is the HTML that is at the beginning and the end of each of our .erb files. You shouldn't have to type that every single time you want to create a new view, and you should be able to change that content in one place to change all the pages. The solution to this is a concept called a "layout template", which we'll create in views/layout.erb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<html>
<head>
    <title>Gothons From Planet Percal #25</title>
</head>
<body>

<%= yield %>

</body>
</html>

This simply takes the common HTML at the top and bottom of every template and puts it into one file. The code <%= yield %> is a Ruby thing that says to stop there and run the other view then come back. By putting this into views/layout.erb we're telling Sinatra to "wrap" all of our templates with this HTML.

We can now write abbreviated HTML for the views/index.erb file:

1
2
3
<p>
I just wanted to say <em style="color: green; font-size: 2em;"><%= greeting %>, <%= name %></em>.
</p>

We can also do the same for the views/html_form.erb file:

1
2
3
4
5
6
7
8
9
<h1>Fill Out This Form</h1>

<form action="/hello/" method="POST">
    A Greeting: <input type="text" name="greeting">
    <br/>
    Your Name: <input type="text" name="name">
    <br/>
    <input type="submit">
</form>

Change your files to match these by deleting the same content and then reload. Use your browser's View Source feature to see that, even though you do not have the contents of views/layout.erb in your new views, it still shows up in the resulting pages.

Writing Automated Tests for Forms

You can also automate the testing of your web application using Rack::Test. First install the gem for it:

$ sudo gem install rack-test

Then write a test file in tests/test_gothonweb.rb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
require './bin/app.rb'
require 'test/unit'
require 'rack/test'

class MyAppTest < Test::Unit::TestCase
  include Rack::Test::Methods

  def app
    Sinatra::Application
  end

  def test_my_default
    get '/'
    assert_equal 'Hello world', last_response.body
  end

  def test_hello_form
    get '/hello/'
    assert last_response.ok?
    assert last_response.body.include?('A Greeting')
  end

  def test_hello_form_post
    post '/hello/', params={:name => 'Frank', :greeting => "Hi"}
    assert last_response.ok?
    assert last_response.body.include?('I just wanted to say')
  end
end

This file is simply pretending to be a web browser, and it looks similar to how the Sinatra handlers are, but written as if you were telling a browser to visit your webapplication with code. To run this test do what you normally do:

$ rake test
/usr/local/bin/ruby -I"lib:tests" -I"/usr/local/lib/ruby/2.1.0" "/usr/local/lib/ruby/2.1.0/rake/rake_test_loader.rb" "tests/test_gothonweb.rb"
Run options:

# Running tests:

Finished tests in 0.028189s, 106.4245 tests/s, 177.3742 assertions/s.
3 tests, 5 assertions, 0 failures, 0 errors, 0 skips

ruby -v: ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin11.0]

Study Drills

  1. Read even more about HTML, and give the simple form a better layout. It helps to draw what you want to do on paper and then implement it with HTML.
  2. This one is hard, but try to figure out how you'd do a file upload form so that you can upload an image and save it to disk.
  3. This is even more mind-numbing, but go find the HTTP RFC (which is the document that describes how HTTP works) and read as much of it as you can. It is really boring but comes in handy once in a while.
  4. This will also be really difficult, but see if you can find someone to help you setup a web server like Apache, Nginx, or thttpd. Try to serve a couple of your .html and .css files with it just to see if you can. Don't worry if you can't. Web servers kind of suck.
  5. Take a break after this and just try making as many different web applications as you can. You should definitely read about sessions in Sinatra so you can understand how to keep state for a user.

Common Student Questions

Why don't my tests run?
Remember, do not change to the tests directory. If you do cd tests to run your tests, you are doing it wrong.

Buy DRM-Free

When you buy directly from the author, Zed A. Shaw, you'll get a professional quality PDF and hours of HD Video, all DRM-free and yours to download.

$29.99

Buy Directly From The Author

Or, you can read Learn Ruby the Hard Way for free right here, video lectures not included.