gadget

package module
v0.0.0-...-c971602 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 6, 2014 License: BSD-2-Clause Imports: 15 Imported by: 0

README

Gadget is a web framework for Go

Build Status

Gadget is a smallish web application framework with a soft spot for content negotiation. It requires a working installation of Go1.1 and a workspace/ environment variables set up according to How to Write Go Code.

You can install and all its subpackages at once:

go get -v github.com/redneckbeard/gadget/...

You can read an overview of how it works, or work through the quick sample project below.

Okay. I've got some photos I'd like to share with some friends, so we'll make a photo app. I'm going to run the gdgt tool at the root of $GOPATH/src.

$ gdgt new photos
Created directory photos/controllers
Created directory photos/app
Created directory photos/templates
Created directory photos/static/css
Created directory photos/static/img
Created directory photos/static/js
Created photos/main.go
Created app/conf.go
Created photos/controllers/home.go

The skeleton project is empty, but it is a complete program and we can compile it right now. Running go install photos will put an executable in $GOPATH/bin. I have that directory on my $PATH, so now I can invoke photos.

$ photos
Available commands:
  help
  serve
  list-routes
Type 'photos help <command>' for more information on a specific command.

I'm curious what list-routes will output.

$ photos list-routes
^$ 	 controllers.HomeController

So, we get a regular expression representing the root url mapped to controllers.HomeController. Now I want to see what that does, so I'm going to run photos serve and throw a request at it on another tab. I want to build some super fancy Ember.js client for this site, so I'll politely ask for JavaScript.

$ curl http://127.0.0.1:8090 -H 'Accept: application/json' -v
< HTTP/1.1 404 Not Found
< Content-Type: application/json
< Date: Sat, 23 Nov 2013 05:02:45 GMT
< Content-Length: 2
< 
* Connection #0 to host 127.0.0.1 left intact
""

Empty string. Booooo. Well, at least it responded with the correct Content-Type. But we want to send back some sort of content, so we'll open up controllers/home.go and make a few changes. The definition of HomeController looks like this when we open it up:

type HomeController struct {
	*gadget.DefaultController
}

func (c *HomeController) Plural() string { return "home" }

We're going to add a method to this to serve some content. For now, it's just going to send back a bunch of strings.

func (c *HomeController) Index(r *gadget.Request) (int, interface{}) {
	return 200, []string{
		"pic1.jpg",
		"pic2.jpg",
		"pic3.jpg",
		"pic4.jpg",
	}
}

In Gadget, controller methods return a status code and a response body. The response body's type is interface{} -- it can be just about anything you want. The framework just has to figure out what to do with it.

So we recompile and start the server again, and give that curl another go.

$ curl http://127.0.0.1:8090 -H 'Accept: application/json' -v
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Sat, 23 Nov 2013 05:15:18 GMT
< Content-Length: 58
< 
[
  "pic1.jpg",
  "pic2.jpg",
  "pic3.jpg",
  "pic4.jpg"
]

That's more like it! But what happens if we don't explicitly request JSON?

$ curl http://127.0.0.1:8090
<html>
  <head>
    <title></title>
  </head>
  <body>
	  
[pic1.jpg pic2.jpg pic3.jpg pic4.jpg]

  </body>
</html>

That may look a little broken, but it highlights a fundamental principle of Gadget's controller mechanism: all requests are subject to content negotiation. This is why the return signature for the response body is interface{} -- it lets us send a single value back to the requester, with the final representation of that value being determined by a series of content brokers that match Accept headers against functions for transforming interface values.

