Creating a Basic Web App
Now that we are done going over the basics of HTTP, let's create a simple but useful web application in Go.
Pulling from our fileserver program that we implemented last chapter, we will
implement a Markdown generator using the github.com/russross/blackfriday
package.
HTML Form
For starters, we will need a basic HTML form for the markdown input:
<html>
<head>
<link href="/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="page-title">
<h1>Markdown Generator</h1>
<p class="lead">Generate your markdown with Go</p>
<hr />
</div>
<form action="/markdown" method="POST">
<div class="form-group">
<textarea class="form-control" name="body" cols="30" rows="10"></textarea>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary pull-right" />
</div>
</form>
</div>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>
Put this HTML into a file named index.html
in the "public" folder of our application
and the bootstrap.min.css
from http://getbootstrap.com/ in the "public/css" folder.
Notice that the form makes an HTTP POST to the "/markdown" endpoint of our
application. We don't actually handle that route right now, so let's add it.
The "/markdown" route
The program to handle the '/markdown' route and serve the public index.html
file looks like this:
package main
import (
"net/http"
"github.com/russross/blackfriday"
)
func main() {
http.HandleFunc("/markdown", generateMarkdown)
http.Handle("/", http.FileServer(http.Dir("public")))
http.ListenAndServe(":8080", nil)
}
func generateMarkdown(rw http.ResponseWriter, r *http.Request) {
markdown := blackfriday.MarkdownCommon([]byte(r.FormValue("body")))
rw.Write(markdown)
}
Let's break it down into smaller pieces to get a better idea of what is going on.
http.HandleFunc("/markdown", generateMarkdown)
http.Handle("/", http.FileServer(http.Dir("public")))
We are using the http.HandleFunc
and http.Handle
methods to define some
simple routing for our application. It is important to note that calling
http.Handle
on the "/" pattern will act as a catch-all route, so we define
that route last. http.FileServer
returns an http.Handler
so we use
http.Handle
to map a pattern string to a handler. The alternative method,
http.HandleFunc
, uses an http.HandlerFunc
instead of an http.Handler
.
This may be more convenient, to think of handling routes via a function
instead of an object.
func generateMarkdown(rw http.ResponseWriter, r *http.Request) {
markdown := blackfriday.MarkdownCommon([]byte(r.FormValue("body")))
rw.Write(markdown)
}
Our generateMarkdown function implements the standard http.HandlerFunc
interface and renders HTML from a form field containing
markdown-formatted text. In this case, the content is retrieved
with r.FormValue("body")
. It is very common to get input from the
http.Request
object that the http.HandlerFunc
receives as an argument.
Some other examples of input are the r.Header
, r.Body
, and r.URL
members.
We finalize the request by writing it out to our http.ResponseWriter
. Notice
that we didn't explicitly send a response code. If we write out to the response
without a code, the net/http
package will assume that the response is a 200
OK
. This means that if something did happen to go wrong, we should set the
response code via the rw.WriteHeader()
method.
http.ListenAndServe(":8080", nil)
The last bit of this program starts the server, we pass nil
as our handler,
which assumes that the HTTP requests will be handled by the net/http
packages
default http.ServeMux
, which is configured using http.Handle
and
http.HandleFunc
, respectively.
And that is all you need to be able to generate markdown as a service in Go. It is a surprisingly small amount of code for the amount of heavy lifting it does. In the next chapter we will learn how to deploy this application to the web using Heroku.