A quick and easy way to setup a RESTful JSON API

Overview

Go-Json-Rest

A quick and easy way to setup a RESTful JSON API

godoc license build

Go-Json-Rest is a thin layer on top of net/http that helps building RESTful JSON APIs easily. It provides fast and scalable request routing using a Trie based implementation, helpers to deal with JSON requests and responses, and middlewares for functionalities like CORS, Auth, Gzip, Status ...

Table of content

Features

  • Many examples.
  • Fast and scalable URL routing. It implements the classic route description syntax using a Trie data structure.
  • Architecture based on a router(App) sitting on top of a stack of Middlewares.
  • The Middlewares implement functionalities like Logging, Gzip, CORS, Auth, Status, ...
  • Implemented as a net/http Handler. This standard interface allows combinations with other Handlers.
  • Test package to help writing tests for your API.
  • Monitoring statistics inspired by Memcached.

Install

This package is "go-gettable", just do:

go get github.com/ant0ine/go-json-rest/rest

Vendoring

The recommended way of using this library in your project is to use the "vendoring" method, where this library code is copied in your repository at a specific revision. This page is a good summary of package management in Go.

Middlewares

Core Middlewares:

Name Description
AccessLogApache Access log inspired by Apache mod_log_config
AccessLogJson Access log with records as JSON
AuthBasic Basic HTTP auth
ContentTypeChecker Verify the request content type
Cors CORS server side implementation
Gzip Compress the responses
If Conditionally execute a Middleware at runtime
JsonIndent Easy to read JSON
Jsonp Response as JSONP
PoweredBy Manage the X-Powered-By response header
Recorder Record the status code and content length in the Env
Status Memecached inspired stats about the requests
Timer Keep track of the elapsed time in the Env

Third Party Middlewares:

Name Description
Statsd Send stats to a statsd server
JWT Provides authentication via Json Web Tokens
AuthToken Provides a Token Auth implementation
ForceSSL Forces SSL on requests
SecureRedirect Redirect clients from HTTP to HTTPS

If you have a Go-Json-Rest compatible middleware, feel free to submit a PR to add it in this list, and in the examples.

Examples

All the following examples can be found in dedicated examples repository: https://github.com/ant0ine/go-json-rest-examples

Basics

First examples to try, as an introduction to go-json-rest.

Hello World!

Tradition!

curl demo:

curl -i http://127.0.0.1:8080/

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
		w.WriteJson(map[string]string{"Body": "Hello World!"})
	}))
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

Lookup

Demonstrate how to use the relaxed placeholder (notation #paramName). This placeholder matches everything until the first /, including .

curl demo:

curl -i http://127.0.0.1:8080/lookup/google.com
curl -i http://127.0.0.1:8080/lookup/notadomain

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net"
	"net/http"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/lookup/#host", func(w rest.ResponseWriter, req *rest.Request) {
			ip, err := net.LookupIP(req.PathParam("host"))
			if err != nil {
				rest.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			w.WriteJson(&ip)
		}),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

Countries

Demonstrate simple POST GET and DELETE operations

curl demo:

curl -i -H 'Content-Type: application/json' \
    -d '{"Code":"FR","Name":"France"}' http://127.0.0.1:8080/countries
curl -i -H 'Content-Type: application/json' \
    -d '{"Code":"US","Name":"United States"}' http://127.0.0.1:8080/countries
curl -i http://127.0.0.1:8080/countries/FR
curl -i http://127.0.0.1:8080/countries/US
curl -i http://127.0.0.1:8080/countries
curl -i -X DELETE http://127.0.0.1:8080/countries/FR
curl -i http://127.0.0.1:8080/countries
curl -i -X DELETE http://127.0.0.1:8080/countries/US
curl -i http://127.0.0.1:8080/countries

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
	"sync"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/countries", GetAllCountries),
		rest.Post("/countries", PostCountry),
		rest.Get("/countries/:code", GetCountry),
		rest.Delete("/countries/:code", DeleteCountry),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

type Country struct {
	Code string
	Name string
}

var store = map[string]*Country{}

var lock = sync.RWMutex{}

func GetCountry(w rest.ResponseWriter, r *rest.Request) {
	code := r.PathParam("code")

	lock.RLock()
	var country *Country
	if store[code] != nil {
		country = &Country{}
		*country = *store[code]
	}
	lock.RUnlock()

	if country == nil {
		rest.NotFound(w, r)
		return
	}
	w.WriteJson(country)
}

func GetAllCountries(w rest.ResponseWriter, r *rest.Request) {
	lock.RLock()
	countries := make([]Country, len(store))
	i := 0
	for _, country := range store {
		countries[i] = *country
		i++
	}
	lock.RUnlock()
	w.WriteJson(&countries)
}

func PostCountry(w rest.ResponseWriter, r *rest.Request) {
	country := Country{}
	err := r.DecodeJsonPayload(&country)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if country.Code == "" {
		rest.Error(w, "country code required", 400)
		return
	}
	if country.Name == "" {
		rest.Error(w, "country name required", 400)
		return
	}
	lock.Lock()
	store[country.Code] = &country
	lock.Unlock()
	w.WriteJson(&country)
}

func DeleteCountry(w rest.ResponseWriter, r *rest.Request) {
	code := r.PathParam("code")
	lock.Lock()
	delete(store, code)
	lock.Unlock()
	w.WriteHeader(http.StatusOK)
}

Users

Demonstrate how to use Method Values.

Method Values have been introduced in Go 1.1.

This shows how to map a Route to a method of an instantiated object (i.e: receiver of the method)

curl demo:

curl -i -H 'Content-Type: application/json' \
    -d '{"Name":"Antoine"}' http://127.0.0.1:8080/users
curl -i http://127.0.0.1:8080/users/0
curl -i -X PUT -H 'Content-Type: application/json' \
    -d '{"Name":"Antoine Imbert"}' http://127.0.0.1:8080/users/0
curl -i -X DELETE http://127.0.0.1:8080/users/0
curl -i http://127.0.0.1:8080/users

code:

package main

import (
	"fmt"
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
	"sync"
)

func main() {

	users := Users{
		Store: map[string]*User{},
	}

	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/users", users.GetAllUsers),
		rest.Post("/users", users.PostUser),
		rest.Get("/users/:id", users.GetUser),
		rest.Put("/users/:id", users.PutUser),
		rest.Delete("/users/:id", users.DeleteUser),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

type User struct {
	Id   string
	Name string
}

type Users struct {
	sync.RWMutex
	Store map[string]*User
}

func (u *Users) GetAllUsers(w rest.ResponseWriter, r *rest.Request) {
	u.RLock()
	users := make([]User, len(u.Store))
	i := 0
	for _, user := range u.Store {
		users[i] = *user
		i++
	}
	u.RUnlock()
	w.WriteJson(&users)
}

func (u *Users) GetUser(w rest.ResponseWriter, r *rest.Request) {
	id := r.PathParam("id")
	u.RLock()
	var user *User
	if u.Store[id] != nil {
		user = &User{}
		*user = *u.Store[id]
	}
	u.RUnlock()
	if user == nil {
		rest.NotFound(w, r)
		return
	}
	w.WriteJson(user)
}

func (u *Users) PostUser(w rest.ResponseWriter, r *rest.Request) {
	user := User{}
	err := r.DecodeJsonPayload(&user)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	u.Lock()
	id := fmt.Sprintf("%d", len(u.Store)) // stupid
	user.Id = id
	u.Store[id] = &user
	u.Unlock()
	w.WriteJson(&user)
}

func (u *Users) PutUser(w rest.ResponseWriter, r *rest.Request) {
	id := r.PathParam("id")
	u.Lock()
	if u.Store[id] == nil {
		rest.NotFound(w, r)
		u.Unlock()
		return
	}
	user := User{}
	err := r.DecodeJsonPayload(&user)
	if err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		u.Unlock()
		return
	}
	user.Id = id
	u.Store[id] = &user
	u.Unlock()
	w.WriteJson(&user)
}

