From ef921ff72806df21771c9ce3943189c67a23a770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thilo=20Karra=C3=9F?= Date: Wed, 28 Oct 2020 11:29:12 +0100 Subject: [PATCH] Initial checkin --- .idea/.gitignore | 8 + .idea/cure.iml | 8 + .idea/encodings.xml | 4 + .idea/inspectionProfiles/Project_Default.xml | 10 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + cure.go | 208 +++++++++++++++++++ 7 files changed, 252 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/cure.iml create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 cure.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/cure.iml b/.idea/cure.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/cure.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..146ab09 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..053617f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/cure.go b/cure.go new file mode 100644 index 0000000..6fed1b3 --- /dev/null +++ b/cure.go @@ -0,0 +1,208 @@ +// Common Uniform Rest Endpoint +// +// This is a library to access REST endpoints in a simplified way. +package cure // import "udico.de/cure" +import ( + "bytes" + "context" + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" + "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 +} + +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.Debugf("%v %v", c.method, c.url) + for k, v := range c.Head { + logrus.Debugf(" [%v: %v]", k, v) + } + for k, v := range c.Parameters { + logrus.Debugf(" + %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 := new(jsonpb.Unmarshaler) + unmarshaller.AllowUnknownFields = true + return callret{ + response.StatusCode, + response.Status, + unmarshaller.Unmarshal(bbuf, result.(proto.Message)), + } +}