OSDN Git Service

add ipfs package
[bytom/vapor.git] / vendor / github.com / ipfs / go-ipfs-api / request.go
1 package shell
2
3 import (
4         "context"
5         "encoding/json"
6         "errors"
7         "fmt"
8         "io"
9         "io/ioutil"
10         "net/http"
11         "net/url"
12         "os"
13         "strings"
14
15         files "github.com/ipfs/go-ipfs-files"
16 )
17
18 type Request struct {
19         ApiBase string
20         Command string
21         Args    []string
22         Opts    map[string]string
23         Body    io.Reader
24         Headers map[string]string
25 }
26
27 func NewRequest(ctx context.Context, url, command string, args ...string) *Request {
28         if !strings.HasPrefix(url, "http") {
29                 url = "http://" + url
30         }
31
32         opts := map[string]string{
33                 "encoding":        "json",
34                 "stream-channels": "true",
35         }
36         return &Request{
37                 ApiBase: url + "/api/v0",
38                 Command: command,
39                 Args:    args,
40                 Opts:    opts,
41                 Headers: make(map[string]string),
42         }
43 }
44
45 type trailerReader struct {
46         resp *http.Response
47 }
48
49 func (r *trailerReader) Read(b []byte) (int, error) {
50         n, err := r.resp.Body.Read(b)
51         if err != nil {
52                 if e := r.resp.Trailer.Get("X-Stream-Error"); e != "" {
53                         err = errors.New(e)
54                 }
55         }
56         return n, err
57 }
58
59 func (r *trailerReader) Close() error {
60         return r.resp.Body.Close()
61 }
62
63 type Response struct {
64         Output io.ReadCloser
65         Error  *Error
66 }
67
68 func (r *Response) Close() error {
69         if r.Output != nil {
70                 // always drain output (response body)
71                 _, err1 := io.Copy(ioutil.Discard, r.Output)
72                 err2 := r.Output.Close()
73                 if err1 != nil {
74                         return err1
75                 }
76                 if err2 != nil {
77                         return err2
78                 }
79         }
80         return nil
81 }
82
83 func (r *Response) Decode(dec interface{}) error {
84         defer r.Close()
85         if r.Error != nil {
86                 return r.Error
87         }
88
89         return json.NewDecoder(r.Output).Decode(dec)
90 }
91
92 type Error struct {
93         Command string
94         Message string
95         Code    int
96 }
97
98 func (e *Error) Error() string {
99         var out string
100         if e.Command != "" {
101                 out = e.Command + ": "
102         }
103         if e.Code != 0 {
104                 out = fmt.Sprintf("%s%d: ", out, e.Code)
105         }
106         return out + e.Message
107 }
108
109 func (r *Request) Send(c *http.Client) (*Response, error) {
110         url := r.getURL()
111         req, err := http.NewRequest("POST", url, r.Body)
112         if err != nil {
113                 return nil, err
114         }
115
116         // Add any headers that were supplied via the RequestBuilder.
117         for k, v := range r.Headers {
118                 req.Header.Add(k, v)
119         }
120
121         if fr, ok := r.Body.(*files.MultiFileReader); ok {
122                 req.Header.Set("Content-Type", "multipart/form-data; boundary="+fr.Boundary())
123                 req.Header.Set("Content-Disposition", "form-data; name=\"files\"")
124         }
125
126         resp, err := c.Do(req)
127         if err != nil {
128                 return nil, err
129         }
130
131         contentType := resp.Header.Get("Content-Type")
132         parts := strings.Split(contentType, ";")
133         contentType = parts[0]
134
135         nresp := new(Response)
136
137         nresp.Output = &trailerReader{resp}
138         if resp.StatusCode >= http.StatusBadRequest {
139                 e := &Error{
140                         Command: r.Command,
141                 }
142                 switch {
143                 case resp.StatusCode == http.StatusNotFound:
144                         e.Message = "command not found"
145                 case contentType == "text/plain":
146                         out, err := ioutil.ReadAll(resp.Body)
147                         if err != nil {
148                                 fmt.Fprintf(os.Stderr, "ipfs-shell: warning! response (%d) read error: %s\n", resp.StatusCode, err)
149                         }
150                         e.Message = string(out)
151                 case contentType == "application/json":
152                         if err = json.NewDecoder(resp.Body).Decode(e); err != nil {
153                                 fmt.Fprintf(os.Stderr, "ipfs-shell: warning! response (%d) unmarshall error: %s\n", resp.StatusCode, err)
154                         }
155                 default:
156                         fmt.Fprintf(os.Stderr, "ipfs-shell: warning! unhandled response (%d) encoding: %s", resp.StatusCode, contentType)
157                         out, err := ioutil.ReadAll(resp.Body)
158                         if err != nil {
159                                 fmt.Fprintf(os.Stderr, "ipfs-shell: response (%d) read error: %s\n", resp.StatusCode, err)
160                         }
161                         e.Message = fmt.Sprintf("unknown ipfs-shell error encoding: %q - %q", contentType, out)
162                 }
163                 nresp.Error = e
164                 nresp.Output = nil
165
166                 // drain body and close
167                 io.Copy(ioutil.Discard, resp.Body)
168                 resp.Body.Close()
169         }
170
171         return nresp, nil
172 }
173
174 func (r *Request) getURL() string {
175
176         values := make(url.Values)
177         for _, arg := range r.Args {
178                 values.Add("arg", arg)
179         }
180         for k, v := range r.Opts {
181                 values.Add(k, v)
182         }
183
184         return fmt.Sprintf("%s/%s?%s", r.ApiBase, r.Command, values.Encode())
185 }