The default Gadget configuration sends */* through to a broker that passes the return value to a Go template. The template we're interested in was created for us by gdgt new as templates/home/index.html. It looks like this:

{{define "main"}}
{{.}}
{{end}}

Not much there. We can change it to loop over the context of the template using the "range" function, and throw a few tags in there.

{{define "main"}}
<ul>
{{range .}}
  <li>{{.}}</li>
{{end}}
</ul>

Now our Acceptless curl looks a little less funky:

$ curl http://127.0.0.1:8090
<html>
  <head>
    <title></title>
  </head>
  <body>
  <ul>
    <li>pic1.jpg</li>
    <li>pic2.jpg</li>
    <li>pic3.jpg</li>
    <li>pic4.jpg</li>
  </ul>
  </body>
</html>

We're hitting / here, but even if we weren't, there's no extension involved: Gadget speaks HTTP and only cares about your Accept header. We can send Accept: text/plain and we will get an unadorned fmt.Print of the response body interface{} value:

$ curl http://127.0.0.1:8090 -H 'Accept: text/plain'
[pic1.jpg pic2.jpg pic3.jpg pic4.jpg]

Of course, the data we're getting back could be a bit more interesting. Let's amend that Index method to return a slice of anonymous structs with a few different fields.

func (c *HomeController) Index(r *gadget.Request) (int, interface{}) {
	return 200, []struct{
		Filename, Title, Description string
	}{
		{"pic1.jpg", "Pic One", "The first picture in our albom."},
		{"pic2.jpg", "Pic Two", "The second picture in our albom."},
		{"pic3.jpg", "Pic Three", "The third picture in our albom."},
		{"pic4.jpg", "Pic Four", "The fourth picture in our albom."},
	}
}

We'll also make a tweak to the inside of that template loop.

<li>
  <a href="{{.Filename}}" title="{{.Title}}">{{.Description}}</a>
</li>

Now our responses for the three content types we've tried sending look like this:

$ curl http://127.0.0.1:8090 -H 'Accept: text/plain'
[{pic1.jpg Pic One The first picture in our albom.} {pic2.jpg Pic Two The second picture in our albom.} {pic3.jpg Pic Three The third picture in our albom.} {pic4.jpg Pic Four The fourth picture in our albom.}]

jlukens-mbp:src jlukens$ curl http://127.0.0.1:8090 -H 'Accept: text/html'
<html>
  <head>
    <title></title>
  </head>
  <body>
  <ul>
    <li>
      <a href="pic1.jpg" title="Pic One">The first picture in our albom.</a>
    </li>
    <li>
      <a href="pic2.jpg" title="Pic Two">The second picture in our albom.</a>
    </li>
    <li>
      <a href="pic3.jpg" title="Pic Three">The third picture in our albom.</a>
    </li>
    <li>
      <a href="pic4.jpg" title="Pic Four">The fourth picture in our albom.</a>
    </li>
  </ul>
  </body>
</html>

$ curl http://127.0.0.1:8090 -H 'Accept: application/json'
[
  {
    "Filename": "pic1.jpg",
    "Title": "Pic One",
    "Description": "The first picture in our albom."
  },
  {
    "Filename": "pic2.jpg",
    "Title": "Pic Two",
    "Description": "The second picture in our albom."
  },
  {
    "Filename": "pic3.jpg",
    "Title": "Pic Three",
    "Description": "The third picture in our albom."
  },
  {
    "Filename": "pic4.jpg",
    "Title": "Pic Four",
    "Description": "The fourth picture in our albom."
  }
]

This is an extremely brief introduction, but if you think it's neat and would like to know more, there are plenty of docs over on godoc.org.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	SetDebugWith  = func(r *Request) bool { return false }
	RequestLogger = func(r *Request, status, contentLength int) string {
		return fmt.Sprintf(`[%s] "%s %s %s" %d %d`, time.Now().Format(time.RFC822), r.Method, r.URL.Path, r.Proto, status, contentLength)
	}
)

SetDebugWith by default is a function that always returns false, no matter what request is passed to it.

Functions

func Go

func Go()

Go calls an app's Configure method and runs the command parser.

func IdentifyUsersWith

func IdentifyUsersWith(ui UserIdentifier)

IdentifyUsersWith allows Gadget applications to register a UserIdentifier function to be called when processing each incoming request. The return value of the UserIdentifier will be set on the Request object if not nil; AnonymousUser will be used otherwise.

func JsonBroker

func JsonBroker(r *Request, status int, body interface{}, data *RouteData) (int, string)

JsonBroker attempts to transform an interface{} value into a JSON string.

func SetApp

func SetApp(g gdgt)

SetApp registers the top-level app with Gadget.

func XmlBroker

func XmlBroker(r *Request, status int, body interface{}, data *RouteData) (int, string)

XmlBroker attempts to transform an interface{} value into a serialized XML string.

Types

type AnonymousUser

type AnonymousUser struct {
}

AnonymousUser is the default User type if the UserIdentifier registered by the application returns nil or if there is no UserIdentifier.

func (*AnonymousUser) Authenticated

func (u *AnonymousUser) Authenticated() bool

Authenticated for AnonymousUser will always return false.

type App

type App struct {
	Brokers     map[string]Broker
	Controllers map[string]Controller
	// contains filtered or unexported fields
}

App provides core Gadget functionality.

func (*App) Accept

func (a *App) Accept(mimes ...string) *contentType

Accept registers MIME type strings with the App. Via can be called on its return value to associated those MIME types with a Broker.

func (*App) HandleFunc

func (a *App) HandleFunc(mount string, handler http.HandlerFunc) *route

HandleFunc mounts an http.HandlerFunc at the specified URL.

func (*App) Handler

func (a *App) Handler() http.HandlerFunc

Handler returns a func encapsulating the Gadget router (and corresponding controllers that can be used in a call to http.HandleFunc. Handler must be invoked only after Routes has been called and all Controllers have been registered.

In theory, Gadget users will not ever have to call Handler, as Gadget will set up http.HandleFunc to use its return value.

func (*App) Prefixed

func (a *App) Prefixed(prefix string, nested ...*route) *route

Prefixed mounts routes at a URL path that is not necessarily a controller name.

func (*App) Register

func (a *App) Register(clist ...Controller)

Register notifies Gadget that you want to use a type as a Controller. It takes a variable number of arguments, all of which must be pointers to struct types that embed DefaultController. It will panic if the name of the Controller is not in the form <resource name>Controller or if a struct value is passed instead of a pointer.

Register makes naive assumptions about the name of your Controller; firstly, that it is English; secondly that it is in singular number; and thirdly, that it can be pluralized simply by appending the letter "s". Thus, "WidgetController" will be available to the Resource function for routes as "widgets", but "EntryController" as "entrys", unless you define the Plural method on the Controller to return something more correct.

func (*App) Resource

func (a *App) Resource(controllerName string, nested ...*route) *route

Resource creates a route to the specified controller and optionally creates additional routes nested under it.

func (*App) Routes

func (a *App) Routes(rtes ...*route)

Routes registers a variable number of routes with the Gadget router. Arguments to Routes should be calls to SetIndex, Resource, or Prefixed.

func (*App) SetIndex

func (a *App) SetIndex(controllerName string) *route

SetIndex creates a route that maps / to the specified controller. /<IdPattern> will still map to the controller's Show, Update, and Delete methods.

type Broker

type Broker func(*Request, int, interface{}, *RouteData) (int, string)

Broker functions transform an interface{} value into a string for the body of a response

type Controller

type Controller interface {
	Index(r *Request) (int, interface{})
	Show(r *Request) (int, interface{})
	Create(r *Request) (int, interface{})
	Update(r *Request) (int, interface{})
	Destroy(r *Request) (int, interface{})

	Filter(verbs []string, filter Filter)
	IdPattern() string
	Plural() string
	// contains filtered or unexported methods
}

Controller is the interface that defines how Gadget applications respond to HTTP requests at a given URL with a particular HTTP verb. Controllers have five primary methods for handling requests. For a Controller mounted at /foo, requests are routed to methods as follows:

GET 	/foo 			Index
POST 	/foo			Create
GET 	/foo/<idPattern> 	Show
PUT 	/foo/<idPattern>	Update
DELETE 	/foo/<idPattern> 	Destroy

Each of these methods takes a *gadget.Request as its only argument and returns an HTTP status code as an int and an interface{} value as the body. The interface{} value is then cast to a string, serialized, used as a template context, etc. according to the application's broker configuration.

Any other exported method with a signature of (*gadget.Request) (int, interface{}) will also be routable. For example, if the controller mounted above at /foo defines a method AllTheThings(r *gadget.Request) (int, interface{}), Gadget will route any request for /foo/all-the-things, regardless of verb, to that method.

Controller also requires two methods that enable users to customize routing options to this controller, IdPattern and Plural. The final exported method of the Controller interface is Filter, which allows for abstracting common patterns from multiple Controller methods. All three of these methods are documented in the fallback implementations provided by DefaultController.

Applications must inform Gadget of the existence of Controller types using the Register function.

type DefaultController

type DefaultController struct {
	// contains filtered or unexported fields
}

DefaultController satisfies the Controller interface, and because Controller includes unexported methods, must be embedded in any other type that implements Controller. The fallback implementations provided by DefaultController and the interactions of other Gadget machinery therewith can be summarized as follows:

  • Index, Show, Create, Update, and Destroy methods all return a 404 with an empty string for the body
  • The return value of IdPattern for use in routes is `\d+`
  • The return value of Plural is "", which Register takes to mean "just add an 's'"

func (*DefaultController) Create

func (c *DefaultController) Create(r *Request) (int, interface{})

Create implements a default return value of is (404, "").

func (*DefaultController) Destroy

func (c *DefaultController) Destroy(r *Request) (int, interface{})

Destroy implements a default return value of is (404, "").

func (*DefaultController) Filter

func (c *DefaultController) Filter(verbs []string, filter Filter)

Filter applies a Filter func to the Controller methods named in the string slice verbs. The strings in the slice should be the lowercased name of the default method, or for additional methods, the hyphenated string that appears in routes.

If any Filter returns non-zero number for the HTTP status, the body of the Controller method will never be executed and the response cycle will begin with the Filter.

c := &PostController{}
c.Filter([]string{"create", "update", "destroy"}, func(r *gadget.Request) (int, interface{}) {
	if !r.User.Authenticated() {
		return 403, "Verboten"
	}
}
gadget.Register(c)

func (*DefaultController) IdPattern

func (c *DefaultController) IdPattern() string

IdPattern returns a string that will be used in a regular expression to match a unique identifier for a resource in a URL. The matched value is then added to gadget.Request.UrlParams.

func (*DefaultController) Index

func (c *DefaultController) Index(r *Request) (int, interface{})

Index implements a default return value of is (404, "").

func (*DefaultController) Plural

func (c *DefaultController) Plural() string

Plural returns a string to be used as the plural form of the first word in the name of the Controller type. Note that this value needs to be the entire plural form of the word, not an ending.

func (*DefaultController) Show

func (c *DefaultController) Show(r *Request) (int, interface{})

Show implements a default return value of is (404, "").

func (*DefaultController) Update

func (c *DefaultController) Update(r *Request) (int, interface{})

Update implements a default return value of is (404, "").

type Filter

type Filter func(*Request) (int, interface{})

Filter is simply a function with the same signature as a controller method minus the receiver. They are used in calls to Controller.Filter.

type ListRoutes

type ListRoutes struct {
	*quimby.Flagger
}

ListRoutes provides a command to print out all routes registered with an application.

func (*ListRoutes) Desc

func (c *ListRoutes) Desc() string

func (*ListRoutes) Run

func (c *ListRoutes) Run()

Run prints all the routes registered with the application.

func (*ListRoutes) SetFlags

func (c *ListRoutes) SetFlags()

type Request

type Request struct {
	*http.Request
	Params    map[string]interface{}
	Path      string
	UrlParams map[string]string
	User      User
}

Request wraps an *http.Request and adds some Gadget-derived conveniences. The Params map contains either POST data, GET query parameters, or the body of the request deserialized as JSON if the request sends an Accept header of application/json. The UrlParams map contains any resource ids plucked from the URL by the router. The User is either an AnonymousUser or an object returned by the UserIdentifier that the application as registered with IdentifyUsersWith.

func (*Request) ContentType

func (r *Request) ContentType() string

ContentType is sort of a dishonest method -- it returns the value of an Accept header if present, and falls back to Content-Type.

func (*Request) Debug

func (r *Request) Debug() bool

Debug returns true if env.Debug is true or if SetDebugWith returns true when passed its receiver r.

type Response

type Response struct {
	Body interface{}

	Cookies []*http.Cookie
	Headers http.Header
	// contains filtered or unexported fields
}

Response provides a wrapper around the interface{} value you would normally return for the response body in a Controller method, but gives you the ability to write headers and cookies to accompany the response.

func NewResponse

func NewResponse(body interface{}) *Response

NewResponse returns a pointer to a Response with its Body and Headers values initialized.

func (*Response) AddCookie

func (r *Response) AddCookie(cookie *http.Cookie)

AddCookie adds a cookie to the Response.

type RouteData

type RouteData struct {
	ControllerName, Action, Verb string
}

RouteData provides context about the where the current request is being executed.

type User

type User interface {
	Authenticated() bool
}

User is the interface that wraps Gadget's pluggable authentication mechanism. Any type that provides an Authenticated method satisfies it.

type UserIdentifier

type UserIdentifier func(*Request) User

UserIdentifier is a function type that returns a User type or nil based on a Request.

Directories

Path Synopsis
Package forms provides tools for validating data found in gadget.Request.Params.
Package forms provides tools for validating data found in gadget.Request.Params.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL