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.