func (u *Users) DeleteUser(w rest.ResponseWriter, r *rest.Request) {
	id := r.PathParam("id")
	u.Lock()
	delete(u.Store, id)
	u.Unlock()
	w.WriteHeader(http.StatusOK)
}

Applications

Common use cases, found in many applications.

API and static files

Combine Go-Json-Rest with other handlers.

api.MakeHandler() is a valid http.Handler, and can be combined with other handlers. In this example the api handler is used under the /api/ prefix, while a FileServer is instantiated under the /static/ prefix.

curl demo:

curl -i http://127.0.0.1:8080/api/message
curl -i http://127.0.0.1:8080/static/main.go

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)

	router, err := rest.MakeRouter(
		rest.Get("/message", func(w rest.ResponseWriter, req *rest.Request) {
			w.WriteJson(map[string]string{"Body": "Hello World!"})
		}),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)

	http.Handle("/api/", http.StripPrefix("/api", api.MakeHandler()))

	http.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir("."))))

	log.Fatal(http.ListenAndServe(":8080", nil))
}

GORM

Demonstrate basic CRUD operation using a store based on MySQL and GORM

GORM is simple ORM library for Go. In this example the same struct is used both as the GORM model and as the JSON model.

curl demo:

curl -i -H 'Content-Type: application/json' \
    -d '{"Message":"this is a test"}' http://127.0.0.1:8080/reminders
curl -i http://127.0.0.1:8080/reminders/1
curl -i http://127.0.0.1:8080/reminders
curl -i -X PUT -H 'Content-Type: application/json' \
    -d '{"Message":"is updated"}' http://127.0.0.1:8080/reminders/1
curl -i -X DELETE http://127.0.0.1:8080/reminders/1

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"log"
	"net/http"
	"time"
)

func main() {

	i := Impl{}
	i.InitDB()
	i.InitSchema()

	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/reminders", i.GetAllReminders),
		rest.Post("/reminders", i.PostReminder),
		rest.Get("/reminders/:id", i.GetReminder),
		rest.Put("/reminders/:id", i.PutReminder),
		rest.Delete("/reminders/:id", i.DeleteReminder),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

type Reminder struct {
	Id        int64     `json:"id"`
	Message   string    `sql:"size:1024" json:"message"`
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
	DeletedAt time.Time `json:"-"`
}

type Impl struct {
	DB *gorm.DB
}

func (i *Impl) InitDB() {
	var err error
	i.DB, err = gorm.Open("mysql", "gorm:gorm@/gorm?charset=utf8&parseTime=True")
	if err != nil {
		log.Fatalf("Got error when connect database, the error is '%v'", err)
	}
	i.DB.LogMode(true)
}

func (i *Impl) InitSchema() {
	i.DB.AutoMigrate(&Reminder{})
}

func (i *Impl) GetAllReminders(w rest.ResponseWriter, r *rest.Request) {
	reminders := []Reminder{}
	i.DB.Find(&reminders)
	w.WriteJson(&reminders)
}

func (i *Impl) GetReminder(w rest.ResponseWriter, r *rest.Request) {
	id := r.PathParam("id")
	reminder := Reminder{}
	if i.DB.First(&reminder, id).Error != nil {
		rest.NotFound(w, r)
		return
	}
	w.WriteJson(&reminder)
}

func (i *Impl) PostReminder(w rest.ResponseWriter, r *rest.Request) {
	reminder := Reminder{}
	if err := r.DecodeJsonPayload(&reminder); err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if err := i.DB.Save(&reminder).Error; err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteJson(&reminder)
}

func (i *Impl) PutReminder(w rest.ResponseWriter, r *rest.Request) {

	id := r.PathParam("id")
	reminder := Reminder{}
	if i.DB.First(&reminder, id).Error != nil {
		rest.NotFound(w, r)
		return
	}

	updated := Reminder{}
	if err := r.DecodeJsonPayload(&updated); err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	reminder.Message = updated.Message

	if err := i.DB.Save(&reminder).Error; err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteJson(&reminder)
}

func (i *Impl) DeleteReminder(w rest.ResponseWriter, r *rest.Request) {
	id := r.PathParam("id")
	reminder := Reminder{}
	if i.DB.First(&reminder, id).Error != nil {
		rest.NotFound(w, r)
		return
	}
	if err := i.DB.Delete(&reminder).Error; err != nil {
		rest.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
}

CORS

Demonstrate how to setup CorsMiddleware around all the API endpoints.

curl demo:

curl -i http://127.0.0.1:8080/countries

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	api.Use(&rest.CorsMiddleware{
		RejectNonCorsRequests: false,
		OriginValidator: func(origin string, request *rest.Request) bool {
			return origin == "http://my.other.host"
		},
		AllowedMethods: []string{"GET", "POST", "PUT"},
		AllowedHeaders: []string{
			"Accept", "Content-Type", "X-Custom-Header", "Origin"},
		AccessControlAllowCredentials: true,
		AccessControlMaxAge:           3600,
	})
	router, err := rest.MakeRouter(
		rest.Get("/countries", GetAllCountries),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

type Country struct {
	Code string
	Name string
}

func GetAllCountries(w rest.ResponseWriter, r *rest.Request) {
	w.WriteJson(
		[]Country{
			Country{
				Code: "FR",
				Name: "France",
			},
			Country{
				Code: "US",
				Name: "United States",
			},
		},
	)
}

JSONP

Demonstrate how to use the JSONP middleware.

curl demo:

curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/?cb=parseResponse

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	api.Use(&rest.JsonpMiddleware{
		CallbackNameKey: "cb",
	})
	api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
		w.WriteJson(map[string]string{"Body": "Hello World!"})
	}))
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

Basic Auth

Demonstrate how to setup AuthBasicMiddleware as a pre-routing middleware.

curl demo:

curl -i http://127.0.0.1:8080/
curl -i -u admin:admin http://127.0.0.1:8080/

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	api.Use(&rest.AuthBasicMiddleware{
		Realm: "test zone",
		Authenticator: func(userId string, password string) bool {
			if userId == "admin" && password == "admin" {
				return true
			}
			return false
		},
	})
	api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
		w.WriteJson(map[string]string{"Body": "Hello World!"})
	}))
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

ForceSSL

Demonstrate how to use the ForceSSL Middleware to force HTTPS on requests to a go-json-rest API.

For the purposes of this demo, we are using HTTP for all requests and checking the X-Forwarded-Proto header to see if it is set to HTTPS (many routers set this to show what type of connection the client is using, such as Heroku). To do a true HTTPS test, make sure and use http.ListenAndServeTLS with a valid certificate and key file.

Additional documentation for the ForceSSL middleware can be found here.

curl demo:

curl -i 127.0.0.1:8080/
curl -H "X-Forwarded-Proto:https" -i 127.0.0.1:8080/

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/jadengore/go-json-rest-middleware-force-ssl"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	api.Use(&forceSSL.Middleware{
		TrustXFPHeader:     true,
		Enable301Redirects: false,
	})
	api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
		w.WriteJson(map[string]string{"body": "Hello World!"})
	}))

	// For the purposes of this demo, only HTTP connections accepted.
	// For true HTTPS, use ListenAndServeTLS.
	// https://golang.org/pkg/net/http/#ListenAndServeTLS
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

Status

Demonstrate how to setup a /.status endpoint

Inspired by memcached "stats", this optional feature can be enabled to help monitoring the service. This example shows how to enable the stats, and how to setup the /.status route.

