rest

package
v0.0.0-...-043deff Latest Latest
Warning

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

Go to latest
Published: May 2, 2023 License: AGPL-3.0, GPL-3.0-or-later Imports: 13 Imported by: 0

README

go-rest A small and evil REST framework for Go

Reflection, Go structs, and JSON marshalling FTW!

Download, build and run example:

go get github.com/ungerik/go-rest
go install github.com/ungerik/go-rest/example && example

Small?

Yes, the framework consists of only three functions: HandleGET, HandlePOST, RunServer.

Evil?

Well, this package can be considered bad design because HandleGET and HandlePOST use dynamic typing to hide 36 combinations of handler function types to make the interface easy to use. 36 static functions would have been more lines of code but dramatic simpler in their individual implementations. So simple in fact, that there wouldn't be a point in abstracting them away in an extra framework. See this great talk about easy vs. simple: http://www.infoq.com/presentations/Simple-Made-Easy Rob Pike may also dislike this approach: https://groups.google.com/d/msg/golang-nuts/z4T_n4MHbXM/jT9PoYc6I1IJ So yes, this package can be called evil because it is an anti-pattern to all that is good and right about Go.

Why use it then? By maximizing dynamic code it is easy to use and reduces code. Yes, that introduces some internal complexity, but this complexity is still very low in absolute terms and thus easy to control and debug. The complexity of the dynamic code also does not spill over into the package users' code, because the arguments and results of the handler functions must be static typed and can't be interface{}.

Now let's have some fun:

HandleGET uses a handler function that returns a struct or string to create the GET response. Structs will be marshalled as JSON, strings will be used as body with auto-detected content type.

Format of GET handler:

func([url.Values]) ([struct|*struct|string][, error]) {}

Example:

type MyStruct struct {
	A in
	B string
}

rest.HandleGET("/data.json", func() *MyStruct {
	return &MyStruct{A: 1, B: "Hello World"}
})

rest.HandleGET("/index.html", func() string {
	return "<!doctype html><p>Hello World"
})

The GET handler function can optionally accept an url.Values argument and return an error as second result value that will be displayed as 500 internal server error if not nil.

Example:

rest.HandleGET("/data.json", func(params url.Values) (string, error) {
	v := params.Get("value")
	if v == "" {
		return nil, errors.New("Expecting GET parameter 'value'")
	}
	return "value = " + v, nil
})

HandlePOST maps POST form data or a JSON document to a struct that is passed to the handler function. An error result from handler will be displayed as 500 internal server error message. An optional first string result will be displayed as a 200 response body with auto-detected content type.

Format of POST handler:

func([*struct|url.Values]) ([struct|*struct|string],[error]) {}

Example:

rest.HandlePOST("/change-data", func(data *MyStruct) (err error) {
	// save data
	return err
})

Both HandleGET and HandlePOST also accept one optional object argument. In that case handler is interpreted as a method of the type of object and called accordingly.

Example:

rest.HandleGET("/method-call", (*myType).MethodName, myTypeObject)

Documentation

Overview

## go-rest A small and evil REST framework for Go

### Reflection, Go structs, and JSON marshalling FTW!

* go get github.com/ungerik/go-rest * import "github.com/ungerik/go-rest" * Documentation: http://go.pkgdoc.org/github.com/ungerik/go-rest * License: Public Domain

Download, build and run example:

go get github.com/ungerik/go-rest
go install github.com/ungerik/go-rest/example && example

Small?

Yes, the framework consists of only three functions: HandleGET, HandlePOST, RunServer.

Evil?

Well, this package can be considered bad design because HandleGET and HandlePOST use dynamic typing to hide 36 combinations of handler function types to make the interface _easy_ to use. 36 static functions would have been more lines of code but dramatic _simpler_ in their individual implementations. So simple in fact, that there wouldn't be a point in abstracting them away in an extra framework. See this great talk about easy vs. simple: http://www.infoq.com/presentations/Simple-Made-Easy Rob Pike may also dislike this approach: https://groups.google.com/d/msg/golang-nuts/z4T_n4MHbXM/jT9PoYc6I1IJ So yes, this package can be called evil because it is an anti-pattern to all that is good and right about Go.

Why use it then? By maximizing dynamic code it is easy to use and reduces code. Yes, that introduces some internal complexity, but this complexity is still very low in absolute terms and thus easy to control and debug. The complexity of the dynamic code also does not spill over into the package users' code, because the arguments and results of the handler functions must be static typed and can't be interface{}.

Now let's have some fun:

HandleGET uses a handler function that returns a struct or string to create the GET response. Structs will be marshalled as JSON, strings will be used as body with auto-detected content type.

Format of GET handler:

func([url.Values]) ([struct|*struct|string][, error]) {}

Example:

type MyStruct struct {
	A in
	B string
}

rest.HandleGET("/data.json", func() *MyStruct {
	return &MyStruct{A: 1, B: "Hello World"}
})

rest.HandleGET("/index.html", func() string {
	return "<!doctype html><p>Hello World"
})

The GET handler function can optionally accept an url.Values argument and return an error as second result value that will be displayed as 500 internal server error if not nil.

Example:

rest.HandleGET("/data.json", func(params url.Values) (string, error) {
	v := params.Get("value")
	if v == "" {
		return nil, errors.New("Expecting GET parameter 'value'")
	}
	return "value = " + v, nil
})

HandlePOST maps POST form data or a JSON document to a struct that is passed to the handler function. An error result from handler will be displayed as 500 internal server error message. An optional first string result will be displayed as a 200 response body with auto-detected content type.

Suported content types for POST requests are: * application/x-www-form-urlencoded * multipart/form-data * text/plain * application/json * application/xml

Format of POST handler:

func([*struct|url.Values]) ([struct|*struct|string],[error]) {}

Example:

rest.HandlePOST("/change-data", func(data *MyStruct) (err error) {
	// save data
	return err
})

Both HandleGET and HandlePOST also accept one optional object argument. In that case handler is interpreted as a method of the type of object and called accordingly.

Example:

rest.HandleGET("/method-call", (*myType).MethodName, myTypeObject)

Index

Constants

This section is empty.

Variables

View Source
var (
	// IndentJSON is the string with which JSON output will be indented.
	IndentJSON string

	// Log is a function pointer compatible to fmt.Println or log.Println.
	// The default value is log.Println.
	Log = log.Println

	// DontCheckRequestMethod disables checking for the correct
	// request method for a handler, which would result in a
	// 405 error if not correct.
	DontCheckRequestMethod bool
)

Functions

func GetJSON

func GetJSON(addr string, out interface{}) error

GetJSON sends a HTTP GET request to addr and unmarshalles the JSON response to out.

func GetJSONStrict

func GetJSONStrict(addr string, out interface{}) error

GetJSONStrict sends a HTTP GET request to addr and unmarshalles the JSON response to out. Returns an error if Content-Type is not application/json.

func HandleGET

func HandleGET(path string, handler interface{}, object ...interface{})

HandleGET registers a HTTP GET handler for path. handler is a function with an optional url.Values argument.

If the first result value of handler is a struct or struct pointer, then the struct will be marshalled as JSON response. If the first result value fo handler is a string, then it will be used as response body with an auto-detected content type. An optional second result value of type error will create a 500 internal server error response if not nil. All non error responses will use status code 200.

A single optional argument can be passed as object. In that case handler is interpreted as a method and object is the address of an object with such a method.

Format of GET handler:

func([url.Values]) ([struct|*struct|string][, error]) {}

func HandlePOST

func HandlePOST(path string, handler interface{}, object ...interface{})

HandlePOST registers a HTTP POST handler for path. handler is a function that takes a struct pointer or url.Values as argument.

If the request content type is text/plain, then only a struct pointer is allowed as handler argument and the request body will be interpreted as JSON and unmarshalled to a new struct instance.

If the request content type multipart/form-data, then only a struct pointer is allowed as handler argument and a file named JSON will be unmarshalled to a new struct instance.

If the request content type is empty or application/x-www-form-urlencoded and the handler argument is of type url.Values, then the form values will be passed directly as url.Values. If the handler argument is a struct pointer and the form contains a single value named "JSON", then the value will be interpreted as JSON and unmarshalled to a new struct instance. If there are multiple form values, then they will be set at struct fields with exact matching names.

If the first result value of handler is a struct or struct pointer, then the struct will be marshalled as JSON response. If the first result value fo handler is a string, then it will be used as response body with an auto-detected content type. An optional second result value of type error will create a 500 internal server error response if not nil. All non error responses will use status code 200.

A single optional argument can be passed as object. In that case handler is interpreted as a method and object is the address of an object with such a method.

Format of POST handler:

func([*struct|url.Values]) ([struct|*struct|string][, error]) {}

func RunServer

func RunServer(addr string, stop chan struct{})

RunServer starts an HTTP server with a given address with the registered GET and POST handlers. If stop is non nil then a send on the channel will gracefully stop the server.

Types

This section is empty.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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