Documentation
¶
Overview ¶
Package httpapi contains code for calling HTTP APIs.
We model HTTP APIs as follows:
1. |Endpoint| is an API endpoint (e.g., https://api.ooni.io);
2. |Descriptor| describes the specific API you want to use (e.g., GET /api/v1/test-list/urls with JSON response body).
Generally, you use |Call| to call the API identified by a |Descriptor| on the specified |Endpoint|. However, there are cases where you need more complex calling patterns. For example, with |SequenceCaller| you can invoke the same API |Descriptor| with multiple equivalent API |Endpoint|s until one of them succeeds or all fail.
Index ¶
- Constants
- Variables
- func Call(ctx context.Context, desc *Descriptor, endpoint *Endpoint) ([]byte, error)
- func CallWithJSONResponse(ctx context.Context, desc *Descriptor, endpoint *Endpoint, response any) error
- type Descriptor
- func MustNewPOSTJSONWithJSONResponseDescriptor(logger model.Logger, urlPath string, request any) *Descriptor
- func NewGETJSONDescriptor(logger model.Logger, urlPath string) *Descriptor
- func NewGETJSONWithQueryDescriptor(logger model.Logger, urlPath string, query url.Values) *Descriptor
- func NewGETResourceDescriptor(logger model.Logger, urlPath string) *Descriptor
- func NewPOSTJSONWithJSONResponseDescriptor(logger model.Logger, urlPath string, request any) (*Descriptor, error)
- type Endpoint
- type ErrHTTPRequestFailed
- type SequenceCaller
Constants ¶
const DefaultCallTimeout = 60 * time.Second
DefaultCallTimeout is the default timeout for an httpapi call.
const DefaultMaxBodySize = 1 << 22
DefaultMaxBodySize is the default value for the maximum body size you can fetch using the httpapi package.
Variables ¶
var ErrAllEndpointsFailed = errors.New("httpapi: all endpoints failed")
ErrAllEndpointsFailed indicates that all endpoints failed.
Functions ¶
func Call ¶
Call invokes the API described by |desc| on the given HTTP |endpoint| and returns the response body (as a slice of bytes) or an error.
Note: this function returns ErrHTTPRequestFailed if the HTTP status code is greater or equal than 400. You could use errors.As to obtain a copy of the error that was returned and see for yourself the actual status code.
func CallWithJSONResponse ¶
func CallWithJSONResponse(ctx context.Context, desc *Descriptor, endpoint *Endpoint, response any) error
CallWithJSONResponse is like Call but also assumes that the response is a JSON body and attempts to parse it into the |response| field.
Note: this function returns ErrHTTPRequestFailed if the HTTP status code is greater or equal than 400. You could use errors.As to obtain a copy of the error that was returned and see for yourself the actual status code.
Types ¶
type Descriptor ¶
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
}
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.
func MustNewPOSTJSONWithJSONResponseDescriptor ¶
func MustNewPOSTJSONWithJSONResponseDescriptor(logger model.Logger, urlPath string, request any) *Descriptor
MustNewPOSTJSONWithJSONResponseDescriptor is like NewPOSTJSONWithJSONResponseDescriptor except that it panics in case it's not possible to JSON serialize the |request|.
func NewGETJSONDescriptor ¶
func NewGETJSONDescriptor(logger model.Logger, urlPath string) *Descriptor
NewGETJSONDescriptor is a convenience factory for creating a new descriptor that uses the GET method and expects a JSON response.
func NewGETJSONWithQueryDescriptor ¶
func NewGETJSONWithQueryDescriptor(logger model.Logger, urlPath string, query url.Values) *Descriptor
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 NewGETResourceDescriptor ¶
func NewGETResourceDescriptor(logger model.Logger, urlPath string) *Descriptor
NewGETResourceDescriptor creates a generic descriptor for GETting a resource of unspecified type using the given |urlPath|.
func NewPOSTJSONWithJSONResponseDescriptor ¶
func NewPOSTJSONWithJSONResponseDescriptor(logger model.Logger, urlPath string, request any) (*Descriptor, error)
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 (*Descriptor) WithBodyLogging ¶
func (desc *Descriptor) WithBodyLogging(value bool) *Descriptor
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.
type Endpoint ¶
type Endpoint struct {
// BaseURL is the MANDATORY endpoint base URL. We will honour the
// path of this URL and prepend it to the actual path specified inside
// a |Descriptor.URLPath|. However, we will always discard any query
// that may have been set inside the BaseURL. The only query string
// will be composed from the |Descriptor.URLQuery| values.
//
// For example, https://api.ooni.io.
BaseURL string
// HTTPClient is the MANDATORY HTTP client to use.
//
// For example, http.DefaultClient. You can introduce circumvention
// here by using an HTTPClient bound to a specific tunnel.
HTTPClient model.HTTPClient
// Host is the OPTIONAL host header to use.
//
// If this field is empty we use the BaseURL's hostname. A specific
// host header may be needed when using cloudfronting.
Host string
// User-Agent is the OPTIONAL user-agent to use. If empty,
// we'll use the stdlib's default user-agent string.
UserAgent string
}
Endpoint models an HTTP endpoint on which you can call several HTTP APIs (e.g., https://api.ooni.io) using a given HTTP client potentially using a circumvention tunnel mechanism such as psiphon or torsf.
The zero value of this struct is invalid. Please, fill all the fields marked as MANDATORY for correct initialization.
func NewEndpointList ¶
func NewEndpointList(httpClient model.HTTPClient, userAgent string, services ...model.OOAPIService) (out []*Endpoint)
NewEndpointList constructs a list of API endpoints from |services| returned by the OONI backend (or known in advance).
Arguments:
- httpClient is the HTTP client to use for accessing the endpoints;
- userAgent is the user agent you would like to use;
- service is the list of services gathered from the backend.
type ErrHTTPRequestFailed ¶
type ErrHTTPRequestFailed struct {
// StatusCode is the status code that failed.
StatusCode int
}
ErrHTTPRequestFailed indicates that the server returned >= 400.
func (*ErrHTTPRequestFailed) Error ¶
func (err *ErrHTTPRequestFailed) Error() string
Error implements error.
type SequenceCaller ¶
type SequenceCaller struct {
// Descriptor is the API |Descriptor|.
Descriptor *Descriptor
// Endpoints is the list of |Endpoint| to use.
Endpoints []*Endpoint
}
SequenceCaller calls the API specified by |Descriptor| once for each of the available |Endpoints| until one of them succeeds.
CAVEAT: this code will ONLY retry API calls with subsequent endpoints when the error originates in the HTTP round trip or while reading the body.
func NewSequenceCaller ¶
func NewSequenceCaller(desc *Descriptor, endpoints ...*Endpoint) *SequenceCaller
NewSequenceCaller is a factory for creating a |SequenceCaller|.
func (*SequenceCaller) Call ¶
Call calls |Call| for each |Endpoint| and |Descriptor| until one endpoint succeeds. The return value is the response body and the selected endpoint index or the error.
CAVEAT: this code will ONLY retry API calls with subsequent endpoints when the error originates in the HTTP round trip or while reading the body.
func (*SequenceCaller) CallWithJSONResponse ¶
CallWithJSONResponse is like |SequenceCaller.Call| except that it invokes the underlying |CallWithJSONResponse| rather than invoking |Call|.
CAVEAT: this code will ONLY retry API calls with subsequent endpoints when the error originates in the HTTP round trip or while reading the body.