curl demo:

curl -i http://127.0.0.1:8080/.status
curl -i http://127.0.0.1:8080/.status
...

Output example:

{
  "Pid": 21732,
  "UpTime": "1m15.926272s",
  "UpTimeSec": 75.926272,
  "Time": "2013-03-04 08:00:27.152986 +0000 UTC",
  "TimeUnix": 1362384027,
  "StatusCodeCount": {
        "200": 53,
        "404": 11
  },
  "TotalCount": 64,
  "TotalResponseTime": "16.777ms",
  "TotalResponseTimeSec": 0.016777,
  "AverageResponseTime": "262.14us",
  "AverageResponseTimeSec": 0.00026214
}

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	statusMw := &rest.StatusMiddleware{}
	api.Use(statusMw)
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/.status", func(w rest.ResponseWriter, r *rest.Request) {
			w.WriteJson(statusMw.GetStatus())
		}),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

Status Auth

Demonstrate how to setup a /.status endpoint protected with basic authentication.

This is a good use case of middleware applied to only one API endpoint.

curl demo:

curl -i http://127.0.0.1:8080/countries
curl -i http://127.0.0.1:8080/.status
curl -i -u admin:admin http://127.0.0.1:8080/.status
...

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	statusMw := &rest.StatusMiddleware{}
	api.Use(statusMw)
	api.Use(rest.DefaultDevStack...)
	auth := &rest.AuthBasicMiddleware{
		Realm: "test zone",
		Authenticator: func(userId string, password string) bool {
			if userId == "admin" && password == "admin" {
				return true
			}
			return false
		},
	}
	router, err := rest.MakeRouter(
		rest.Get("/countries", GetAllCountries),
		rest.Get("/.status", auth.MiddlewareFunc(
			func(w rest.ResponseWriter, r *rest.Request) {
				w.WriteJson(statusMw.GetStatus())
			},
		)),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

type Country struct {
	Code string
	Name string
}

func GetAllCountries(w rest.ResponseWriter, r *rest.Request) {
	w.WriteJson(
		[]Country{
			Country{
				Code: "FR",
				Name: "France",
			},
			Country{
				Code: "US",
				Name: "United States",
			},
		},
	)
}

Advanced

More advanced use cases.

JWT

Demonstrates how to use the Json Web Token Auth Middleware to authenticate via a JWT token.

curl demo:

curl -d '{"username": "admin", "password": "admin"}' -H "Content-Type:application/json" http://localhost:8080/api/login
curl -H "Authorization:Bearer TOKEN_RETURNED_FROM_ABOVE" http://localhost:8080/api/auth_test
curl -H "Authorization:Bearer TOKEN_RETURNED_FROM_ABOVE" http://localhost:8080/api/refresh_token

code:

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/StephanDollberg/go-json-rest-middleware-jwt"
	"github.com/ant0ine/go-json-rest/rest"
)

func handle_auth(w rest.ResponseWriter, r *rest.Request) {
	w.WriteJson(map[string]string{"authed": r.Env["REMOTE_USER"].(string)})
}

func main() {
	jwt_middleware := &jwt.JWTMiddleware{
		Key:        []byte("secret key"),
		Realm:      "jwt auth",
		Timeout:    time.Hour,
		MaxRefresh: time.Hour * 24,
		Authenticator: func(userId string, password string) bool {
			return userId == "admin" && password == "admin"
		}}

	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	// we use the IfMiddleware to remove certain paths from needing authentication
	api.Use(&rest.IfMiddleware{
		Condition: func(request *rest.Request) bool {
			return request.URL.Path != "/login"
		},
		IfTrue: jwt_middleware,
	})
	api_router, _ := rest.MakeRouter(
		rest.Post("/login", jwt_middleware.LoginHandler),
		rest.Get("/auth_test", handle_auth),
		rest.Get("/refresh_token", jwt_middleware.RefreshHandler),
	)
	api.SetApp(api_router)

	http.Handle("/api/", http.StripPrefix("/api", api.MakeHandler()))

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Streaming

Demonstrate a streaming REST API, where the data is "flushed" to the client ASAP.

The stream format is a Line Delimited JSON.

curl demo:

curl -i http://127.0.0.1:8080/stream

Output:

HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 16 Feb 2014 00:39:19 GMT
Transfer-Encoding: chunked

{"Name":"thing #1"}
{"Name":"thing #2"}
{"Name":"thing #3"}

code:

package main

import (
	"fmt"
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
	"time"
)

func main() {
	api := rest.NewApi()
	api.Use(&rest.AccessLogApacheMiddleware{})
	api.Use(rest.DefaultCommonStack...)
	router, err := rest.MakeRouter(
		rest.Get("/stream", StreamThings),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

type Thing struct {
	Name string
}

func StreamThings(w rest.ResponseWriter, r *rest.Request) {
	cpt := 0
	for {
		cpt++
		w.WriteJson(
			&Thing{
				Name: fmt.Sprintf("thing #%d", cpt),
			},
		)
		w.(http.ResponseWriter).Write([]byte("\n"))
		// Flush the buffer to client
		w.(http.Flusher).Flush()
		// wait 3 seconds
		time.Sleep(time.Duration(3) * time.Second)
	}
}

Non JSON payload

Exceptional use of non JSON payloads.

The ResponseWriter implementation provided by go-json-rest is designed to build JSON responses. In order to serve different kind of content, it is recommended to either: a) use another server and configure CORS (see the cors/ example) b) combine the api.MakeHandler() with another http.Handler (see api-and-static/ example)

That been said, exceptionally, it can be convenient to return a different content type on a JSON endpoint. In this case, setting the Content-Type and using the type assertion to access the Write method is enough. As shown in this example.

curl demo:

curl -i http://127.0.0.1:8080/message.txt

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/message.txt", func(w rest.ResponseWriter, req *rest.Request) {
			w.Header().Set("Content-Type", "text/plain")
			w.(http.ResponseWriter).Write([]byte("Hello World!"))
		}),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

API Versioning

First, API versioning is not easy and you may want to favor a mechanism that uses only backward compatible changes and deprecation cycles.

That been said, here is an example of API versioning using Semver

It defines a middleware that parses the version, checks a min and a max, and makes it available in the request.Env.

curl demo:

curl -i http://127.0.0.1:8080/api/1.0.0/message
curl -i http://127.0.0.1:8080/api/2.0.0/message
curl -i http://127.0.0.1:8080/api/2.0.1/message
curl -i http://127.0.0.1:8080/api/0.0.1/message
curl -i http://127.0.0.1:8080/api/4.0.1/message

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/coreos/go-semver/semver"
	"log"
	"net/http"
)

type SemVerMiddleware struct {
	MinVersion string
	MaxVersion string
}

func (mw *SemVerMiddleware) MiddlewareFunc(handler rest.HandlerFunc) rest.HandlerFunc {

	minVersion, err := semver.NewVersion(mw.MinVersion)
	if err != nil {
		panic(err)
	}

	maxVersion, err := semver.NewVersion(mw.MaxVersion)
	if err != nil {
		panic(err)
	}

	return func(writer rest.ResponseWriter, request *rest.Request) {

		version, err := semver.NewVersion(request.PathParam("version"))
		if err != nil {
			rest.Error(
				writer,
				"Invalid version: "+err.Error(),
				http.StatusBadRequest,
			)
			return
		}

		if version.LessThan(*minVersion) {
			rest.Error(
				writer,
				"Min supported version is "+minVersion.String(),
				http.StatusBadRequest,
			)
			return
		}

		if maxVersion.LessThan(*version) {
			rest.Error(
				writer,
				"Max supported version is "+maxVersion.String(),
				http.StatusBadRequest,
			)
			return
		}

		request.Env["VERSION"] = version
		handler(writer, request)
	}
}

