A Go framework for building JSON web services inspired by Dropwizard

Tiger Tonic

Build Status

A Go framework for building JSON web services inspired by Dropwizard. If HTML is your game, this will hurt a little.

Like the Go language itself, Tiger Tonic strives to keep features orthogonal. It defers what it can to the Go standard library and a few other packages.

Documentation

Articles and talks

Reference

http://godoc.org/github.com/rcrowley/go-tigertonic

Community

Synopsis

tigertonic.TrieServeMux

HTTP routing in the Go standard library is pretty anemic. Enter tigertonic.TrieServeMux. It accepts an HTTP method, a URL pattern, and an http.Handler or an http.HandlerFunc. Components in the URL pattern wrapped in curly braces - { and } - are wildcards: their values (which don't cross slashes) are added to the URL as u.Query().Get("name").

HandleNamespace is like Handle but additionally strips the namespace from the URL, making API versioning, multitenant services, and relative links easier to manage. This is roughly equivalent to http.ServeMux's behavior.

tigertonic.HostServeMux

Use tigertonic.HostServeMux to serve multiple domain names from the same net.Listener.

tigertonic.Marshaled

Wrap a function in tigertonic.Marshaled to turn it into an http.Handler. The function signature must be something like this or tigertonic.Marshaled will panic:

func myHandler(*url.URL, http.Header, *MyRequest) (int, http.Header, *MyResponse, error)

Request bodies will be unmarshaled into a MyRequest struct and response bodies will be marshaled from MyResponse structs.

Should you need to respond with an error, the tigertonic.HTTPEquivError interface is implemented by tigertonic.BadRequest (and so on for every other HTTP response status) that can be wrapped around any error:

func myHandler(*url.URL, http.Header, *MyRequest) (int, http.Header, *MyResponse, error) {
    return 0, nil, nil, tigertonic.BadRequest{errors.New("Bad Request")}
}

Alternatively, you can return a valid status as the first output parameter and an error as the last; that status will be used in the error response.

If the return type of a tigertonic.Marshaled handler interface implements the io.Reader interface the stream will be written directly to the requestor. A Content-Type header is required to be specified in the response headers and the Accept header for these particular requests can be anything.

Additionally, if the return type of the tigertonic.Marshaled handler implements the io.Closer interface the stream will be automatically closed after it is flushed to the requestor.

tigertonic.Logged, tigertonic.JSONLogged, and tigertonic.ApacheLogged

Wrap an http.Handler in tigertonic.Logged to have the request and response headers and bodies logged to standard output. The second argument is an optional func(string) string called as requests and responses are logged to give the caller the opportunity to redact sensitive information from log entries.

Wrap an http.Handler in tigertonic.JSONLogged to have the request and response headers and bodies logged to standard output as JSON suitable for sending to ElasticSearch, Flume, Logstash, and so on. The JSON will be prefixed with @json: . The second argument is an optional func(string) string called as requests and responses are logged to give the caller the opportunity to redact sensitive information from log entries.

Wrap an http.Handler in tigertonic.ApacheLogged to have the request and response logged in the more traditional Apache combined log format.

tigertonic.Counted and tigertonic.Timed

Wrap an http.Handler in tigertonic.Counted or tigertonic.Timed to have the request counted or timed with go-metrics.

tigertonic.CountedByStatus and tigertonic.CountedByStatusXX

Wrap an http.Handler in tigertonic.CountedByStatus or tigertonic.CountedByStatusXX to have the response counted with go-metrics with a metrics.Counter for each HTTP status code or family of status codes (1xx, 2xx, and so on).

tigertonic.First

Call tigertonic.First with a variadic slice of http.Handlers. It will call ServeHTTP on each in succession until the first one that calls w.WriteHeader.

tigertonic.If

tigertonic.If expresses the most common use of tigertonic.First more naturally. Call tigertonic.If with a func(*http.Request) (http.Header, error) and an http.Handler. It will conditionally call the handler unless the function returns an error. In that case, the error is used to create a response.

tigertonic.PostProcessed and tigertonic.TeeResponseWriter

tigertonic.PostProcessed uses a tigertonic.TeeResponseWriter to record the response and call a func(*http.Request, *http.Response) after the response is written to the client to allow post-processing requests and responses.

tigertonic.HTTPBasicAuth

Wrap an http.Handler in tigertonic.HTTPBasicAuth, providing a map[string]string of authorized usernames to passwords, to require the request include a valid Authorization header.

tigertonic.CORSHandler and tigertonic.CORSBuilder

Wrap an http.Handler in tigertonic.CORSHandler (using CORSBuilder.Build()) to inject CORS-related headers. Currently only Origin-related headers (used for cross-origin browser requests) are supported.

tigertonic.Configure

Call tigertonic.Configure to read and unmarshal a JSON configuration file into a configuration structure of your own design. This is mere convenience and what you do with it after is up to you.

tigertonic.WithContext and tigertonic.Context

Wrap an http.Handler and a zero value of any non-interface type in tigertonic.WithContext to enable per-request context. Each request may call tigertonic.Context with the *http.Request in progress to get a pointer to the context which is of the type passed to tigertonic.WithContext.

tigertonic.Version

Respond with a version string that may be set at compile-time.

Usage

Install dependencies:

sh bootstrap.sh

Then define your service. The working example may be a more convenient place to start.

Requests that have bodies have types. JSON is deserialized by adding tigertonic.Marshaled to your routes.

type MyRequest struct {
	ID     string      `json:"id"`
	Stuff  interface{} `json:"stuff"`
}

Responses, too, have types. JSON is serialized by adding tigertonic.Marshaled to your routes.

type MyResponse struct {
	ID     string      `json:"id"`
	Stuff  interface{} `json:"stuff"`
}

Routes are just functions with a particular signature. You control the request and response types.

func myHandler(u *url.URL, h http.Header, *MyRequest) (int, http.Header, *MyResponse, error) {
    return http.StatusOK, nil, &MyResponse{"ID", "STUFF"}, nil
}

Wire it all up in main.main!

mux := tigertonic.NewTrieServeMux()
mux.Handle("POST", "/stuff", tigertonic.Timed(tigertonic.Marshaled(myHandler), "myHandler", nil))
tigertonic.NewServer(":8000", tigertonic.Logged(mux, nil)).ListenAndServe()

Ready for more? See the full example which includes all of these handlers plus an example of how to use tigertonic.Server to stop gracefully. Build it with go build, run it with ./example, and test it out:

curl -H"Host: example.com" -sv "http://127.0.0.1:8000/1.0/stuff/ID"
curl -H"Host: example.com" -X"POST" -d'{"id":"ID","stuff":"STUFF"}' -sv "http://127.0.0.1:8000/1.0/stuff"
curl -H"Host: example.com" -X"POST" -d'{"id":"ID","stuff":"STUFF"}' -sv "http://127.0.0.1:8000/1.0/stuff/ID"
curl -H"Host: example.com" -sv "http://127.0.0.1:8000/1.0/forbidden"

WTF?

Dropwizard was named after http://gunshowcomic.com/316 so Tiger Tonic was named after http://gunshowcomic.com/338.

If Tiger Tonic isn't your cup of tea, perhaps one of these fine tools suits you:

Comments
  • Error Handler

    Error Handler

    If I were to extract the current errors handling to an interface and allow it to be replaced, would that be something that might be pulled? Or would I be wasting my time?

  • Allow Marshaled to respond with arbitrary streams.

    Allow Marshaled to respond with arbitrary streams.

    If the return type of a Marshaled handler interface implements the io.Reader interface the stream will be written directly to the requestor. A 'Content-Type' header is required to be specified in the response headers and the 'Accept' header for these particular requests can be anything.

    Additionally, if the return type of the Marshaled handler implements the io.Closer interface the stream will be automatically closed after it is flushed to the requestor.

    @wadey

  • too narrow listing of TLS cipher suites

    too narrow listing of TLS cipher suites

    The cipher suites in go-tigertonic all rely on RC4 which has many suspicions around it (its biases are a little funny and there are strong rumors of breaks from certain Government Bodies). While RC4 is a fine fallback for TLS 1.0 (due to BEAST), later versions of TLS are better off using CBC modes. Also, Go's crypto/tls already mitigates the BEAST attack.

    The default list of cipher suites in crypto/tls is very solid and was chosen carefully.

    If you wish to lock the cipher suite list down further, a more scalable way to do this would be to require TLS 1.2 as the minimum TLS version and continue to use the default cipher suite list. (TLS 1.2 and 1.1 fix the BEAST attack by changing how CBC is performed as one useful, if mooted, example). Using the defaults and setting the minimum version will net you the Langley-Approved cipher suites with few backwards compatibility problems.

    Of course, perhaps I've mis-guessed why those were selected.

  • Dropwizard-like configuration file parser

    Dropwizard-like configuration file parser

    @mihasya's favorite thing about Dropwizard is the way configuration classes are declared and instantiated from YAML files. The flags package notwithstanding, Tiger Tonic should really have the same.

  • Healthchecks

    Healthchecks

    It'd be nice to have a HealthCheck registry, and some sort of "healthy" or "unhealthy" output with a Handler that can be linked up...

  • Dev/cors

    Dev/cors

    This is an attempt to introduce CORS (http://www.w3.org/TR/cors/#access-control-allow-origin-response-header https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS) headers into tigertonic. (#28)

    Since CORS responses to OPTIONS requests are somewhat orthogonal to the actual handling of the request, I've decided to try and put the logic in a separate little wrapper in cors.go. I am now realizing that SOME of the headers need to be returned for non-OPTIONS requests as well (looks like just the Origin one, but who knows, this shit is super inconsistent), so I'll probably need to hook in at one other place and type-check for CORSHandler.

  • Add access to http.Request object in Marshaled()

    Add access to http.Request object in Marshaled()

    I need to access the remote address in a marshaled function, and for this I need access to the http.Request object. Currently there's no obvious way to do that. Am I missing something? Will be happy for any guidance if there is an easy way to do so. Otherwise, please add this option. Will be happy to do that myself and pull request if you'll guide me first.

  • should graceful shutdown branch be the default now?

    should graceful shutdown branch be the default now?

    I'm using the go1.3 branch because I need the graceful shutdown part. go1.3 is out now and the currently recommended go version. Would you consider merging the 1.3 shutdown work into the master branch now?

  • Responsecodes

    Responsecodes

    Adds tigertonic.StatusCodeCounted which increments counters for 1xx, 2xx etc responses.

    I've tested this briefly in our lab environment and it appears to be working correctly.

  • We need a way to factor out per-request initialization

    We need a way to factor out per-request initialization

    @wadey says:

    main thing I miss is a way to build up state as the request passes along the filters, not sure what the best way to model that is though i dont like how some frameworks just make a grabbag map on the request object but I feel like there is something we could do, maybe a domain specific request object you can create I want to noodle on that, thats the main thing I feel like tigertonic is missing

    It's pretty dissatisfying to have to call a GetUser(request) function in every single http.Handler that needs a *User, for example.

    Method values may give us something but we've used those so far to provide global, not per-request, context.

  • go1.2 breaks http error wrappers

    go1.2 breaks http error wrappers

    After testing with go1.2, I found that my tigertonic applications don't compile with the following error:

    myfile.go:20: implicit assignment of unexported field 'error' in tigertonic.NotFound literal
    

    The code I am doing on this line is basically:

    err := &tigertonic.NotFound{fmt.Errorf("My error message")}
    

    Looks like go1.2 is preventing assignment to embedded "error" fields if you aren't in the same package. This seems like a pretty unfortunate change, we might need to make a helper method to construct these wrapper errors.

  • Potential security issue (XSS in Accept header)

    Potential security issue (XSS in Accept header)

    The HTTP response returns unsanitized value from Accept header. This may allow the attacker to conduct cross-site scripting attack. Technically, the likelihood of exploiting this is very low. It requires the victim to use Internet Explorer with MIME sniffing feature enabled, on top of that, setting the these headers to the victim's request is not really possible from my knowledge. However, I believe it is worth sanitize user input here.

    GET /xxx HTTP/1.1
    Accept: xxx_<script>alert(/XSS/)</script>_yyy
    Host: example.com
    Connection: close
    
    HTTP/1.1 406 Not Acceptable
    Content-Type: text/plain
    ...
    
    tigertonic.MarshalerError: Accept header "xxx_<script>alert(/XSS/)</script>_yyy" does not allow "application/json"
    

    The underlying code is as follows: https://github.com/rcrowley/go-tigertonic/blob/master/marshaler.go

    	if !isReader && !acceptJSON(r) {
    		ResponseErrorWriter.WritePlaintextError(w, NewHTTPEquivError(NewMarshalerError(
    			"Accept header %q does not allow \"application/json\"",
    			r.Header.Get("Accept"),
    		), http.StatusNotAcceptable))
    		return
    	}
    
  • Hijackable Logger

    Hijackable Logger

    Given a tigertonic based server that serves both REST and web socket endpoints. And the server uses a logged HTTP handler (e.g. tigertonic.ApacheLogged()).

    When accessing a web socket API the request will fail complaining that apacheLoggerResponseWriter is not hijack-able (i.e. does not implement http.Hijacker).

    The logged HTTP handlers should use response writers that are fully transparent. The logged response writer should support hijacking if the underlying response writer does.

    A simple fix would be to add method Hijack to the logged response writers. However, this does not feel 100% right.

  • Erroneously returns 405 when the last segment of the request URL matches a parameter in some other handled URL

    Erroneously returns 405 when the last segment of the request URL matches a parameter in some other handled URL

    I'm handling a couple of URLs:

    mux.Handle("GET", "/api/{organization}/groups", ...)
    mux.Handle("GET", "/api/{organization}/groups/{group}/hosts", ...)
    

    Given this set of handlers: GET @ /api/12345/foo - Returns 404, as I'd expect ...but... GET @ /api/12345/groups/ - Returns 405. GET @ /api/12345/groups/foo - Also returns 405.

    Both 405 responses come back claiming:

    {
        "description": "only OPTIONS are allowed",
        "error": "tigertonic.MethodNotAllowed"
    }
    

    There seems to be some weird behavior if it's trying to match URLs and the last segment of the requested URL matches a parameter portion of any other URL, even if there's no way the URL could match, as in /api/12345/groups/foo. It also doesn't seem to be checking for empty parameter values in cases like this, causing /api/12345/groups to work as expected but /api/12345/groups/ to fail with 405.

    Also, if I add another handler:

    mux.Handle("GET", "/api/{organization}/groups/{group}", ...)
    

    ...then requests to /api/12345/groups/ do get routed to this new handler, but it gets an empty "group" value.

  • Return Error Messages

    Return Error Messages

    This is a non issue really, I'm just curious! What's the design reason for spitting out the 'kind' of error? I don't get the benefit of return it to the end user (and exposing internals).

    {
      "description": "Invalid id given",
      "error": "validator.Errors"
    }
    

    vs

    {
      "error": "Invalid id given"
    }
    
  • Allow no body when Content-Length = 0

    Allow no body when Content-Length = 0

    The HTTP spec is ambiguous in determining if a request could have no body when the method is POST, PUT, or PATCH. Some popular HTTP server, like nginx, adopt the policy that a request of the aforementioned methods with no body is allowed if and only if it has the header Content-Length set to 0.

    It makes sense not to try to marshal the body when Content-Length has that value.

    I understand that an empty body is not the same as no body, and this is why I said the spec is ambiguous. Even though a request of such methods with no body might be unRESTful it is useful for implementing "actions".

    Would you consider merging this?

    Regards, Alfonso.

Mango is a modular web-application framework for Go, inspired by Rack, and PEP333.

Mango Mango is a modular web-application framework for Go, inspired by Rack and PEP333. Note: Not actively maintained. Overview Mango is most of all a

Nov 17, 2022
Flamingo Framework and Core Library. Flamingo is a go based framework for pluggable web projects. It is used to build scalable and maintainable (web)applications.
Flamingo Framework and Core Library. Flamingo is a go based framework for pluggable web projects. It is used to build scalable and maintainable (web)applications.

Flamingo Framework Flamingo is a web framework based on Go. It is designed to build pluggable and maintainable web projects. It is production ready, f

Jan 5, 2023
Golanger Web Framework is a lightweight framework for writing web applications in Go.

/* Copyright 2013 Golanger.com. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except

Nov 14, 2022
A simple blog framework built with GO. Uses HTML files and a JSON dict to give you more control over your content.

Go-Blog A simple template based blog framework. Instructions Built for GO version: 1 See the Documentation or Getting Started pages in the wiki. Notes

Sep 10, 2022
Provide open, community driven reusable components for building distributed applications

Components Contrib The purpose of Components Contrib is to provide open, community driven reusable components for building distributed applications. T

Nov 28, 2021
An ideally refined web framework for Go.

Air An ideally refined web framework for Go. High-performance? Fastest? Almost all web frameworks are using these words to tell people that they are t

Dec 15, 2022
Web framework for creating apps using Go in Google AppEngine

Welcome to app.go v3.0 app.go is a simple web framework for use in Google AppEngine. Just copy the app folder to your working folder and import it fro

Mar 21, 2021
Eudore is the core of a golang lightweight web framework.

Eudore eudore是一个golang轻量级web框架核心,可以轻松扩展成一个技术栈专用框架,具有完整框架设计体系。 反馈和交流请加群组:QQ群373278915。 Features 易扩展:主要设计目标、核心全部解耦,接口即为逻辑。 简单:对象语义明确,框架代码量少复杂度低,无依赖库。 易用

Nov 7, 2022
Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.

Gin Web Framework Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks

Jan 2, 2023
Goal is a toolkit for high productivity web development in Go language in the spirit of Revel Framework that is built around the concept of code generation.

Goal Goal is a set of tools for high productivity web development in Go language. Goal, being mostly inspired by Revel Framework and its discussions,

Sep 27, 2021
a golang web mvc framework, like asp.net mvc.

goku goku is a Web Mvc Framework for golang, mostly like ASP.NET MVC. doc & api Installation To install goku, simply run go get github.com/QLeelulu/go

Dec 7, 2022
A high level web-framework for Go

go-start is a high level web-framework for Go, like Django for Python or Rails for Ruby. Installation: go get github.com/ungerik/go-start Documentatio

Dec 24, 2022
A lightweight RESTful web framework for Go
A lightweight RESTful web framework for Go

Goweb A lightweight RESTful web framework for Go. For examples and usage, please read the Goweb API Documentation Read our Articles Who uses Goweb? "U

Dec 12, 2022
Fast and Reliable Golang Web Framework
Fast and Reliable Golang Web Framework

Gramework The Good Framework Gramework long-term testing stand metrics screenshot made with Gramework Stats Dashboard and metrics middleware What is i

Dec 18, 2022
Classy web framework for Go

Martini NOTE: The martini framework is no longer maintained. Martini is a powerful package for quickly writing modular web applications/services in Go

Dec 29, 2022
The web framework for Golang
The web framework for Golang

uAdmin the Golang Web Framework Easy to use, blazing fast and secure. Originally open source by IntegrityNet Solutions and Services For Documentation:

Dec 24, 2022
Simple web framework for go, still quite beta at this point

WFDR Framework - Beta Release New 18/Feb/2012: Updated for go 1.0, new directory layout to take advantage of the go build tool. Background There's a m

Feb 11, 2021
:bullettrain_side: High-performance web server for Go.
:bullettrain_side: High-performance web server for Go.

Aero is a high-performance web server with a clean API. Installation go get -u github.com/aerogo/aero/... Usage Run this in an empty directory: aero -

Dec 8, 2022
The easiest way to create web applications with Go

web.go web.go is the simplest way to write web applications in the Go programming language. It's ideal for writing simple, performant backend web serv

Dec 24, 2022