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:
I've labeled the lines with letters so I can walk you through a regular request process:
- 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.
- 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.
- Once my computer accepts it, my web application gets it on line (D), and my Ruby code runs the index.GET handler.
- The response comes out of my Ruby server when I return it, and it goes back to your browser over line (D) again.
- The server running this site takes the response off line (D), then sends it back over the internet on line (C).
- 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).
- 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:
- 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.
- 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.
- 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.
- 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.
- 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.
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:
The changes I've made are:
- Make the get '/hello/' handler simply return a :hello_form page. We'll make that next.
- 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
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
A form then consists of the following:
- 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.
- Text like you might put in another HTML tag, but also...
- <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.
- 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.
- 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
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
We can also do the same for the views/html_form.erb file:
1 2 3 4 5 6 7 8 9
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:
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]
- 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.
- 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.
- 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.
- 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.
- 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.