Browse Source

Add an example app.

I'm totally not making fun of any social networks. Promise.
Carl Jackson 12 years ago
parent
commit
0c3f48afe0
5 changed files with 257 additions and 0 deletions
  1. +4
    -0
      README.md
  2. +10
    -0
      example/README.md
  3. +148
    -0
      example/main.go
  4. +47
    -0
      example/middleware.go
  5. +48
    -0
      example/models.go

+ 4
- 0
README.md View File

@ -29,6 +29,10 @@ func main() {
}
```
Goji also includes a [sample application][sample] in the `example` folder which
was artificially constructed to show off all of Goji's features. Check it out!
[sample]: https://github.com/zenazn/goji/tree/master/example
Features
--------


+ 10
- 0
example/README.md View File

@ -0,0 +1,10 @@
Gritter
=======
Gritter is an example application built using Goji, where people who have
nothing better to do can post short 140-character "greets."
A good place to start is with `main.go`, which contains a well-commented
walthrough of Goji's features. Gritter uses a couple custom middlwares, which
have been arbitrarily placed in `middleware.go`. Finally some uninteresting
"database models" live in `models.go`.

+ 148
- 0
example/main.go View File

@ -0,0 +1,148 @@
// Package example is a sample application built with Goji. Its goal is to give
// you a taste for what Goji looks like in the real world by artificially using
// all of its features.
//
// In particular, this is a complete working site for gritter.com, a site where
// users can post 140-character "greets". Any resemblance to real websites,
// alive or dead, is purely coincidental.
package main
import (
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"github.com/zenazn/goji"
"github.com/zenazn/goji/param"
"github.com/zenazn/goji/web"
)
// Note: the code below cuts a lot of corners to make the example app simple.
func main() {
// Add routes to the global handler
goji.Get("/", Root)
// Fully backwards compatible with net/http's Handlers
goji.Get("/greets", http.RedirectHandler("/", 301))
// Use your favorite HTTP verbs
goji.Post("/greets", NewGreet)
// Use Sinatra-style patterns in your URLs
goji.Get("/users/:name", GetUser)
// Goji also supports regular expressions with named capture groups.
goji.Get(regexp.MustCompile(`^/greets/(?P<id>\d+)$`), GetGreet)
// Middleware can be used to inject behavior into your app. The
// middleware for this application are defined in middleware.go, but you
// can put them wherever you like.
goji.Use("PlainText", PlainText)
// Sub-routes can be used to set custom middleware on sub-applications.
// Goji's interfaces are completely composable.
admin := web.New()
goji.Sub("/admin", admin)
// Insert the super-secure middleware into the stack above the built-in
// AutomaticOptions middleware.
admin.Insert("SuperSecure", SuperSecure, "AutomaticOptions")
// Set up admin routes. Note that sub-routes do *not* mutate the path in
// any way, so we need to supply full ("/admin" prefixed) paths.
admin.Get("/admin", AdminRoot)
admin.Get("/admin/finances", AdminFinances)
// Use a custom 404 handler
goji.NotFound(NotFound)
// Call Serve() at the bottom of your main() function, and it'll take
// care of everything else for you, including binding to a socket (with
// automatic support for systemd and Einhorn) and supporting graceful
// shutdown on SIGINT. Serve() is appropriate for both development and
// production.
goji.Serve()
}
// Root route (GET "/"). Print a list of greets.
func Root(w http.ResponseWriter, r *http.Request) {
// In the real world you'd probably use a template or something.
io.WriteString(w, "Gritter\n======\n\n")
for i := len(Greets) - 1; i >= 0; i-- {
Greets[i].Write(w)
}
}
// Create a new greet (POST "/greets"). Creates a greet and redirects you to the
// created greet.
//
// To post a new greet, try this at a shell:
// $ now=$(date +'%Y-%m-%mT%H:%M:%SZ')
// $ curl -i -d "user=carl&message=Hello+World&time=$now" localhost:8000/greets
func NewGreet(w http.ResponseWriter, r *http.Request) {
var greet Greet
// Parse the POST body into the Greet struct. The format is the same as
// is emitted by (e.g.) jQuery.params.
r.ParseForm()
err := param.Parse(r.Form, &greet)
if err != nil || len(greet.Message) > 140 {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// We make no effort to prevent races against other insertions.
Greets = append(Greets, greet)
url := fmt.Sprintf("/greets/%d", len(Greets)-1)
http.Redirect(w, r, url, http.StatusCreated)
}
// Get a given user and her greets (GET "/user/:name")
func GetUser(c web.C, w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Gritter\n======\n\n")
handle := c.UrlParams["name"]
user, ok := Users[handle]
if !ok {
http.Error(w, http.StatusText(404), 404)
return
}
user.Write(w, handle)
io.WriteString(w, "\nGreets:\n")
for i := len(Greets) - 1; i >= 0; i-- {
if Greets[i].User == handle {
Greets[i].Write(w)
}
}
}
// Get a particular greet by ID (GET "/greet/\d+"). Does no bounds checking, so
// will probably panic.
func GetGreet(c web.C, w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(c.UrlParams["id"])
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
// This will panic if id is too big. Try it out!
greet := Greets[id]
io.WriteString(w, "Gritter\n======\n\n")
greet.Write(w)
}
// Admin root (GET "/admin/root"). Much secret. Very administrate. Wow.
func AdminRoot(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Gritter\n======\n\nSuper secret admin page!\n")
}
// How are we doing? (GET "/admin/finances")
func AdminFinances(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Gritter\n======\n\nWe're broke! :(\n")
}
// 404 handler.
func NotFound(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Umm... have you tried turning it off and on again?", 404)
}

+ 47
- 0
example/middleware.go View File

@ -0,0 +1,47 @@
package main
import (
"encoding/base64"
"net/http"
"strings"
"github.com/zenazn/goji/web"
)
// Middleware to render responses as plain text.
func PlainText(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
// Nobody will ever guess this!
const Password = "admin:admin"
// HTTP Basic Auth middleware for super-secret admin page. Shhhh!
func SuperSecure(c *web.C, h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if !strings.HasPrefix(auth, "Basic ") {
pleaseAuth(w)
return
}
password, err := base64.StdEncoding.DecodeString(auth[6:])
if err != nil || string(password) != Password {
pleaseAuth(w)
return
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func pleaseAuth(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", `Basic realm="Gritter"`)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Go away!\n"))
}

+ 48
- 0
example/models.go View File

@ -0,0 +1,48 @@
package main
import (
"fmt"
"io"
"time"
)
// A greet is a 140-character micro-blogpost that has no resemblance whatsoever
// to the noise a bird makes.
type Greet struct {
User string `param:"user"`
Message string `param:"message"`
Time time.Time `param:"time"`
}
// Store all our greets in a big list in memory, because, let's be honest, who's
// actually going to use a service that only allows you to post 140-character
// messages?
var Greets = []Greet{
{"carl", "Welcome to Gritter!", time.Now()},
{"alice", "Wanna know a secret?", time.Now()},
{"bob", "Okay!", time.Now()},
{"eve", "I'm listening...", time.Now()},
}
// Write out a representation of the greet
func (g Greet) Write(w io.Writer) {
fmt.Fprintf(w, "%s\n@%s at %s\n---\n", g.Message, g.User,
g.Time.Format(time.UnixDate))
}
// A user
type User struct {
Name, Bio string
}
// All the users we know about! There aren't very many...
var Users = map[string]User{
"alice": {"Alice in Wonderland", "Eating mushrooms"},
"bob": {"Bob the Builder", "Making children dumber"},
"carl": {"Carl Jackson", "Duct tape aficionado"},
}
// Write out the user
func (u User) Write(w io.Writer, handle string) {
fmt.Fprintf(w, "%s (@%s)\n%s\n", u.Name, handle, u.Bio)
}

Loading…
Cancel
Save