cure/cure.go
2021-10-18 14:58:35 +02:00

215 lines
4.8 KiB
Go

// Package cure is the Common Uniform Rest Endpoint
//
// This is a library to access REST endpoints in a simplified way.
package cure // import "udico.de/uditaren/cure"
import (
"bytes"
"context"
"encoding/json"
"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 {
/*marshaller := new(jsonpb.Marshaler)
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
*/
buf := &bytes.Buffer{}
jEnc := json.NewEncoder(buf)
jEnc.Encode(param)
logrus.Trace(string(buf.Bytes()))
c.Body = bytes.NewReader(buf.Bytes())
return c
}
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()
}
logrus.Tracef("%v %v", c.method, c.url)
for k, v := range c.Head {
logrus.Tracef(" [%v: %v]", k, v)
}
for k, v := range c.Parameters {
logrus.Tracef(" + %v = '%v'", k, v)
}
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[:]))
unmarshaller := json.NewDecoder(bbuf) //new(jsonpb.Unmarshaler)
//unmarshaller.AllowUnknownFields = true
return callret{
response.StatusCode,
response.Status,
unmarshaller.Decode(result),
}
}