+++ /dev/null
-package shell
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "os"
- "strings"
-
- files "github.com/ipfs/go-ipfs-files"
-)
-
-type Request struct {
- ApiBase string
- Command string
- Args []string
- Opts map[string]string
- Body io.Reader
- Headers map[string]string
-}
-
-func NewRequest(ctx context.Context, url, command string, args ...string) *Request {
- if !strings.HasPrefix(url, "http") {
- url = "http://" + url
- }
-
- opts := map[string]string{
- "encoding": "json",
- "stream-channels": "true",
- }
- return &Request{
- ApiBase: url + "/api/v0",
- Command: command,
- Args: args,
- Opts: opts,
- Headers: make(map[string]string),
- }
-}
-
-type trailerReader struct {
- resp *http.Response
-}
-
-func (r *trailerReader) Read(b []byte) (int, error) {
- n, err := r.resp.Body.Read(b)
- if err != nil {
- if e := r.resp.Trailer.Get("X-Stream-Error"); e != "" {
- err = errors.New(e)
- }
- }
- return n, err
-}
-
-func (r *trailerReader) Close() error {
- return r.resp.Body.Close()
-}
-
-type Response struct {
- Output io.ReadCloser
- Error *Error
-}
-
-func (r *Response) Close() error {
- if r.Output != nil {
- // always drain output (response body)
- _, err1 := io.Copy(ioutil.Discard, r.Output)
- err2 := r.Output.Close()
- if err1 != nil {
- return err1
- }
- if err2 != nil {
- return err2
- }
- }
- return nil
-}
-
-func (r *Response) Decode(dec interface{}) error {
- defer r.Close()
- if r.Error != nil {
- return r.Error
- }
-
- return json.NewDecoder(r.Output).Decode(dec)
-}
-
-type Error struct {
- Command string
- Message string
- Code int
-}
-
-func (e *Error) Error() string {
- var out string
- if e.Command != "" {
- out = e.Command + ": "
- }
- if e.Code != 0 {
- out = fmt.Sprintf("%s%d: ", out, e.Code)
- }
- return out + e.Message
-}
-
-func (r *Request) Send(c *http.Client) (*Response, error) {
- url := r.getURL()
- req, err := http.NewRequest("POST", url, r.Body)
- if err != nil {
- return nil, err
- }
-
- // Add any headers that were supplied via the RequestBuilder.
- for k, v := range r.Headers {
- req.Header.Add(k, v)
- }
-
- if fr, ok := r.Body.(*files.MultiFileReader); ok {
- req.Header.Set("Content-Type", "multipart/form-data; boundary="+fr.Boundary())
- req.Header.Set("Content-Disposition", "form-data; name=\"files\"")
- }
-
- resp, err := c.Do(req)
- if err != nil {
- return nil, err
- }
-
- contentType := resp.Header.Get("Content-Type")
- parts := strings.Split(contentType, ";")
- contentType = parts[0]
-
- nresp := new(Response)
-
- nresp.Output = &trailerReader{resp}
- if resp.StatusCode >= http.StatusBadRequest {
- e := &Error{
- Command: r.Command,
- }
- switch {
- case resp.StatusCode == http.StatusNotFound:
- e.Message = "command not found"
- case contentType == "text/plain":
- out, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- fmt.Fprintf(os.Stderr, "ipfs-shell: warning! response (%d) read error: %s\n", resp.StatusCode, err)
- }
- e.Message = string(out)
- case contentType == "application/json":
- if err = json.NewDecoder(resp.Body).Decode(e); err != nil {
- fmt.Fprintf(os.Stderr, "ipfs-shell: warning! response (%d) unmarshall error: %s\n", resp.StatusCode, err)
- }
- default:
- fmt.Fprintf(os.Stderr, "ipfs-shell: warning! unhandled response (%d) encoding: %s", resp.StatusCode, contentType)
- out, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- fmt.Fprintf(os.Stderr, "ipfs-shell: response (%d) read error: %s\n", resp.StatusCode, err)
- }
- e.Message = fmt.Sprintf("unknown ipfs-shell error encoding: %q - %q", contentType, out)
- }
- nresp.Error = e
- nresp.Output = nil
-
- // drain body and close
- io.Copy(ioutil.Discard, resp.Body)
- resp.Body.Close()
- }
-
- return nresp, nil
-}
-
-func (r *Request) getURL() string {
-
- values := make(url.Values)
- for _, arg := range r.Args {
- values.Add("arg", arg)
- }
- for k, v := range r.Opts {
- values.Add(k, v)
- }
-
- return fmt.Sprintf("%s/%s?%s", r.ApiBase, r.Command, values.Encode())
-}