156 lines
4.8 KiB
Go
156 lines
4.8 KiB
Go
|
package httpapi
|
||
|
|
||
|
//
|
||
|
// HTTP API descriptor (e.g., GET /api/v1/test-list/urls)
|
||
|
//
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"net/http"
|
||
|
"net/url"
|
||
|
"time"
|
||
|
|
||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||
|
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
||
|
)
|
||
|
|
||
|
// Descriptor contains the parameters for calling a given HTTP
|
||
|
// API (e.g., GET /api/v1/test-list/urls).
|
||
|
//
|
||
|
// The zero value of this struct is invalid. Please, fill all the
|
||
|
// fields marked as MANDATORY for correct initialization.
|
||
|
type Descriptor struct {
|
||
|
// Accept contains the OPTIONAL accept header.
|
||
|
Accept string
|
||
|
|
||
|
// Authorization is the OPTIONAL authorization.
|
||
|
Authorization string
|
||
|
|
||
|
// ContentType is the OPTIONAL content-type header.
|
||
|
ContentType string
|
||
|
|
||
|
// LogBody OPTIONALLY enables logging bodies.
|
||
|
LogBody bool
|
||
|
|
||
|
// Logger is the MANDATORY logger to use.
|
||
|
//
|
||
|
// For example, model.DiscardLogger.
|
||
|
Logger model.Logger
|
||
|
|
||
|
// MaxBodySize is the OPTIONAL maximum response body size. If
|
||
|
// not set, we use the |DefaultMaxBodySize| constant.
|
||
|
MaxBodySize int64
|
||
|
|
||
|
// Method is the MANDATORY request method.
|
||
|
Method string
|
||
|
|
||
|
// RequestBody is the OPTIONAL request body.
|
||
|
RequestBody []byte
|
||
|
|
||
|
// Timeout is the OPTIONAL timeout for this call. If no timeout
|
||
|
// is specified we will use the |DefaultCallTimeout| const.
|
||
|
Timeout time.Duration
|
||
|
|
||
|
// URLPath is the MANDATORY URL path.
|
||
|
URLPath string
|
||
|
|
||
|
// URLQuery is the OPTIONAL query.
|
||
|
URLQuery url.Values
|
||
|
}
|
||
|
|
||
|
// WithBodyLogging returns a SHALLOW COPY of |Descriptor| with LogBody set to |value|. You SHOULD
|
||
|
// only use this method when initializing the descriptor you want to use.
|
||
|
func (desc *Descriptor) WithBodyLogging(value bool) *Descriptor {
|
||
|
out := &Descriptor{}
|
||
|
*out = *desc
|
||
|
out.LogBody = value
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// DefaultMaxBodySize is the default value for the maximum
|
||
|
// body size you can fetch using the httpapi package.
|
||
|
const DefaultMaxBodySize = 1 << 22
|
||
|
|
||
|
// DefaultCallTimeout is the default timeout for an httpapi call.
|
||
|
const DefaultCallTimeout = 60 * time.Second
|
||
|
|
||
|
// NewGETJSONDescriptor is a convenience factory for creating a new descriptor
|
||
|
// that uses the GET method and expects a JSON response.
|
||
|
func NewGETJSONDescriptor(logger model.Logger, urlPath string) *Descriptor {
|
||
|
return NewGETJSONWithQueryDescriptor(logger, urlPath, url.Values{})
|
||
|
}
|
||
|
|
||
|
// applicationJSON is the content-type for JSON
|
||
|
const applicationJSON = "application/json"
|
||
|
|
||
|
// NewGETJSONWithQueryDescriptor is like NewGETJSONDescriptor but it also
|
||
|
// allows you to provide |query| arguments. Leaving |query| nil or empty
|
||
|
// is equivalent to calling NewGETJSONDescriptor directly.
|
||
|
func NewGETJSONWithQueryDescriptor(logger model.Logger, urlPath string, query url.Values) *Descriptor {
|
||
|
return &Descriptor{
|
||
|
Accept: applicationJSON,
|
||
|
Authorization: "",
|
||
|
ContentType: "",
|
||
|
LogBody: false,
|
||
|
Logger: logger,
|
||
|
MaxBodySize: DefaultMaxBodySize,
|
||
|
Method: http.MethodGet,
|
||
|
RequestBody: nil,
|
||
|
Timeout: DefaultCallTimeout,
|
||
|
URLPath: urlPath,
|
||
|
URLQuery: query,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NewPOSTJSONWithJSONResponseDescriptor creates a descriptor that POSTs a JSON document
|
||
|
// and expects to receive back a JSON document from the API.
|
||
|
//
|
||
|
// This function ONLY fails if we cannot serialize the |request| to JSON. So, if you know
|
||
|
// that |request| is JSON-serializable, you can safely call MustNewPostJSONWithJSONResponseDescriptor instead.
|
||
|
func NewPOSTJSONWithJSONResponseDescriptor(logger model.Logger, urlPath string, request any) (*Descriptor, error) {
|
||
|
rawRequest, err := json.Marshal(request)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
desc := &Descriptor{
|
||
|
Accept: applicationJSON,
|
||
|
Authorization: "",
|
||
|
ContentType: applicationJSON,
|
||
|
LogBody: false,
|
||
|
Logger: logger,
|
||
|
MaxBodySize: DefaultMaxBodySize,
|
||
|
Method: http.MethodPost,
|
||
|
RequestBody: rawRequest,
|
||
|
Timeout: DefaultCallTimeout,
|
||
|
URLPath: urlPath,
|
||
|
URLQuery: nil,
|
||
|
}
|
||
|
return desc, nil
|
||
|
}
|
||
|
|
||
|
// MustNewPOSTJSONWithJSONResponseDescriptor is like NewPOSTJSONWithJSONResponseDescriptor except that
|
||
|
// it panics in case it's not possible to JSON serialize the |request|.
|
||
|
func MustNewPOSTJSONWithJSONResponseDescriptor(logger model.Logger, urlPath string, request any) *Descriptor {
|
||
|
desc, err := NewPOSTJSONWithJSONResponseDescriptor(logger, urlPath, request)
|
||
|
runtimex.PanicOnError(err, "NewPOSTJSONWithJSONResponseDescriptor failed")
|
||
|
return desc
|
||
|
}
|
||
|
|
||
|
// NewGETResourceDescriptor creates a generic descriptor for GETting a
|
||
|
// resource of unspecified type using the given |urlPath|.
|
||
|
func NewGETResourceDescriptor(logger model.Logger, urlPath string) *Descriptor {
|
||
|
return &Descriptor{
|
||
|
Accept: "",
|
||
|
Authorization: "",
|
||
|
ContentType: "",
|
||
|
LogBody: false,
|
||
|
Logger: logger,
|
||
|
MaxBodySize: DefaultMaxBodySize,
|
||
|
Method: http.MethodGet,
|
||
|
RequestBody: nil,
|
||
|
Timeout: DefaultCallTimeout,
|
||
|
URLPath: urlPath,
|
||
|
URLQuery: url.Values{},
|
||
|
}
|
||
|
}
|