func main() {

	svmw := SemVerMiddleware{
		MinVersion: "1.0.0",
		MaxVersion: "3.0.0",
	}
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/#version/message", svmw.MiddlewareFunc(
			func(w rest.ResponseWriter, req *rest.Request) {
				version := req.Env["VERSION"].(*semver.Version)
				if version.Major == 2 {
					// https://en.wikipedia.org/wiki/Second-system_effect
					w.WriteJson(map[string]string{
						"Body": "Hello broken World!",
					})
				} else {
					w.WriteJson(map[string]string{
						"Body": "Hello World!",
					})
				}
			},
		)),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	http.Handle("/api/", http.StripPrefix("/api", api.MakeHandler()))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Statsd

Demonstrate how to use the Statsd Middleware to collect statistics about the requests/reponses. This middleware is based on the g2s statsd client.

curl demo:

# start statsd server
# monitor network
ngrep -d any port 8125

curl -i http://127.0.0.1:8080/message
curl -i http://127.0.0.1:8080/doesnotexist

code:

package main

import (
	"github.com/ant0ine/go-json-rest-middleware-statsd"
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
	"time"
)

func main() {
	api := rest.NewApi()
	api.Use(&statsd.StatsdMiddleware{})
	api.Use(rest.DefaultDevStack...)
	api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, req *rest.Request) {

		// take more than 1ms so statsd can report it
		time.Sleep(100 * time.Millisecond)

		w.WriteJson(map[string]string{"Body": "Hello World!"})
	}))
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

NewRelic

NewRelic integration based on the GoRelic plugin: github.com/yvasiyarov/gorelic

curl demo:

curl -i http://127.0.0.1:8080/

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/yvasiyarov/go-metrics"
	"github.com/yvasiyarov/gorelic"
	"log"
	"net/http"
	"time"
)

type NewRelicMiddleware struct {
	License string
	Name    string
	Verbose bool
	agent   *gorelic.Agent
}

func (mw *NewRelicMiddleware) MiddlewareFunc(handler rest.HandlerFunc) rest.HandlerFunc {

	mw.agent = gorelic.NewAgent()
	mw.agent.NewrelicLicense = mw.License
	mw.agent.HTTPTimer = metrics.NewTimer()
	mw.agent.Verbose = mw.Verbose
	mw.agent.NewrelicName = mw.Name
	mw.agent.CollectHTTPStat = true
	mw.agent.Run()

	return func(writer rest.ResponseWriter, request *rest.Request) {

		handler(writer, request)

		// the timer middleware keeps track of the time
		startTime := request.Env["START_TIME"].(*time.Time)
		mw.agent.HTTPTimer.UpdateSince(*startTime)
	}
}

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	api.Use(&NewRelicMiddleware{
		License: "<REPLACE WITH THE LICENSE KEY>",
		Name:    "<REPLACE WITH THE APP NAME>",
		Verbose: true,
	})
	api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
		w.WriteJson(map[string]string{"Body": "Hello World!"})
	}))
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

Graceful Shutdown

This example uses https://github.com/tylerb/graceful to try to be nice with the clients waiting for responses during a server shutdown (or restart). The HTTP response takes 10 seconds to be completed, printing a message on the wire every second. 10 seconds is also the timeout set for the graceful shutdown. You can play with these numbers to show that the server waits for the responses to complete.

curl demo:

curl -i http://127.0.0.1:8080/message

code:

package main

import (
	"fmt"
	"github.com/ant0ine/go-json-rest/rest"
        "gopkg.in/tylerb/graceful.v1"
	"log"
	"net/http"
	"time"
)

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/message", func(w rest.ResponseWriter, req *rest.Request) {
			for cpt := 1; cpt <= 10; cpt++ {

				// wait 1 second
				time.Sleep(time.Duration(1) * time.Second)

				w.WriteJson(map[string]string{
					"Message": fmt.Sprintf("%d seconds", cpt),
				})
				w.(http.ResponseWriter).Write([]byte("\n"))

				// Flush the buffer to client
				w.(http.Flusher).Flush()
			}
		}),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)

	server := &graceful.Server{
		Timeout: 10 * time.Second,
		Server: &http.Server{
			Addr:    ":8080",
			Handler: api.MakeHandler(),
		},
	}

	log.Fatal(server.ListenAndServe())
}

SPDY

Demonstrate how to use SPDY with https://github.com/shykes/spdy-go

For a command line client, install spdycat from: https://github.com/tatsuhiro-t/spdylay

spdycat demo:

spdycat -v --no-tls -2 http://localhost:8080/users/0

code:

package main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/shykes/spdy-go"
	"log"
)

type User struct {
	Id   string
	Name string
}

func GetUser(w rest.ResponseWriter, req *rest.Request) {
	user := User{
		Id:   req.PathParam("id"),
		Name: "Antoine",
	}
	w.WriteJson(&user)
}

func main() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		rest.Get("/users/:id", GetUser),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	log.Fatal(spdy.ListenAndServeTCP(":8080", api.MakeHandler()))
}

GAE

Demonstrate a simple Google App Engine app

Here are my steps to make it work with the GAE SDK. (Probably not the best ones)

Assuming that go-json-rest is installed using "go get" and that the GAE SDK is also installed.

Setup:

  • copy this examples/gae/ dir outside of the go-json-rest/ tree
  • cd gae/
  • mkdir -p github.com/ant0ine
  • cp -r $GOPATH/src/github.com/ant0ine/go-json-rest github.com/ant0ine/go-json-rest
  • rm -rf github.com/ant0ine/go-json-rest/examples/
  • path/to/google_appengine/dev_appserver.py .

curl demo:

curl -i http://127.0.0.1:8080/message

code:

package gaehelloworld

import (
	"github.com/ant0ine/go-json-rest/rest"
	"log"
	"net/http"
)

func init() {
	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		&rest.Get("/message", func(w rest.ResponseWriter, req *rest.Request) {
			w.WriteJson(map[string]string{"Body": "Hello World!"})
		}),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	http.Handle("/", api.MakeHandler())
}

Websocket

Demonstrate how to run websocket in go-json-rest

go client demo:

origin := "http://localhost:8080/"
url := "ws://localhost:8080/ws"
ws, err := websocket.Dial(url, "", origin)
if err != nil {
	log.Fatal(err)
}
if _, err := ws.Write([]byte("hello, world\n")); err != nil {
	log.Fatal(err)
}
var msg = make([]byte, 512)
var n int
if n, err = ws.Read(msg); err != nil {
	log.Fatal(err)
}
log.Printf("Received: %s.", msg[:n])

code:

package main

import (
	"io"
	"log"
	"net/http"

	"github.com/ant0ine/go-json-rest/rest"
	"golang.org/x/net/websocket"
)

func main() {
	wsHandler := websocket.Handler(func(ws *websocket.Conn) {
		io.Copy(ws, ws)
	})

	router, err := rest.MakeRouter(
		rest.Get("/ws", func(w rest.ResponseWriter, r *rest.Request) {
			wsHandler.ServeHTTP(w.(http.ResponseWriter), r.Request)
		}),
	)
	if err != nil {
		log.Fatal(err)
	}

	api := rest.NewApi()
	api.Use(rest.DefaultDevStack...)
	api.SetApp(router)
	log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

External Documentation

Old v1 blog posts:

Version 3 release notes

What's New in v3

  • Public Middlewares. (12 included in the package)
  • A new App interface. (the router being the provided App)
  • A new Api object that manages the Middlewares and the App.
  • Optional and interchangeable App/router.

Here is for instance the new minimal "Hello World!"

api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
        w.WriteJson(map[string]string{"Body": "Hello World!"})
}))
http.ListenAndServe(":8080", api.MakeHandler())

