// 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), } }