ensemble

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

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

Go to latest
Published: Nov 23, 2020 License: GPL-3.0 Imports: 13 Imported by: 0

README

Ensemble

Ensemble is a service for combining aggregate RestFUL requests in one call. It's intended primarily for mobile or javascript developers who need to get send and receive data using multiple RestFUL requests.

To use this service you need to create you're own go http server e.g.

package main
import (
	"fmt"
	"github.com/russellsimpkins/ensemble"
	"net/http"
	"time"
)

func StartListener() {
	http.HandleFunc("/magic, ensemble.Handle)

	srv := &http.Server{
		Handler:        nil,
		Addr:           ":8080",
		WriteTimeout:   15 * time.Second,
		ReadTimeout:    15 * time.Second,
		MaxHeaderBytes: 32768,
	}
	
	err := srv.ListenAndServe()	
	if err != nil {
		fmt.Println("ListenAndServe: ", err)
	}
}

func main() {
	StartListener()
}

A lot of RestFUL APIs are written as CRUD services and this tool will let you glue API calls together.

Here is the most basic example.

{
    "requests": [{
        "id": "1",
        "url": "http://localhost/test1?foo=bar",
        "method": "GET"
    }, {
        "id": "2",
        "url": "http://localhost/test2",
        "method": "POST",
        "payload": "boo=far"
    }],
    "strictorder": false
}

The JSON specifies two calls and says that order is not important. The service will call each service and return the id, data and code. The id is there to help the caller identify each request. You can also have the service follow the order by setting "strictorder":true e.g.

{
    "requests": [{
        "id": "1",
        "url": "http://localhost:8080/test1?foo=bar",
        "method": "GET"
    }, {
        "id": "2",
        "url": "http://localhost:8080/test2",
        "method": "POST",
        "payload": "boo=far"
    }],
    "strictorder": true
}

Assuming /test1 returns "This worked" and /test2 returns "That worked" your JSON response would look like this:

{
    "reponses": [{
        "id": "1",
        "payload": "This worked",
        "code": 200
    }, {
        "id": "2",
        "payload": "That worked",
        "code": 200
    }]
}

If /test2 returns JSON, it would look like this:

{
    "reponses": [{
        "id": "1",
        "payload": "This worked",
        "code": 200
    }, {
        "id": "2",
        "payload": "{\"is this\":\"magic?\"},{\"yup\":\"magic happens\"}",,
        "code": 200
    }]
}

You can also add dependency calls into the mix. Let's say you want a couple of calls to be made before your call to test2, and you want that data sent along. Let's assume that /provide1 returns {"is this":"magic?"} and /provide2 returns {"yup":"magic happens"} which you wanted posted to /test2 and you created the following request:

{
    "requests": [{
        "id": "1",
        "url": "http://localhost:8080/test1",
        "method": "GET"
    }, {
        "id": "2",
        "url": "http://localhost:8080/test2",
        "method": "POST",
        "payload": "{\"data\":%s}",
        "dependency": [{
            "request": {
                "id": "21",
                "url": "http://localhost:8080/provide1",
                "method": "GET"
            }
        }, {
            "request": {
                "id": "22",
                "url": "http://localhost:8080/provide2",
                "method": "GET"
            }
        }],
        "useData": true,
        "doJoin": true,
        "joinChar": ","
    }],
    "strictorder": true
}

TODO

  • Add in manipulators. Manipulators would parse json responses and grab significant parts. The significant parts get re-arranged in the response. Manipulators could work for aggregates.

What if you want to grab data["field"]["item"][0]

And put that into your response as resp["age"]

Documentation

Index

Constants

View Source
const DefaultTimeout = 10 * time.Second

Variables

This section is empty.

Functions

func Handle

func Handle(writer http.ResponseWriter, req *http.Request)

Handle function is entry point for all http request.

func Help

func Help() (msg string)

func IsValidHTTPMethod

func IsValidHTTPMethod(method *string) (valid bool)

utility method to validate the HTTP method someone desires to use.

func MakeMagicEndpoint

func MakeMagicEndpoint(magic *Magic) endpoint.Endpoint

MakeMagicEndpoint creates go-kit endpoint function

func MakeRequest

func MakeRequest(req *Request, response *Response) (err error)

* we expect a valid url. GET requests support name value pairs. * Data will always go in the body of the request. It's more for backward compatability * since I know of services that combine request data with query strings.

Types

type Call

type Call struct {
	Req Request
	Res Response
}

type Dependency

type Dependency struct {
	Request Request `json:"request"`
}

what if we could allow call dependencies? b depends on the result data of a, that sort of thing.

type Magic

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

func (*Magic) DoMagic

func (magic *Magic) DoMagic(workload Workload) (result Result, err error)

type MagicService

type MagicService interface {
	// all magic comes in as a json blob and get's returned as a json blob
	DoMagic(Workload) (Result, error)
}

type Middleware

type Middleware func(endpoint.Endpoint) endpoint.Endpoint

type PassThruData

type PassThruData struct {
	Data []interface{}
}

a utility structure to pass one or more results to a request

type Request

type Request struct {
	Id           string       `json:"id"`           // some way to identify this request in the response
	URL          string       `json:"url"`          // the restful api to call
	Method       string       `json:"method"`       // request method: get/put/post/delete
	Data         string       `json:"data"`         // data to pass to api. if it's a get, we add ? to URL
	Header       http.Header  `json:"headers"`      // request specific headers to add
	Dependents   []Dependency `json:"dependency"`   // id of the request this request depends on
	UseData      bool         `json:"useData"`      // if so, Payload is sprintf-able
	UseDepHeader bool         `json:"useDepHeader"` // if you want to use the headers from dependent calls
	DepHeader    []string     `json:"DepHeaders"`   // name the headers to use.
	DoJoin       bool         `json:"doJoin"`       // you plan on passing multiple dependencies and need to join the results
	JoinChar     string       `json:"joinChar"`     // e.g. , or |
	PassByName   string       `json:"passName"`     // if it's not json, it parameterized and needs a name
}

our definition of a request i'm not sure if i should have ContentType or assume it's in the headers

type Response

type Response struct {
	Id     string      `json:"id"`   // some way to identify this request in the response
	Data   string      `json:"data"` // put the data here
	Object interface{} `json:"object"`
	Code   int         `json:"code"` // http response code
	Header http.Header `json:"headers"`
}

type Result

type Result struct {
	Responses []Response `json:"responses"`
	Err       string     `json:"err,omitempty"`
	Code      int        `json:"code"`
}

type Workload

type Workload struct {
	Requests    []Request `json:"requests"`
	StrictOrder bool      `json:"strictorder"` // sync or async
	Timeout     int64     `json:"timeout"`     // TODO add a real timeout for the sync process
	UseHeaders  bool      `json:"use_headers"` // set this to true if the requests should use the headers of the work request
	// contains filtered or unexported fields
}

func (*Workload) SetHeader

func (workload *Workload) SetHeader(h http.Header)

allows us to get the header of the original request

Jump to

Keyboard shortcuts

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