All 19 examples have been updated to use the new API. See here

Deprecating the ResourceHandler

V3 is about deprecating the ResourceHandler in favor of a new API that exposes the Middlewares. As a consequence, all the Middlewares are now public, and the new Api object helps putting them together as a stack. Some default stack configurations are offered. The router is now an App that sits on top of the stack of Middlewares. Which means that the router is no longer required to use Go-Json-Rest.

Design ideas and discussion See here

Migration guide from v2 to v3

V3 introduces an API change (see Semver). But it was possible to maintain backward compatibility, and so, ResourceHandler still works. ResourceHandler does the same thing as in V2, but it is now considered as deprecated, and will be removed in a few months. In the meantime, it logs a deprecation warning.

How to map the ResourceHandler options to the new stack of middlewares ?

  • EnableGzip bool: Just include GzipMiddleware in the stack of middlewares.
  • DisableJsonIndent bool: Just don't include JsonIndentMiddleware in the stack of middlewares.
  • EnableStatusService bool: Include StatusMiddleware in the stack and keep a reference to it to access GetStatus().
  • EnableResponseStackTrace bool: Same exact option but moved to RecoverMiddleware.
  • EnableLogAsJson bool: Include AccessLogJsonMiddleware, and possibly remove AccessLogApacheMiddleware.
  • EnableRelaxedContentType bool: Just don't include ContentTypeCheckerMiddleware.
  • OuterMiddlewares []Middleware: You are now building the full stack, OuterMiddlewares are the first in the list.
  • PreRoutingMiddlewares []Middleware: You are now building the full stack, OuterMiddlewares are the last in the list.
  • Logger *log.Logger: Same option but moved to AccessLogApacheMiddleware and AccessLogJsonMiddleware.
  • LoggerFormat AccessLogFormat: Same exact option but moved to AccessLogApacheMiddleware.
  • DisableLogger bool: Just don't include any access log middleware.
  • ErrorLogger *log.Logger: Same exact option but moved to RecoverMiddleware.
  • XPoweredBy string: Same exact option but moved to PoweredByMiddleware.
  • DisableXPoweredBy bool: Just don't include PoweredByMiddleware.

Version 2 release notes

  • Middlewares, the notion of middleware is now formally defined. They can be setup as global pre-routing Middlewares wrapping all the endpoints, or on a per endpoint basis. In fact the internal code of go-json-rest is itself implemented with Middlewares, they are just hidden behind configuration boolean flags to make these very common options even easier to use.

  • A new ResponseWriter. This is now an interface, and allows Middlewares to wrap the writer. The provided writer implements, in addition of rest.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, and http.ResponseWriter. A lot more Go-ish, and very similar to net/http.

  • The AuthBasic and CORS Middlewares have been added. More to come in the future.

  • Faster, more tasks are performed at init time, and less for each request.

  • New documentation, with more examples.

  • A lot of other small improvements, See the Migration guide to v2

Migration guide from v1 to v2

Go-Json-Rest follows Semver and a few breaking changes have been introduced with the v2.

The import path has changed to github.com/ant0ine/go-json-rest/rest

This is more conform to Go style, and makes goimports work.

This:

import (
        "github.com/ant0ine/go-json-rest"
)

has to be changed to this:

import (
        "github.com/ant0ine/go-json-rest/rest"
)

rest.ResponseWriter is now an interface

This change allows the ResponseWriter to be wrapped, like the one of the net/http package. This is much more powerful, and allows the creation of Middlewares that wrap the writer. The gzip option, for instance, uses this to encode the payload (see gzip.go).

This:

func (w *rest.ResponseWriter, req *rest.Request) {
        ...
}

has to be changed to this:

func (w rest.ResponseWriter, req *rest.Request) {
        ...
}

SetRoutes now takes pointers to Route

Instead of copying Route structures everywhere, pointers are now used. This is more elegant, more efficient, and will allow more sophisticated Route manipulations in the future (like reverse route resolution).

This:

handler.SetRoutes(
		rest.Route{
		      // ...
		},
)

has to be changed to this:

handler.SetRoutes(
		&rest.Route{
		      // ...
		},
)

The notion of Middleware is now formally defined

A middleware is an object satisfying this interface:

type Middleware interface {
	MiddlewareFunc(handler HandlerFunc) HandlerFunc
}

Code using PreRoutingMiddleware will have to be adapted to provide a list of Middleware objects. See the Basic Auth example.

Flush(), CloseNotify() and Write() are not directly exposed anymore

They used to be public methods of the ResponseWriter. The implementation is still there but a type assertion of the corresponding interface is now necessary. Regarding these features, a rest.ResponseWriter now behaves exactly as the http.ResponseWriter implementation provided by net/http.

This:

writer.Flush()

has to be changed to this:

writer.(http.Flusher).Flush()

The /.status endpoint is not created automatically anymore

The route has to be manually defined. See the Status example. This is more flexible (the route is customizable), and allows combination with Middlewarres. See for instance how to protect this status endpoint with the AuthBasic middleware.

Request utility methods have changed

Overall, they provide the same features, but with two methods instead of three, better names, and without the confusing UriForWithParams.

  • func (r *Request) UriBase() url.URL is now func (r *Request) BaseUrl() *url.URL, Note the pointer as the returned value.

  • func (r *Request) UriForWithParams(path string, parameters map[string][]string) url.URL is now func (r *Request) UrlFor(path string, queryParams map[string][]string) *url.URL.

  • func (r *Request) UriFor(path string) url.URL has be removed.

Thanks

Copyright (c) 2013-2016 Antoine Imbert

MIT License

Analytics

