Skip to content

Commit

Permalink
Merge pull request #602 from ooni/probe/887
Browse files Browse the repository at this point in the history
jsonapi, probeservices: refactoring before detecting blocking
  • Loading branch information
bassosimone committed May 19, 2020
2 parents 6892aaf + b603f14 commit f888c5c
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 309 deletions.
11 changes: 7 additions & 4 deletions experiment.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/ooni/probe-engine/experiment/urlgetter"
"github.com/ooni/probe-engine/experiment/web_connectivity"
"github.com/ooni/probe-engine/experiment/whatsapp"
"github.com/ooni/probe-engine/internal/jsonapi"
"github.com/ooni/probe-engine/internal/platform"
"github.com/ooni/probe-engine/model"
"github.com/ooni/probe-engine/netx/bytecounter"
Expand Down Expand Up @@ -396,10 +397,12 @@ func (e *Experiment) openReport(ctx context.Context) (err error) {
continue
}
client := &probeservices.Client{
BaseURL: c.Address,
HTTPClient: httpClient,
Logger: e.session.logger,
UserAgent: e.session.UserAgent(),
Client: jsonapi.Client{
BaseURL: c.Address,
HTTPClient: httpClient,
Logger: e.session.logger,
UserAgent: e.session.UserAgent(),
},
}
template := probeservices.ReportTemplate{
DataFormatVersion: probeservices.DefaultDataFormatVersion,
Expand Down
44 changes: 44 additions & 0 deletions internal/jsonapi/fake_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package jsonapi

import (
"io/ioutil"
"net/http"
"time"
)

type FakeTransport struct {
Err error
Func func(*http.Request) (*http.Response, error)
Resp *http.Response
}

func (txp FakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
time.Sleep(10 * time.Microsecond)
if txp.Func != nil {
return txp.Func(req)
}
if req.Body != nil {
ioutil.ReadAll(req.Body)
req.Body.Close()
}
if txp.Err != nil {
return nil, txp.Err
}
txp.Resp.Request = req // non thread safe but it doesn't matter
return txp.Resp, nil
}

func (txp FakeTransport) CloseIdleConnections() {}

type FakeBody struct {
Err error
}

func (fb FakeBody) Read(p []byte) (int, error) {
time.Sleep(10 * time.Microsecond)
return 0, fb.Err
}

func (fb FakeBody) Close() error {
return nil
}
78 changes: 32 additions & 46 deletions internal/jsonapi/jsonapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,37 @@ type Client struct {
// HTTPClient is the http client to use.
HTTPClient *http.Client

// Host allows to set a specific host header.
// Host allows to set a specific host header. This is useful
// to implement, e.g., cloudfronting.
Host string

// Logger is the logger to use.
Logger model.Logger

// ProxyURL allows to force a proxy URL to fallback to a tunnel.
// ProxyURL allows to force a proxy URL to fallback to a
// tunnel, e.g., Psiphon.
ProxyURL *url.URL

// UserAgent is the user agent to use.
UserAgent string
}

func (c *Client) makeRequestWithJSONBody(
// NewRequest creates a new request with a JSON body
func (c Client) NewRequest(
ctx context.Context, method, resourcePath string,
query url.Values, body interface{},
) (*http.Request, error) {
query url.Values, body interface{}) (*http.Request, error) {
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
c.Logger.Debugf("jsonapi: request body: %s", string(data))
return c.makeRequest(ctx, method, resourcePath, query, bytes.NewReader(data))
c.Logger.Debugf("jsonapi: request body: %d bytes", len(data))
return c.newRequestWithSerializedJSONBody(
ctx, method, resourcePath, query, bytes.NewReader(data))
}

func (c *Client) makeRequest(
func (c Client) newRequestWithSerializedJSONBody(
ctx context.Context, method, resourcePath string,
query url.Values, body io.Reader,
) (*http.Request, error) {
query url.Values, body io.Reader) (*http.Request, error) {
URL, err := url.Parse(c.BaseURL)
if err != nil {
return nil, err
Expand All @@ -82,79 +84,63 @@ func (c *Client) makeRequest(
return request.WithContext(ctx), nil
}

func (c *Client) dox(
do func(req *http.Request) (*http.Response, error),
readall func(r io.Reader) ([]byte, error),
request *http.Request,
output interface{},
) error {
response, err := do(request)
// Do performs the provided request and unmarshals the JSON response body
// into the provided output variable.
func (c Client) Do(request *http.Request, output interface{}) error {
response, err := c.HTTPClient.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode >= 400 {
return fmt.Errorf("Request failed: %s", response.Status)
return fmt.Errorf("jsonapi: request failed: %s", response.Status)
}
data, err := readall(response.Body)
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
c.Logger.Debugf("jsonapi: response body: %s", string(data))
c.Logger.Debugf("jsonapi: response body: %d bytes", len(data))
return json.Unmarshal(data, output)
}

func (c *Client) do(request *http.Request, output interface{}) error {
return c.dox(
c.HTTPClient.Do,
ioutil.ReadAll,
request, output,
)
}

// Read reads the JSON resource at resourcePath and unmarshals the
// results into output. The request is bounded by the lifetime of the
// context passed as argument. Returns the error that occurred.
func (c *Client) Read(
ctx context.Context, resourcePath string, output interface{},
) error {
func (c Client) Read(ctx context.Context, resourcePath string, output interface{}) error {
return c.ReadWithQuery(ctx, resourcePath, nil, output)
}

// ReadWithQuery is like Read but also has a query.
func (c *Client) ReadWithQuery(
func (c Client) ReadWithQuery(
ctx context.Context, resourcePath string,
query url.Values, output interface{},
) error {
request, err := c.makeRequest(ctx, "GET", resourcePath, query, nil)
query url.Values, output interface{}) error {
request, err := c.newRequestWithSerializedJSONBody(ctx, "GET", resourcePath, query, nil)
if err != nil {
return err
}
return c.do(request, output)
return c.Do(request, output)
}

// Create creates a JSON subresource of the resource at resourcePath
// using the JSON document at input and returning the result into the
// JSON document at output. The request is bounded by the context's
// lifetime. Returns the error that occurred.
func (c *Client) Create(
ctx context.Context, resourcePath string, input, output interface{},
) error {
request, err := c.makeRequestWithJSONBody(ctx, "POST", resourcePath, nil, input)
func (c Client) Create(
ctx context.Context, resourcePath string, input, output interface{}) error {
request, err := c.NewRequest(ctx, "POST", resourcePath, nil, input)
if err != nil {
return err
}
return c.do(request, output)
return c.Do(request, output)
}

// Update updates a JSON resource at a specific path and returns
// the error that occurred and possibly an output document
func (c *Client) Update(
ctx context.Context, resourcePath string, input, output interface{},
) error {
request, err := c.makeRequestWithJSONBody(ctx, "PUT", resourcePath, nil, input)
func (c Client) Update(
ctx context.Context, resourcePath string, input, output interface{}) error {
request, err := c.NewRequest(ctx, "PUT", resourcePath, nil, input)
if err != nil {
return err
}
return c.do(request, output)
return c.Do(request, output)
}

0 comments on commit f888c5c

Please sign in to comment.