2021-10-18 14:58:35 +02:00
|
|
|
// Package cure is the Common Uniform Rest Endpoint
|
2020-10-28 11:29:12 +01:00
|
|
|
//
|
|
|
|
// This is a library to access REST endpoints in a simplified way.
|
2020-10-28 11:32:08 +01:00
|
|
|
package cure // import "udico.de/uditaren/cure"
|
2020-10-28 11:29:12 +01:00
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
2021-10-18 14:58:35 +02:00
|
|
|
"encoding/json"
|
2020-10-28 11:29:12 +01:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Endpoint struct {
|
|
|
|
// The base url of the REST API
|
|
|
|
Base string
|
|
|
|
|
|
|
|
// use this string as user agent
|
|
|
|
UserAgent string
|
|
|
|
|
|
|
|
// Additional HTTP header attributes to set in every call.
|
|
|
|
// You can (and should) also set per Call attributes within the Call object
|
|
|
|
Head map[string]string
|
|
|
|
|
|
|
|
client *http.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(baseurl string) *Endpoint {
|
|
|
|
return &Endpoint{
|
|
|
|
Base: baseurl,
|
|
|
|
UserAgent: "Common Uniform Rest Endpoint, 1.0.0",
|
|
|
|
Head: make(map[string]string),
|
|
|
|
client: &http.Client{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type call struct {
|
|
|
|
url string
|
|
|
|
method string
|
|
|
|
api *Endpoint
|
|
|
|
|
|
|
|
// HTTP header attributes to set. These will override the ones set within the endpoint.
|
|
|
|
Head map[string]string
|
|
|
|
|
|
|
|
// The parameters of your call. Depending on the method, they'll be appended to the query or posted url-form-encoded
|
|
|
|
Parameters map[string]string
|
|
|
|
|
|
|
|
// If set, this data will be used as POST data (
|
|
|
|
Body *bytes.Reader
|
|
|
|
}
|
|
|
|
|
|
|
|
type callret struct {
|
|
|
|
Code int
|
|
|
|
Message string
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r callret) Ok() bool {
|
|
|
|
if r.err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// We don't expect 1xx codes
|
|
|
|
if (r.Code / 100) > 2 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Simplify error handling at client side
|
|
|
|
func (r callret) Error() error {
|
|
|
|
if r.err != nil {
|
|
|
|
return r.err
|
|
|
|
}
|
|
|
|
if (r.Code / 100) > 2 {
|
|
|
|
return errors.New(r.Message)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Endpoint) createCall(method, endpoint string) *call {
|
|
|
|
ret := &call{
|
|
|
|
url: a.Base + endpoint, // todo: ensure we have a valid url
|
|
|
|
method: method,
|
|
|
|
api: a,
|
|
|
|
Head: make(map[string]string),
|
|
|
|
Parameters: make(map[string]string),
|
|
|
|
}
|
|
|
|
ret.Head["user-agent"] = a.UserAgent
|
|
|
|
for k, v := range a.Head {
|
|
|
|
ret.Head[k] = v
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Endpoint) Get(endpoint string) *call {
|
|
|
|
return a.createCall("GET", endpoint)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Endpoint) PostForm(endpoint string) *call {
|
|
|
|
ret := a.createCall("POST", endpoint)
|
|
|
|
ret.Head["content-type"] = "application/x-www-form-urlencoded"
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *Endpoint) PostBody(endpoint string) *call {
|
|
|
|
ret := a.createCall("POST", endpoint)
|
|
|
|
ret.Head["content-type"] = "application/json"
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *call) Param(param interface{}) *call {
|
2021-10-18 14:58:35 +02:00
|
|
|
/*marshaller := new(jsonpb.Marshaler)
|
2020-10-28 11:29:12 +01:00
|
|
|
marshaller.EmitDefaults = false
|
|
|
|
marshaller.EnumsAsInts = false
|
|
|
|
marshaller.OrigName = true
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
_ = marshaller.Marshal(buf, param.(proto.Message))
|
|
|
|
logrus.Debug(string(buf.Bytes()[:]))
|
|
|
|
logrus.Debug(string(buf.Bytes()[:]))
|
|
|
|
c.Body = bytes.NewReader(buf.Bytes())
|
|
|
|
return c
|
2021-10-18 14:58:35 +02:00
|
|
|
*/
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
jEnc := json.NewEncoder(buf)
|
|
|
|
jEnc.Encode(param)
|
|
|
|
logrus.Trace(string(buf.Bytes()))
|
|
|
|
c.Body = bytes.NewReader(buf.Bytes())
|
|
|
|
return c
|
2020-10-28 11:29:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *call) Fire(ctx context.Context, result interface{}) callret {
|
|
|
|
// disable HTTP2
|
|
|
|
//client := &http.Client{Transport: &http.Transport{TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper)}}
|
|
|
|
//client := &http.Client{}
|
|
|
|
|
|
|
|
// handle POST parameters
|
|
|
|
var request *http.Request
|
|
|
|
var err error
|
|
|
|
if c.method == "POST" {
|
|
|
|
if c.Body != nil {
|
|
|
|
request, err = http.NewRequest(c.method, c.url, c.Body)
|
|
|
|
//request.Header.Set("content-length", fmt.Sprintf("%v", c.Body.Len()))
|
|
|
|
} else {
|
|
|
|
fdata := url.Values{}
|
|
|
|
for k, v := range c.Parameters {
|
|
|
|
fdata.Add(k, v)
|
|
|
|
}
|
|
|
|
logrus.Debug(fdata.Encode())
|
|
|
|
sRead := strings.NewReader(fdata.Encode())
|
|
|
|
request, err = http.NewRequest(c.method, c.url, sRead)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
request, err = http.NewRequest(c.method, c.url, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range c.Head {
|
|
|
|
request.Header.Set(k, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
request = request.WithContext(ctx)
|
|
|
|
|
|
|
|
// handle GET parameters
|
|
|
|
if c.method == "GET" {
|
|
|
|
gdata := request.URL.Query()
|
|
|
|
for k, v := range c.Parameters {
|
|
|
|
gdata.Add(k, v)
|
|
|
|
}
|
|
|
|
request.URL.RawQuery = gdata.Encode()
|
|
|
|
}
|
|
|
|
|
2021-10-18 14:58:35 +02:00
|
|
|
logrus.Tracef("%v %v", c.method, c.url)
|
2020-10-28 11:29:12 +01:00
|
|
|
for k, v := range c.Head {
|
2021-10-18 14:58:35 +02:00
|
|
|
logrus.Tracef(" [%v: %v]", k, v)
|
2020-10-28 11:29:12 +01:00
|
|
|
}
|
|
|
|
for k, v := range c.Parameters {
|
2021-10-18 14:58:35 +02:00
|
|
|
logrus.Tracef(" + %v = '%v'", k, v)
|
2020-10-28 11:29:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
hbuf := &bytes.Buffer{}
|
|
|
|
request.Header.Write(hbuf)
|
|
|
|
|
|
|
|
// go!
|
|
|
|
response, err := c.api.client.Do(request)
|
|
|
|
if err != nil {
|
|
|
|
return callret{0, "", err}
|
|
|
|
}
|
|
|
|
logrus.Debugf("-> %v", response.Status)
|
|
|
|
|
|
|
|
if response == nil {
|
|
|
|
return callret{0, "", errors.New("empty response")}
|
|
|
|
}
|
|
|
|
|
|
|
|
// no result requested
|
|
|
|
if result == nil {
|
|
|
|
return callret{0, "", nil}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: dump the content
|
|
|
|
//buf := &bytes.Buffer{}
|
|
|
|
buf, err := ioutil.ReadAll(response.Body)
|
|
|
|
bbuf := bytes.NewReader(buf)
|
|
|
|
logrus.Debug(string(buf[:]))
|
|
|
|
|
2021-10-18 14:58:35 +02:00
|
|
|
unmarshaller := json.NewDecoder(bbuf) //new(jsonpb.Unmarshaler)
|
|
|
|
//unmarshaller.AllowUnknownFields = true
|
2020-10-28 11:29:12 +01:00
|
|
|
return callret{
|
|
|
|
response.StatusCode,
|
|
|
|
response.Status,
|
2021-10-18 14:58:35 +02:00
|
|
|
unmarshaller.Decode(result),
|
2020-10-28 11:29:12 +01:00
|
|
|
}
|
|
|
|
}
|