miniflux-v2/client/request.go
Alexandros Kosiaris 638643cda7 client: Try to parse response Body on InternalServerError
Try to parse the response body from the server when an HTTP 500 is
returned (i.e. http.StatusInternalServerError) as it might contain
useful information. If successful, create a new error and append that
information to the returned error message. Otherwise just maintain the
same behavior
2021-06-16 20:48:12 -07:00

168 lines
3.9 KiB
Go

// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package client // import "miniflux.app/client"
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"time"
)
const (
userAgent = "Miniflux Client Library"
defaultTimeout = 80
)
// List of exposed errors.
var (
ErrNotAuthorized = errors.New("miniflux: unauthorized (bad credentials)")
ErrForbidden = errors.New("miniflux: access forbidden")
ErrServerError = errors.New("miniflux: internal server error")
ErrNotFound = errors.New("miniflux: resource not found")
)
type errorResponse struct {
ErrorMessage string `json:"error_message"`
}
type request struct {
endpoint string
username string
password string
apiKey string
}
func (r *request) Get(path string) (io.ReadCloser, error) {
return r.execute(http.MethodGet, path, nil)
}
func (r *request) Post(path string, data interface{}) (io.ReadCloser, error) {
return r.execute(http.MethodPost, path, data)
}
func (r *request) PostFile(path string, f io.ReadCloser) (io.ReadCloser, error) {
return r.execute(http.MethodPost, path, f)
}
func (r *request) Put(path string, data interface{}) (io.ReadCloser, error) {
return r.execute(http.MethodPut, path, data)
}
func (r *request) Delete(path string) error {
_, err := r.execute(http.MethodDelete, path, nil)
return err
}
func (r *request) execute(method, path string, data interface{}) (io.ReadCloser, error) {
if r.endpoint[len(r.endpoint)-1:] == "/" {
r.endpoint = r.endpoint[:len(r.endpoint)-1]
}
u, err := url.Parse(r.endpoint + path)
if err != nil {
return nil, err
}
request := &http.Request{
URL: u,
Method: method,
Header: r.buildHeaders(),
}
if r.username != "" && r.password != "" {
request.SetBasicAuth(r.username, r.password)
}
if data != nil {
switch data := data.(type) {
case io.ReadCloser:
request.Body = data
default:
request.Body = io.NopCloser(bytes.NewBuffer(r.toJSON(data)))
}
}
client := r.buildClient()
response, err := client.Do(request)
if err != nil {
return nil, err
}
switch response.StatusCode {
case http.StatusUnauthorized:
response.Body.Close()
return nil, ErrNotAuthorized
case http.StatusForbidden:
response.Body.Close()
return nil, ErrForbidden
case http.StatusInternalServerError:
defer response.Body.Close()
var resp errorResponse
decoder := json.NewDecoder(response.Body)
// If we failed to decode, just return a generic ErrServerError
if err := decoder.Decode(&resp); err != nil {
return nil, ErrServerError
}
return nil, errors.New("miniflux: internal server error: " + resp.ErrorMessage)
case http.StatusNotFound:
response.Body.Close()
return nil, ErrNotFound
case http.StatusNoContent:
response.Body.Close()
return nil, nil
case http.StatusBadRequest:
defer response.Body.Close()
var resp errorResponse
decoder := json.NewDecoder(response.Body)
if err := decoder.Decode(&resp); err != nil {
return nil, fmt.Errorf("miniflux: bad request error (%v)", err)
}
return nil, fmt.Errorf("miniflux: bad request (%s)", resp.ErrorMessage)
}
if response.StatusCode > 400 {
response.Body.Close()
return nil, fmt.Errorf("miniflux: status code=%d", response.StatusCode)
}
return response.Body, nil
}
func (r *request) buildClient() http.Client {
return http.Client{
Timeout: time.Duration(defaultTimeout * time.Second),
}
}
func (r *request) buildHeaders() http.Header {
headers := make(http.Header)
headers.Add("User-Agent", userAgent)
headers.Add("Content-Type", "application/json")
headers.Add("Accept", "application/json")
if r.apiKey != "" {
headers.Add("X-Auth-Token", r.apiKey)
}
return headers
}
func (r *request) toJSON(v interface{}) []byte {
b, err := json.Marshal(v)
if err != nil {
log.Println("Unable to convert interface to JSON:", err)
return []byte("")
}
return b
}