diff --git a/http/client.go b/http/client.go index 75d3c75c7..e7c94fe16 100644 --- a/http/client.go +++ b/http/client.go @@ -84,9 +84,9 @@ func (c *InternalClient) maxShardByIndex(ctx context.Context) (map[string]uint64 req.Header.Set("Accept", "application/json") // Execute request. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, errors.Wrap(err, "executing request") + return nil, err } defer resp.Body.Close() @@ -115,16 +115,14 @@ func (c *InternalClient) Schema(ctx context.Context) ([]*pilosa.IndexInfo, error req.Header.Set("Accept", "application/json") // Execute request. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, errors.Wrap(err, "executing request") + return nil, err } defer resp.Body.Close() var rsp getSchemaResponse - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("http: status=%d", resp.StatusCode) - } else if err := json.NewDecoder(resp.Body).Decode(&rsp); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&rsp); err != nil { return nil, fmt.Errorf("json decode: %s", err) } return rsp.Indexes, nil @@ -152,27 +150,14 @@ func (c *InternalClient) CreateIndex(ctx context.Context, index string, opt pilo req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) // Execute request against the host. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return errors.Wrap(err, "executing request") - } - defer resp.Body.Close() - - // Read body. - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return errors.Wrap(err, "reading") - } - - // Handle response based on status code. - switch resp.StatusCode { - case http.StatusOK: - return nil // ok - case http.StatusConflict: - return pilosa.ErrIndexExists - default: - return errors.New(string(body)) + if resp.StatusCode == http.StatusConflict { + return pilosa.ErrIndexExists + } + return err } + return nil } // FragmentNodes returns a list of nodes that own a shard. @@ -191,19 +176,16 @@ func (c *InternalClient) FragmentNodes(ctx context.Context, index string, shard req.Header.Set("Accept", "application/json") // Execute request. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, errors.Wrap(err, "executing request") + return nil, err } defer resp.Body.Close() var a []*pilosa.Node - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("http: status=%d", resp.StatusCode) - } else if err := json.NewDecoder(resp.Body).Decode(&a); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&a); err != nil { return nil, fmt.Errorf("json decode: %s", err) } - return a, nil } @@ -222,19 +204,16 @@ func (c *InternalClient) Nodes(ctx context.Context) ([]*pilosa.Node, error) { req.Header.Set("Accept", "application/json") // Execute request. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, errors.Wrap(err, "executing request") + return nil, err } defer resp.Body.Close() var a []*pilosa.Node - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("http: status=%d", resp.StatusCode) - } else if err := json.NewDecoder(resp.Body).Decode(&a); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&a); err != nil { return nil, fmt.Errorf("json decode: %s", err) } - return a, nil } @@ -269,9 +248,9 @@ func (c *InternalClient) QueryNode(ctx context.Context, uri *pilosa.URI, index s req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) // Execute request against the host. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, errors.Wrap(err, "executing request") + return nil, err } defer resp.Body.Close() @@ -452,9 +431,9 @@ func (c *InternalClient) importNode(ctx context.Context, node *pilosa.Node, inde req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) // Execute request against the host. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return errors.Wrap(err, "executing request") + return err } defer resp.Body.Close() @@ -462,8 +441,6 @@ func (c *InternalClient) importNode(ctx context.Context, node *pilosa.Node, inde body, err := ioutil.ReadAll(resp.Body) if err != nil { return errors.Wrap(err, "reading") - } else if resp.StatusCode != http.StatusOK { - return errors.New(string(body)) } var isresp pilosa.ImportResponse @@ -608,17 +585,12 @@ func (c *InternalClient) ImportRoaring(ctx context.Context, uri *pilosa.URI, ind req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) // Execute request against the host. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return errors.Wrap(err, "executing request") + return err } defer resp.Body.Close() - // Validate status code. - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("invalid status: %d", resp.StatusCode) - } - dec := json.NewDecoder(resp.Body) rbody := &pilosa.ImportResponse{} dec.Decode(rbody) @@ -677,17 +649,12 @@ func (c *InternalClient) exportNodeCSV(ctx context.Context, node *pilosa.Node, i req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) // Execute request against the host. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return errors.Wrap(err, "executing request") + return err } defer resp.Body.Close() - // Validate status code. - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("invalid status: %d", resp.StatusCode) - } - // Copy body to writer. if _, err := io.Copy(w, resp.Body); err != nil { return errors.Wrap(err, "copying") @@ -720,18 +687,12 @@ func (c *InternalClient) backupShardNode(ctx context.Context, index, field strin req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) // Execute request. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, errors.Wrap(err, "executing request") - } - - // Return error if status is not OK. - if resp.StatusCode == http.StatusNotFound { - resp.Body.Close() - return nil, pilosa.ErrFragmentNotFound - } else if resp.StatusCode != http.StatusOK { - resp.Body.Close() - return nil, fmt.Errorf("unexpected backup status code: host=%s, code=%d", node.URI, resp.StatusCode) + if resp.StatusCode == http.StatusNotFound { + return nil, pilosa.ErrFragmentNotFound + } + return nil, err } return resp.Body, nil @@ -783,27 +744,15 @@ func (c *InternalClient) CreateFieldWithOptions(ctx context.Context, index, fiel req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) // Execute request against the host. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return errors.Wrap(err, "executing request") - } - defer resp.Body.Close() - - // Read body. - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return errors.Wrap(err, "reading") + if resp.StatusCode == http.StatusConflict { + return pilosa.ErrFieldExists + } + return err } - // Handle response based on status code. - switch resp.StatusCode { - case http.StatusOK: - return nil // ok - case http.StatusConflict: - return pilosa.ErrFieldExists - default: - return errors.New(string(body)) - } + return nil } // FragmentBlocks returns a list of block checksums for a fragment on a host. @@ -830,21 +779,16 @@ func (c *InternalClient) FragmentBlocks(ctx context.Context, uri *pilosa.URI, in req.Header.Set("Accept", "application/json") // Execute request. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, errors.Wrap(err, "executing request") + // Return the appropriate error. + if resp.StatusCode == http.StatusNotFound { + return nil, pilosa.ErrFragmentNotFound + } + return nil, err } defer resp.Body.Close() - // Return error if status is not OK. - switch resp.StatusCode { - case http.StatusOK: // ok - case http.StatusNotFound: - return nil, pilosa.ErrFragmentNotFound - default: - return nil, fmt.Errorf("unexpected status: code=%d", resp.StatusCode) - } - // Decode response object. var rsp getFragmentBlocksResponse if err := json.NewDecoder(resp.Body).Decode(&rsp); err != nil { @@ -879,21 +823,15 @@ func (c *InternalClient) BlockData(ctx context.Context, uri *pilosa.URI, index, req.Header.Set("Accept", "application/protobuf") req.Header.Set("User-Agent", "pilosa/"+pilosa.Version) - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, nil, errors.Wrap(err, "executing request") + if resp.StatusCode == http.StatusNotFound { + return nil, nil, nil + } + return nil, nil, err } defer resp.Body.Close() - // Return error if status is not OK. - switch resp.StatusCode { - case http.StatusOK: // fallthrough - case http.StatusNotFound: - return nil, nil, nil - default: - return nil, nil, fmt.Errorf("unexpected status: code=%d", resp.StatusCode) - } - // Decode response object. var rsp pilosa.BlockDataResponse if body, err := ioutil.ReadAll(resp.Body); err != nil { @@ -927,19 +865,12 @@ func (c *InternalClient) ColumnAttrDiff(ctx context.Context, uri *pilosa.URI, in req.Header.Set("Accept", "application/json") // Execute request. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, errors.Wrap(err, "executing request") + return nil, err } defer resp.Body.Close() - // Return error if status is not OK. - switch resp.StatusCode { - case http.StatusOK: // ok - default: - return nil, fmt.Errorf("unexpected status: code=%d", resp.StatusCode) - } - // Decode response object. var rsp postIndexAttrDiffResponse if err := json.NewDecoder(resp.Body).Decode(&rsp); err != nil { @@ -971,21 +902,15 @@ func (c *InternalClient) RowAttrDiff(ctx context.Context, uri *pilosa.URI, index req.Header.Set("Accept", "application/json") // Execute request. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + resp, err := c.executeRequest(req.WithContext(ctx)) if err != nil { - return nil, errors.Wrap(err, "executing request") + if resp.StatusCode == http.StatusNotFound { + return nil, pilosa.ErrFieldNotFound + } + return nil, err } defer resp.Body.Close() - // Return error if status is not OK. - switch resp.StatusCode { - case http.StatusOK: // ok - case http.StatusNotFound: - return nil, pilosa.ErrFieldNotFound - default: - return nil, fmt.Errorf("unexpected status: code=%d", resp.StatusCode) - } - // Decode response object. var rsp postFieldAttrDiffResponse if err := json.NewDecoder(resp.Body).Decode(&rsp); err != nil { @@ -1006,28 +931,38 @@ func (c *InternalClient) SendMessage(ctx context.Context, uri *pilosa.URI, msg [ req.Header.Set("Accept", "application/json") // Execute request. - resp, err := c.httpClient.Do(req.WithContext(ctx)) + _, err = c.executeRequest(req.WithContext(ctx)) if err != nil { - return fmt.Errorf("executing http request: %v", err) + return err } - defer resp.Body.Close() - - // Read body. - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("reading response body: %v", err) - } - - // Return error if status is not OK. - switch resp.StatusCode { - case http.StatusOK: // ok - default: - return fmt.Errorf("unexpected response status code: %d: %s", resp.StatusCode, body) - } - return nil } +// executeRequest executes the given request and checks the Response +func (c *InternalClient) executeRequest(req *http.Request) (*http.Response, error) { + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, errors.Wrap(err, "executing request") + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + defer resp.Body.Close() + buf, err := ioutil.ReadAll(resp.Body) + if err != nil { + return resp, errors.Wrapf(err, "bad status '%s' and err reading body", resp.Status) + } + var msg string + // try to decode a JSON response + var sr successResponse + if err = json.Unmarshal(buf, &sr); err == nil { + msg = sr.Error.Error() + } else { + msg = string(buf) + } + return resp, errors.Errorf("Server error %s: '%s'", resp.Status, msg) + } + return resp, nil +} + // Bits is a slice of Bit. type Bits []pilosa.Bit