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.

⚡️ Express inspired web framework written in Go
⚡️ Express inspired web framework written in Go

Fiber is an Express inspired web framework built on top of Fasthttp, the fastest HTTP engine for Go. Designed to ease things up for fast development w

Jan 2, 2023
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
Simple and lightweight Go web framework inspired by koa
Simple and lightweight Go web framework inspired by koa

VOX A golang web framework for humans, inspired by Koa heavily. Getting started Installation Using the go get power: $ go get -u github.com/aisk/vox B

Dec 14, 2022
Rocinante is a gin inspired web framework built on top of net/http.

Rocinante Rocinante is a gin inspired web framework built on top of net/http. ⚙️ Installation $ go get -u github.com/fskanokano/rocinante-go ⚡️ Quicks

Jul 27, 2021
Gouweb: A web framework for go inspired by laravel

gouweb is a web framework for go inspired by laravel Installation go get -u gith

Feb 17, 2022
Go-igni: monolith-based web-framework in Go Inspired by classical PHP Frameworks like CodeIgnier & Laravel

go-igni Web Framework in Go About This is research project about developing monolith-based web-framework in Go Inspired by classical PHP Frameworks li

Feb 25, 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
The jin is a simplified version of the gin web framework that can help you quickly understand the core principles of a web framework.

jin About The jin is a simplified version of the gin web framework that can help you quickly understand the core principles of a web framework. If thi

Jul 14, 2022
lightweight, idiomatic and composable router for building Go HTTP services

chi is a lightweight, idiomatic and composable router for building Go HTTP services. It's especially good at helping you write large REST API services

Jan 6, 2023
Couper is a lightweight API gateway designed to support developers in building and operating API-driven Web projects
Couper is a lightweight API gateway designed to support developers in building and operating API-driven Web projects

Couper Couper is a lightweight API gateway designed to support developers in building and operating API-driven Web projects. Getting started The quick

Nov 18, 2022
⚡ Rux is an simple and fast web framework. support middleware, compatible http.Handler interface. 简单且快速的 Go web 框架,支持中间件,兼容 http.Handler 接口

Rux Simple and fast web framework for build golang HTTP applications. NOTICE: v1.3.x is not fully compatible with v1.2.x version Fast route match, sup

Dec 8, 2022
Roche is a Code Generator and Web Framework, makes web development super concise with Go, CleanArch
Roche is a Code Generator and Web Framework, makes web development super concise with Go, CleanArch

It is still under development, so please do not use it. We plan to release v.1.0.0 in the summer. roche is a web framework optimized for microservice

Sep 19, 2022
A powerful go web framework for highly scalable and resource efficient web application

webfr A powerful go web framework for highly scalable and resource efficient web application Installation: go get -u github.com/krishpranav/webfr Exa

Nov 28, 2021
A powerful go web framework for highly scalable and resource efficient web application

A powerful go web framework for highly scalable and resource efficient web application

Oct 3, 2022
A web app built using Go Buffalo web framework

Welcome to Buffalo Thank you for choosing Buffalo for your web development needs. Database Setup It looks like you chose to set up your application us

Feb 7, 2022
Vektor - Build production-grade web services quickly
Vektor - Build production-grade web services quickly

Vektor enables development of modern web services in Go. Vektor is designed to simplify the development of web APIs by eliminating boilerplate, using secure defaults, providing plug-in points, and offering common pieces needed for web apps. Vektor is fairly opinionated, but aims to provide flexibility in the right places.

Dec 15, 2022
Flexible E-Commerce Framework on top of Flamingo. Used to build E-Commerce "Portals" and connect it with the help of individual Adapters to other services.

Flamingo Commerce With "Flamingo Commerce" you get your toolkit for building fast and flexible commerce experience applications. A demoshop using the

Dec 31, 2022