Comments
  • Ideas for Go-Json-Rest v3.0.0 (RFC)

    Ideas for Go-Json-Rest v3.0.0 (RFC)

    Ideas for Go-Json-Rest v3.0.0 (RFC)

    V3 is the opportunity for API changes and improvements.

    In the past two years Go-Json-Rest has changed a lot. It got the notion of middleware, bringing basic features like Auth, CORS, JSONP that were missing at the beginning. Over time the list of options and settings has doubled. This is reflected in the main object ResourceHandler that has become a long list of options. It works, but it could be more convenient to use. The goal of the v3 is to provide a replacement for ResourceHandler that is simpler to use and more flexible.

    The ResourceHandler does not do much, given the settings, it instantiates the middlewares and the router, and wraps all that in order to produce a net/http Handler. The middlewares and the router are in fact the low-level api of go-json-rest. The idea of v3 is to open it, make it public.

    Proposed new API

    1) Make public the existing private Middlewares

    So in addition the following, already public, middlewares:

    • AuthBasicMiddleware
    • CorsMiddleware
    • JsonpMiddleware

    We will get the following ones:

    • AccessLogApacheMiddleware
    • AccessLogJsonMiddleware
    • TimerMiddleware
    • RecorderMiddleware
    • GzipMiddleware
    • RecoverMiddleware
    • ContentTypeCheckerMiddleware
    • JsonIndentMiddleware
    • PoweredByMiddleware
    • StatusMiddleware

    2) Propose some predefined stacks of Middlewares

    Precise lists and options to be determined, but for instance:

    var DefaultDevStack = []Middleware{
            &AccessLogApacheMiddleware{},
            &TimerMiddleware{},
            &RecorderMiddleware{},
            &JsonIndentMiddleware{},
            &PoweredByMiddleware{},
            &RecoverMiddleware{
                    EnableResponseStackTrace: true,
            },
    }
    
    var DefaultProdStack = []Middleware{
            &AccessLogApacheMiddleware{},
            &TimerMiddleware{},
            &RecorderMiddleware{},
            &GzipMiddleware{},
            &PoweredByMiddleware{},
            &RecoverMiddleware{},
            &CheckContentTypeMiddleware{},
    }
    

    Most of the options and settings of the current ResourceHandler end up being options of the middlewares, or just including of not including a middleware. Example:

    MyStack := []Middleware{
            &AccessLogApacheMiddleware{
                   Logger: &myLogger,
                   Format: rest.CombinedLogFormat,
            },
            &TimerMiddleware{},
            &RecorderMiddleware{},
            &GzipMiddleware{},
            &RecoverMiddleware{
                   Logger: &myLogger,
            },
            &CheckContentTypeMiddleware{},
    }
    

    3) A new App interface

    Go-Json-Rest already defines this interface:

        type HandlerFunc func(ResponseWriter, *Request)
    
        type Middleware interface {
            MiddlewareFunc(handler HandlerFunc) HandlerFunc
        }
    

    In v3, it will be completed by this new:

        type App interface {
            AppFunc() HandlerFunc
        }
    

    v3 will also offer the two following adapter types. Convenient to write simple Apps or Middlewares without defining types. (Unit tests are written this way, for instance)

    type MiddlewareSimple func(handler HandlerFunc) HandlerFunc
    func (ms MiddlewareSimple) MiddlewareFunc(handler HandlerFunc) HandlerFunc {
    }
    
    type AppSimple HandlerFunc
    func (as AppSimple) AppFunc() HandlerFunc {
    }
    

    It allows to write

            api.SetApp(AppSimple(func(w ResponseWriter, r *Request) {
                    ...
            }))
    
            api.Use(MiddlewareSimple(func(handler HandlerFunc) HandlerFunc {
                    return func(w ResponseWriter, r *Request) {
                            ...
                    }
            }))
    

    4) Explicitly build the router as an App

    The router already implements the App interface by providing the AppFunc method. The following function will be added to explicitly build it:

    func MakeRouter(routes ...Routes) (App, error) {
    }
    

    Building a Go-Json-Rest api now consists in assembling a stack of Middlewares and putting an App on top of it. It also open the door to interchangeable routers by allowing third party routers to be wrapped inside an App.

    5) Finally, this new Api object that provides the syntactic sugar

    type Api struct {
    }
    
    func NewApi() *Api {
    }
    
    func SetApp(app App) {
    }
    
    func (api *Api) Use(middlewares ...Middleware) {
    }
    
    func (api *Api) MakeHandler() http.Handler {
    }
    

    The new "Hello World!" example

    "Hello World!" with JSONP support:

    func main() {
    
            api := rest.NewApi()
    
            // the Middleware stack
            api.Use(rest.DefaultDevStack...)
            api.Use(&rest.JsonpMiddleware{
                    CallbackNameKey: "cb",
            })
    
            // build the App, here the rest Router
            router, err := rest.MakeRouter(
                    &Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) {
                            w.WriteJson(map[string]string{"Body": "Hello World!"})
                    }},
            )
            if err != nil {
                    log.Fatal(err)
            }
            api.SetApp(router)
    
            // build and run the handler
            log.Fatal(http.ListenAndServe(
                    ":8080",
                    api.MakeHandler(),
            ))
    }
    

    Compared to the previous version:

    This:

    func main() {
            handler := rest.ResourceHandler{
                    PreRoutingMiddlewares: []rest.Middleware{
                            &rest.JsonpMiddleware{
                                    CallbackNameKey: "cb",
                            },
                    },
            }
            err := handler.SetRoutes(
                    &rest.Route{"GET", "/message", func(w rest.ResponseWriter, req *rest.Request) {
                            w.WriteJson(map[string]string{"Body": "Hello World!"})
                    }},
            )
            if err != nil {
                    log.Fatal(err)
            }
            log.Fatal(http.ListenAndServe(":8080", &handler))
    }
    

    Semver and backward compatibility

    Go-Json-Rest follows Semver, breaking API changes are introduced only with major version changes. In this particular case, even if the API changes are important, it is possible to maintain the backward compatibility and just mark the old API as deprecated.

    The objects will be marked as deprecated, and after a few months, can be removed from the package.

    opened by ant0ine 25
  • Excluding certain path from Middleware handling

    Excluding certain path from Middleware handling

    I'm working on a user database REST API using go-json-rest and authentication using JWT from https://github.com/StephanDollberg/go-json-rest-middleware-jwt.

    Users register with "POST /user". They should be able to do so without being authenticated. But the middleware intercepts the request and fails it.

    So far the only ways I can think of to work around this are:

    1. Route the registration call under another path which is handled by a separate App (which doesn't setup the JWT middleware)
    2. Wrap, or re-implement the JWT middleware to allow "POST /user" through without authentication.

    Both look ugly - the first because it breaks the RESTful principles as I understand them and the second because it basically means that I replicate most of the functionality of an existing package.

    Is there a better way to achieve what I need?

    Thanks.

    enhancement 
    opened by amosshapira 21
  • added jwt authentication middleware

    added jwt authentication middleware

    This PR adds a middleware for Json-Web-Token authentication (JWT). I use the jwt-go package for the jwt implementation. I don't know what your stance on third-party packages is as I think this would be the first one.

    I have mirrored the rest to be in line with the basic auth middleware.

    If you like this we can expand it by adding more signing algorithms and adding a refresh option. Also, I can add an example to the readme.

    Looking forward to hear your feedback!

    opened by StephanDollberg 10
  • Logger interface

    Logger interface

    Hi, how about using logger interface in resource handler? I'm using https://github.com/Sirupsen/logrus in my application and want specify that logger to go-json-rest. There is example of such interface here: https://godoc.org/github.com/Sirupsen/logrus#StdLogger.

    duplicate enhancement 
    opened by LK4D4 10
  • Parse Json error

    Parse Json error

    I get error on running Countries.

    CMD:

    curl -i -d '{"Code":"FR","Name":"France"}' http://127.0.0.1:8080/countries
    

    Error:

    HTTP/1.1 500 Internal Server Error
    Content-Type: application/json
    X-Powered-By: go-json-rest
    Date: Fri, 12 Dec 2014 04:29:59 GMT
    Content-Length: 71
    
    {
      "Error": "invalid character '\\'' looking for beginning of value"
    }
    

    JQuery:

    $.post('http://localhost:8080/countries', {"Code":"FR","Name":"France"}, function(data) { console.log(data) });
    

    Error:

    {
      "Error": "invalid character 'C' looking for beginning of value"
    }
    

    After I checked the source, I found the problem is parsing json on here.

    I am appreciated if you could help me.

    opened by ayming 9
  • PathParam with a dot returns 404

    PathParam with a dot returns 404

    If route is &Route{"GET", "/r/:id/", ...}

    Having a dot in the path param returns a 404

    $ curl "http://localhost:8080/r/123.456/"
    

    I can submit a pull request if you can point me in the right direction on how to fix this please.

    opened by Quantisan 9
  • Make rest.responseWriter implement http.Hijacker

    Make rest.responseWriter implement http.Hijacker

    Is there any chance to make rest.ResponseWriter include http.ResponseWriter interface? rest.responseWriter already does implement it and I need to extract it to pass rest.ResponseWriter to a third-party http.Handler.

    I understand that it can't simply implement it, but may be it can return one? Really desperate to do that connection to the third-party http.Handler.

    Even something as simple as this will work:

    --- a/rest/response.go
    +++ b/rest/response.go
    @@ -11,7 +11,7 @@ const xPoweredByDefault = "go-json-rest"
     // Note that the object instantiated by the ResourceHandler that implements this interface,
     // also happens to implement http.ResponseWriter, http.Flusher and http.CloseNotifier.
     type ResponseWriter interface {
    -
    +       HttpResponseWriter() http.ResponseWriter
            // Identical to the http.ResponseWriter interface
            Header() http.Header
    
    @@ -58,6 +58,10 @@ type responseWriter struct {
            xPoweredBy  string
     }
    
    +func (w *responseWriter) HttpResponseWriter() http.ResponseWriter {
    +       return w.ResponseWriter
    +}
    +
     func (w *responseWriter) WriteHeader(code int) {
            if w.Header().Get("Content-Type") == "" {
                    w.Header().Set("Content-Type", "application/json")
    

    Thoughts?

    opened by yrashk 8
  • Support flushing the rest.ResponseWriter

    Support flushing the rest.ResponseWriter

    Because recorderResponseWriter wraps http.ResponseWriter, but Flush() on the writer is only available via a different interface, it's not possible to cast the recorderRW to http.Flusher. This pull adds support to allow flushing the response from within a handler.

    opened by smarterclayton 8
  • need ACL support for basic authentication middleware

    need ACL support for basic authentication middleware

    Hi ant0ine

    go-json-rest is quite well that help me quickly get my simple restful service working, i added basic auth middleware to the restful interfaces. now i want to add ACL support so that, for example, only Admin can have POST/PUT/DELET privileges while User can only GET. but the Authenticator signature: Authenticator: func(userId string, password string) bool, only pass the user name and password while i want more context, e.g. Method, URI, etc.

    so i think we can simple pass one more parameter request *Request to it: Authenticator: func(userId string, password string, request *Request) bool (I copied the auth_basic.go and modify it to fit my ACL need locally), but i think this could be common if people need ACL support.

    opened by missedone 7
  • Simplified version of routes api

    Simplified version of routes api

    Hi! I've just made simple version of Routes api. In the example with countries it will be looks like:

    router, err := rest.MakeRouterSimply(
            rest.Get{"/countries", GetAllCountries},
            rest.Post{"/countries", PostCountry},
            rest.Get{"/countries/:code", GetCountry},
            rest.Delete{"/countries/:code", DeleteCountry},
        )
    

    Current version is:

    router, err := rest.MakeRouter(
            &rest.Route{"GET", "/countries", GetAllCountries},
            &rest.Route{"POST", "/countries", PostCountry},
            &rest.Route{"GET", "/countries/:code", GetCountry},
            &rest.Route{"DELETE", "/countries/:code", DeleteCountry},
        )
    

    In the result, its just a high level version on current api. In my point of view, it looks more pretty, but what do you think about that?

    opened by saromanov 6
  • Let resource handlers write arbitrary data, not solely JSON

    Let resource handlers write arbitrary data, not solely JSON

    Expose the Http.ResponseWrite.Write([]byte) so that resource handlers have the possibility to write raw data as a response (plain/text, html, ...).

    Avoid the response's Content-Type to be application/json only, in order to deal with the functionality above (text/plain, text/html, ...).

    opened by agrison 6
  • New middleware Mimiclatency

    New middleware Mimiclatency

    I've created a (small) middleware that implements time.Sleep(). May be useful during development to see how clients handle timeouts or slow requests.

    Also ordered the list alphabetically.

    opened by MrWaggel 0
  • 
Fix function comments based on best practices from Effective Go

    Fix function comments based on best practices from Effective Go

    Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name being declared. From effective go.

    PR generated by CodeLingo. Install here to drive Continuous Higher Standards.

    opened by CodeLingoBot 2
  • how to return status_code?

    how to return status_code?

    I build this project example.

    in Countries demo

    curl -i -H 'Content-Type: application/json' \
        -d '{"Code":"FR","Name":"France"}' http://127.0.0.1:8080/countries
     # it has to return 201(CREATED), but return 200(OK)
    curl -i -H 'Content-Type: application/json' \
        -d '{"Code":"US","Name":"United States"}' http://127.0.0.1:8080/countries
     # it has to return 201(CREATED), but return 200(OK)
    curl -i -H 'Content-Type: application/json' \
        -d '{"Code":"US","Name":"United States"}' http://127.0.0.1:8080/countries
    # return 209(CONFLICT) I customed
    curl -i http://127.0.0.1:8080/countries/FR
    curl -i http://127.0.0.1:8080/countries/US
    curl -i http://127.0.0.1:8080/countries
    curl -i -X DELETE http://127.0.0.1:8080/countries/FR
    curl -i http://127.0.0.1:8080/countries
    curl -i -X DELETE http://127.0.0.1:8080/countries/US
    curl -i http://127.0.0.1:8080/countries
    

    if data is conflict

    if store[country.Code] != nil {
    	rest.Error(w, "conflict", 409)
    	return
    }
    

    but I don't know modified response_code override. could you help me?

    opened by mcauto 0
  • Adds PathExp to Requests

    Adds PathExp to Requests

    This adds the PathExp to the Request object so that code downstream of the router is aware which route is satisfying the current request. Primary motivation for this is to give middlewares some additional information.

    opened by dtm 0
Releases(v3.3.2)
  • v3.3.2(Dec 4, 2016)

    Version 3.3.2 Release Notes

    What's New in v3.3.2 ?

    • Parse ipv6 addresses correctly (Thanks @mattsch)
    • Gzip middleware bug fix (Thanks @darvik80)
    • Fix microseconds Apache style logging
    • Fix recorder middleware to record status code only once (Thanks @mgkeen)
    • Fix CORS bug when Chrome send empty string CORS headers (Thanks @bboozzoo)
    Source code(tar.gz)
    Source code(zip)
  • v3.3.1(Jan 19, 2016)

    Version 3.3.1 Release Notes

    What's New in v3.3.1 ?

    • Refactor/add unit tests
    • Remove the ResourceHandler (v2 API) that was marked as deprecated a year ago.
    • Start using the simplifications (gofmt -s)
    • Minor documentation improvements

    New third party Middlewares

    Source code(tar.gz)
    Source code(zip)
  • v3.3.0(Nov 28, 2015)

    Version 3.3.0 Release Notes

    What's New in v3.3.0 ?

    • Include charset=utf-8 in the JSON response Content-Type (See issue about Chrome)
    • Add the ability to customize the field name in the error response.
    • Additional HTTP methods shortcuts (Thanks @sebest !)
    • Add error is the parsed JSON payload is empty (Thanks @sebest !)

    New third party Middlewares

    Source code(tar.gz)
    Source code(zip)
  • v3.2.0(May 17, 2015)

    Version 3.2.0 Release Notes

    What's New in v3.2.0 ?

    • New shortcut methods to create Routes more easily: Get(...), Post(...), Put(...), Delete(...). It also has the benefit to make golint happier. And &Route{...} remains available for any other use case.
    • New Websocket example. (Thanks @wingyplus !)
    • Security fix on the Jsonp Middleware. (Thanks @sebest !)
    • Documentation improvements
    Source code(tar.gz)
    Source code(zip)
  • v3.1.0(Mar 30, 2015)

    Version 3.1.0 Release Notes

    What's New in v3.1.0 ?

    • A new IfMiddleware that allows to conditionally execute a Middleware at runtime.
    • Better error messages when Env variables are missing (Timer, AccessLogApache, and Status Middlewares updated)
    • New Statsd example
    • New JWT authentication example
    • Improved code coverage

    New Third party Middlewares

    • JWT authentication Middleware by @StephanDollberg
    • Statsd app monitoring Middleware
    Source code(tar.gz)
    Source code(zip)
  • v3.0.0(Feb 3, 2015)

    Version 3 Release Notes

    What's New in v3

    • Public Middlewares. (12 included in the package)
    • A new App interface. (the router being the provided App)
    • A new Api object that manages the Middlewares and the App.
    • Optional and interchangeable App/router.

    Here is for instance the new minimal "Hello World!"

    api := rest.NewApi()
    api.Use(rest.DefaultDevStack...)
    api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
            w.WriteJson(map[string]string{"Body": "Hello World!"})
    }))
    http.ListenAndServe(":8080", api.MakeHandler())
    

    All 19 examples have been updated to use the new API. See here

    Deprecating the ResourceHandler

    V3 is about deprecating the ResourceHandler in favor of a new API that exposes the Middlewares. As a consequence, all the Middlewares are now public, and the new Api object helps putting them together as a stack. Some default stack configurations are offered. The router is now an App that sits on top of the stack of Middlewares. Which means that the router is no longer required to use Go-Json-Rest.

    Design ideas and discussion See here

    Migration guide See here

    Source code(tar.gz)
    Source code(zip)
  • v2.1.0(Nov 30, 2014)

    Main changes

    • Apache-style access log
    • Improved timer and recorder middlewares
    • new JSONP middleware
    • Make the gzip middleware support streaming responses

    Apache-style access log

    Go-Json-Rest now reuses the well known Apache log formatting syntax to define the access log.

    With this new feature, the user can define his own record format:

    rest.ResourceHandler{
            LoggerFormat: "%t %r %s %b",
    }
    

    or pick a predefined one:

    rest.ResourceHandler{
            LoggerFormat: rest.CommonLogFormat,
    }
    

    or just just ignore this field and get a default, development friendly access log.

    This is an implementation of a subset of the Apache mod_log_config syntax. Some options are not implemented yet, I expect the support can grow over time. See http://httpd.apache.org/docs/2.0/mod/mod_log_config.html for reference. And See Godoc for the list of supported options and predefined formats: https://godoc.org/github.com/ant0ine/go-json-rest/rest#AccessLogFormat

    Note: Compatibility with the existing JSON logging is maintained. This JSON logging feature may be deprecated in the future is favor of a more powerful one. Internally, logMiddleware is replaced by accessLogApacheMiddleware and accessLogJsonMiddleware.

    Improved timer and recorder middlewares

    • timerMiddleware now populates request.Env["START_TIME"]
    • recorderMiddleware now records request.Env["BYTES_WRITTEN"]
    • tests have been added to both

    JSONP middleware

    This is a new public middleware that can be instantiated like this:

        handler := rest.ResourceHandler{
            PreRoutingMiddlewares: []rest.Middleware{
                &rest.JsonpMiddleware{
                    CallbackNameKey: "cb",
                },
            },
        }
    

    See the complete example here: https://github.com/ant0ine/go-json-rest#jsonp

    Make the gzip middleware support streaming responses

    The gzip Writer is now instantiated once per response, allowing multiple calls to response.Write() or response.WriteJson(). See the streaming example here: https://github.com/ant0ine/go-json-rest#streaming

    Source code(tar.gz)
    Source code(zip)
  • v2.0.6(Oct 28, 2014)

    • Deprecate RouteObjectMethod in favor of Method Values
    • New option to fully disable the access log middleware
    • support http.Hijacker
    • performance improvements
    Source code(tar.gz)
    Source code(zip)
  • v2.0.5(Aug 4, 2014)

  • v2.0.4(Jul 13, 2014)

    • New "relaxed" placeholder type (notation #paramName) that matches all chars until the first /
    • Improved and new examples
    • Performance improvements
    • Documentation improvements
    Source code(tar.gz)
    Source code(zip)
  • v2.0.3(May 10, 2014)

  • v2.0.2(Apr 19, 2014)

  • v2.0.1(Apr 13, 2014)

  • v2.0.0(Apr 3, 2014)

  • v1.0.1(Apr 3, 2014)

  • v1.0.0(Feb 27, 2014)

TeslaMateApi is a RESTful API to get data collected by self-hosted data logger TeslaMate in JSON

TeslaMateApi is a RESTful API to get data collected by self-hosted data logger TeslaMate in JSON.

Tobias Lindberg 55 Dec 10, 2022
skr: The lightweight and powerful web framework using the new way for Go.Another go the way.

skr Overview Introduction Documents Features Install Quickstart Releases Todo Pull Request Issues Thanks Introduction The lightweight and powerful web

go-the-way 1 Jan 11, 2022
Quick REST API built with Go

Simple REST API built with Go # install mux router go get -u github.com/gorilla/

Miggy Pinaroc 0 Jul 7, 2022
Include files in your binary the easy way

New Projects should use the official embed package instead, which was added in go 1.16. binclude binclude is a tool for including static files into Go

null 283 Dec 18, 2022
golang crud restful api with gorm , gin and mysql DB

crud restful api with golang , gorm , gin and mysql this api does a simple CRUD operations on a single table mysql Database . this is build on top off

Brahim Bessrour 14 Feb 26, 2022
This project demonstrates a simple RESTful API built with Go and Chi.

Go and chi RESTful API This project demonstrates a simple RESTful API built with Go and chi. This API provides the following endpoints: GET / - Verify

newline: sandboxes 23 Dec 14, 2022
An example repo for RESTful API with swagger docs & unit testing

go REST API An example repo for RESTful API with swagger docs & unit testing Starting development server Copy .env.example to .env in the same directo

Rishabh Pandey 0 Nov 5, 2021
An restful api that uses CRUDL to support user endpoints. Stores the users in mysqlite. Creates 10 users when the program starts.

UserAPI An restful api that uses CRUDL to support user endpoints. Stores the users in mysqlite. Creates 10 users when the program starts. How to start

Nazar Trut 0 Nov 26, 2021
Implementing a restful-api with Golang.

Go-REST Create a restful api with Golang. The main idea behind this project was to gain some knowledge about implementing a rest-api in Golang. Setup

AmirH.Najafizadeh 3 Aug 22, 2022
GateCracker-REST - A RESTful API example for simple lock model information application with Go

Go Lock Models REST API Example A RESTful API example for simple lock model info

Yunus AYDIN 19 Jun 11, 2022
Go-userapi-rest - Build a RESTful API that can get/create/update/delete user data from a persistence database

GO ASSIGNMENT Overview of the Task Context Build a RESTful API that can get/crea

Harshit sharma 1 Sep 6, 2022
A Golang restful API boilerplate based on Echo framework v4

A Golang restful API boilerplate based on Echo framework v4. Includes tools for module generation, db migration, authorization, authentication and more.

Dzung Tran 10 Nov 15, 2022
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

Stretchr, Inc. 629 Jan 5, 2023
Project template for creating an RESTful webservice in Go, with relational database backend & example content.

gofound-restful Project template for creating an RESTfull webservice in Go, with relational database backend and example content. Used packages github

Bart 0 Dec 15, 2022
Fastrest - fast restful framework for golang.

fastrest fast restful framework for golang. Create your app directory, like mkdir myapp; cd myapp; go mod init myapp; Create initial config.toml in a

bingoohuang 1 Nov 8, 2022
⚓️ Golang RESTful APIs

Golang RESTful Golang RESTful API started on January 23rd, 2022. 环境配置相关解决办法

Hou Wenbo 0 Jan 23, 2022
Headless CMS with automatic JSON API. Featuring auto-HTTPS from Let's Encrypt, HTTP/2 Server Push, and flexible server framework written in Go.

Ponzu Watch the video introduction Ponzu is a powerful and efficient open-source HTTP server framework and CMS. It provides automatic, free, and secur

Ponzu 5.5k Dec 28, 2022
This package provides a generic way of deep copying Go objects

Package for copying Go values This package provides a generic way of deep copying Go objects. It is designed with performance in mind and is suitable

Google 42 Nov 9, 2022