Update upstream source from tag 'upstream/0.0_git20200310.e035052'
Update to upstream version '0.0~git20200310.e035052'
with Debian dir a6d39782099147317b1e65bbc4ad553d124fdb0c
Shengjing Zhu
3 years ago
0 | [![GoDoc](https://godoc.org/github.com/kolo/xmlrpc?status.svg)](https://godoc.org/github.com/kolo/xmlrpc) | |
1 | ||
0 | 2 | ## Overview |
1 | 3 | |
2 | 4 | xmlrpc is an implementation of client side part of XMLRPC protocol in Go language. |
5 | ||
6 | ## Status | |
7 | ||
8 | This project is in minimal maintenance mode with no further development. Bug fixes | |
9 | are accepted, but it might take some time until they will be merged. | |
3 | 10 | |
4 | 11 | ## Installation |
5 | 12 | |
27 | 34 | arguments. |
28 | 35 | |
29 | 36 | Data types encoding rules: |
37 | ||
30 | 38 | * int, int8, int16, int32, int64 encoded to int; |
31 | 39 | * float32, float64 encoded to double; |
32 | 40 | * bool encoded to boolean; |
33 | 41 | * string encoded to string; |
34 | 42 | * time.Time encoded to datetime.iso8601; |
35 | 43 | * xmlrpc.Base64 encoded to base64; |
36 | * slice decoded to array; | |
44 | * slice encoded to array; | |
37 | 45 | |
38 | Structs decoded to struct by following rules: | |
46 | Structs encoded to struct by following rules: | |
47 | ||
39 | 48 | * all public field become struct members; |
40 | 49 | * field name become member name; |
41 | 50 | * if field has xmlrpc tag, its value become member name. |
51 | * for fields tagged with `",omitempty"`, empty values are omitted; | |
42 | 52 | |
43 | 53 | Server method can accept few arguments, to handle this case there is |
44 | 54 | special approach to handle slice of empty interfaces (`[]interface{}`). |
49 | 59 | Result of remote function is decoded to native Go data type. |
50 | 60 | |
51 | 61 | Data types decoding rules: |
62 | ||
52 | 63 | * int, i4 decoded to int, int8, int16, int32, int64; |
53 | 64 | * double decoded to float32, float64; |
54 | 65 | * boolean decoded to bool; |
71 | 82 | |
72 | 83 | ## Contribution |
73 | 84 | |
74 | Feel free to fork the project, submit pull requests, ask questions. | |
85 | See [project status](#status). | |
75 | 86 | |
76 | 87 | ## Authors |
77 | 88 |
0 | 0 | package xmlrpc |
1 | 1 | |
2 | 2 | import ( |
3 | "errors" | |
3 | 4 | "fmt" |
4 | 5 | "io/ioutil" |
5 | 6 | "net/http" |
6 | 7 | "net/http/cookiejar" |
7 | 8 | "net/rpc" |
8 | 9 | "net/url" |
10 | "sync" | |
9 | 11 | ) |
10 | 12 | |
11 | 13 | type Client struct { |
26 | 28 | // responses presents map of active requests. It is required to return request id, that |
27 | 29 | // rpc.Client can mark them as done. |
28 | 30 | responses map[uint64]*http.Response |
31 | mutex sync.Mutex | |
29 | 32 | |
30 | response *Response | |
33 | response Response | |
31 | 34 | |
32 | 35 | // ready presents channel, that is used to link request and it`s response. |
33 | 36 | ready chan uint64 |
37 | ||
38 | // close notifies codec is closed. | |
39 | close chan uint64 | |
34 | 40 | } |
35 | 41 | |
36 | 42 | func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) { |
37 | 43 | httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args) |
38 | 44 | |
45 | if err != nil { | |
46 | return err | |
47 | } | |
48 | ||
39 | 49 | if codec.cookies != nil { |
40 | 50 | for _, cookie := range codec.cookies.Cookies(codec.url) { |
41 | 51 | httpRequest.AddCookie(cookie) |
42 | 52 | } |
43 | } | |
44 | ||
45 | if err != nil { | |
46 | return err | |
47 | 53 | } |
48 | 54 | |
49 | 55 | var httpResponse *http.Response |
57 | 63 | codec.cookies.SetCookies(codec.url, httpResponse.Cookies()) |
58 | 64 | } |
59 | 65 | |
66 | codec.mutex.Lock() | |
60 | 67 | codec.responses[request.Seq] = httpResponse |
68 | codec.mutex.Unlock() | |
69 | ||
61 | 70 | codec.ready <- request.Seq |
62 | 71 | |
63 | 72 | return nil |
64 | 73 | } |
65 | 74 | |
66 | 75 | func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) { |
67 | seq := <-codec.ready | |
76 | var seq uint64 | |
77 | select { | |
78 | case seq = <-codec.ready: | |
79 | case <-codec.close: | |
80 | return errors.New("codec is closed") | |
81 | } | |
82 | response.Seq = seq | |
83 | ||
84 | codec.mutex.Lock() | |
68 | 85 | httpResponse := codec.responses[seq] |
86 | delete(codec.responses, seq) | |
87 | codec.mutex.Unlock() | |
88 | ||
89 | defer httpResponse.Body.Close() | |
69 | 90 | |
70 | 91 | if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { |
71 | return fmt.Errorf("request error: bad status code - %d", httpResponse.StatusCode) | |
92 | response.Error = fmt.Sprintf("request error: bad status code - %d", httpResponse.StatusCode) | |
93 | return nil | |
72 | 94 | } |
73 | 95 | |
74 | respData, err := ioutil.ReadAll(httpResponse.Body) | |
75 | ||
96 | body, err := ioutil.ReadAll(httpResponse.Body) | |
76 | 97 | if err != nil { |
77 | return err | |
98 | response.Error = err.Error() | |
99 | return nil | |
78 | 100 | } |
79 | 101 | |
80 | httpResponse.Body.Close() | |
81 | ||
82 | resp := NewResponse(respData) | |
83 | ||
84 | if resp.Failed() { | |
85 | response.Error = fmt.Sprintf("%v", resp.Err()) | |
102 | resp := Response(body) | |
103 | if err := resp.Err(); err != nil { | |
104 | response.Error = err.Error() | |
105 | return nil | |
86 | 106 | } |
87 | 107 | |
88 | 108 | codec.response = resp |
89 | ||
90 | response.Seq = seq | |
91 | delete(codec.responses, seq) | |
92 | 109 | |
93 | 110 | return nil |
94 | 111 | } |
97 | 114 | if v == nil { |
98 | 115 | return nil |
99 | 116 | } |
100 | ||
101 | if err = codec.response.Unmarshal(v); err != nil { | |
102 | return err | |
103 | } | |
104 | ||
105 | return nil | |
117 | return codec.response.Unmarshal(v) | |
106 | 118 | } |
107 | 119 | |
108 | 120 | func (codec *clientCodec) Close() error { |
109 | transport := codec.httpClient.Transport.(*http.Transport) | |
110 | transport.CloseIdleConnections() | |
121 | if transport, ok := codec.httpClient.Transport.(*http.Transport); ok { | |
122 | transport.CloseIdleConnections() | |
123 | } | |
124 | ||
125 | close(codec.close) | |
126 | ||
111 | 127 | return nil |
112 | 128 | } |
113 | 129 | |
134 | 150 | codec := clientCodec{ |
135 | 151 | url: u, |
136 | 152 | httpClient: httpClient, |
153 | close: make(chan uint64), | |
137 | 154 | ready: make(chan uint64), |
138 | 155 | responses: make(map[uint64]*http.Response), |
139 | 156 | cookies: jar, |
0 | // +build integration | |
1 | ||
0 | 2 | package xmlrpc |
1 | 3 | |
2 | 4 | import ( |
5 | "context" | |
6 | "io" | |
7 | "net/http" | |
8 | "net/http/httptest" | |
9 | "runtime" | |
10 | "sync" | |
3 | 11 | "testing" |
4 | 12 | "time" |
5 | 13 | ) |
68 | 76 | } |
69 | 77 | } |
70 | 78 | |
79 | func Test_ConcurrentCalls(t *testing.T) { | |
80 | client := newClient(t) | |
81 | ||
82 | call := func() { | |
83 | var result time.Time | |
84 | client.Call("service.time", nil, &result) | |
85 | } | |
86 | ||
87 | var wg sync.WaitGroup | |
88 | for i := 0; i < 100; i++ { | |
89 | wg.Add(1) | |
90 | go func() { | |
91 | call() | |
92 | wg.Done() | |
93 | }() | |
94 | } | |
95 | ||
96 | wg.Wait() | |
97 | client.Close() | |
98 | } | |
99 | ||
100 | func Test_CloseMemoryLeak(t *testing.T) { | |
101 | expected := runtime.NumGoroutine() | |
102 | ||
103 | for i := 0; i < 3; i++ { | |
104 | client := newClient(t) | |
105 | client.Call("service.time", nil, nil) | |
106 | client.Close() | |
107 | } | |
108 | ||
109 | var actual int | |
110 | ||
111 | // It takes some time to stop running goroutinges. This function checks number of | |
112 | // running goroutines. It finishes execution if number is same as expected or timeout | |
113 | // has been reached. | |
114 | func() { | |
115 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) | |
116 | defer cancel() | |
117 | ||
118 | for { | |
119 | select { | |
120 | case <-ctx.Done(): | |
121 | return | |
122 | default: | |
123 | actual = runtime.NumGoroutine() | |
124 | if actual == expected { | |
125 | return | |
126 | } | |
127 | } | |
128 | } | |
129 | }() | |
130 | ||
131 | if actual != expected { | |
132 | t.Errorf("expected number of running goroutines to be %d, but got %d", expected, actual) | |
133 | } | |
134 | } | |
135 | ||
136 | func Test_BadStatus(t *testing.T) { | |
137 | ||
138 | // this is a mock xmlrpc server which sends an invalid status code on the first request | |
139 | // and an empty methodResponse for all subsequence requests | |
140 | first := true | |
141 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
142 | if first { | |
143 | first = false | |
144 | http.Error(w, "bad status", http.StatusInternalServerError) | |
145 | } else { | |
146 | io.WriteString(w, ` | |
147 | <?xml version="1.0" encoding="UTF-8"?> | |
148 | <methodResponse> | |
149 | <params> | |
150 | <param> | |
151 | <value> | |
152 | <struct></struct> | |
153 | </value> | |
154 | </param> | |
155 | </params> | |
156 | </methodResponse> | |
157 | `) | |
158 | } | |
159 | })) | |
160 | ||
161 | client, err := NewClient(ts.URL, nil) | |
162 | if err != nil { | |
163 | t.Fatalf("Can't create client: %v", err) | |
164 | } | |
165 | defer client.Close() | |
166 | ||
167 | var result interface{} | |
168 | ||
169 | // expect an error due to the bad status code | |
170 | if err := client.Call("method", nil, &result); err == nil { | |
171 | t.Fatalf("Bad status didn't result in error") | |
172 | } | |
173 | ||
174 | // expect subsequent calls to succeed | |
175 | if err := client.Call("method", nil, &result); err != nil { | |
176 | t.Fatalf("Failed to recover after bad status: %v", err) | |
177 | } | |
178 | } | |
179 | ||
71 | 180 | func newClient(t *testing.T) *Client { |
72 | 181 | client, err := NewClient("http://localhost:5001", nil) |
73 | 182 | if err != nil { |
74 | 183 | t.Fatalf("Can't create client: %v", err) |
75 | 184 | } |
76 | ||
77 | 185 | return client |
78 | 186 | } |
11 | 11 | "time" |
12 | 12 | ) |
13 | 13 | |
14 | const iso8601 = "20060102T15:04:05" | |
14 | const ( | |
15 | iso8601 = "20060102T15:04:05" | |
16 | iso8601Z = "20060102T15:04:05Z07:00" | |
17 | iso8601Hyphen = "2006-01-02T15:04:05" | |
18 | iso8601HyphenZ = "2006-01-02T15:04:05Z07:00" | |
19 | ) | |
15 | 20 | |
16 | 21 | var ( |
17 | 22 | // CharsetReader is a function to generate reader which converts a non UTF-8 |
18 | 23 | // charset into UTF-8. |
19 | 24 | CharsetReader func(string, io.Reader) (io.Reader, error) |
20 | 25 | |
26 | timeLayouts = []string{iso8601, iso8601Z, iso8601Hyphen, iso8601HyphenZ} | |
21 | 27 | invalidXmlError = errors.New("invalid xml") |
22 | 28 | ) |
23 | 29 | |
123 | 129 | ismap = true |
124 | 130 | } else if checkType(val, reflect.Interface) == nil && val.IsNil() { |
125 | 131 | var dummy map[string]interface{} |
126 | pmap = reflect.New(reflect.TypeOf(dummy)).Elem() | |
127 | valType = pmap.Type() | |
132 | valType = reflect.TypeOf(dummy) | |
133 | pmap = reflect.New(valType).Elem() | |
134 | val.Set(pmap) | |
128 | 135 | ismap = true |
129 | 136 | } else { |
130 | 137 | return err |
216 | 223 | } |
217 | 224 | } |
218 | 225 | case "array": |
219 | pslice := val | |
226 | slice := val | |
220 | 227 | if checkType(val, reflect.Interface) == nil && val.IsNil() { |
221 | var dummy []interface{} | |
222 | pslice = reflect.New(reflect.TypeOf(dummy)).Elem() | |
228 | slice = reflect.ValueOf([]interface{}{}) | |
223 | 229 | } else if err = checkType(val, reflect.Slice); err != nil { |
224 | 230 | return err |
225 | 231 | } |
232 | 238 | |
233 | 239 | switch t := tok.(type) { |
234 | 240 | case xml.StartElement: |
241 | var index int | |
235 | 242 | if t.Name.Local != "data" { |
236 | 243 | return invalidXmlError |
237 | 244 | } |
238 | ||
239 | slice := reflect.MakeSlice(pslice.Type(), 0, 0) | |
240 | ||
241 | 245 | DataLoop: |
242 | 246 | for { |
243 | 247 | if tok, err = dec.Token(); err != nil { |
250 | 254 | return invalidXmlError |
251 | 255 | } |
252 | 256 | |
253 | v := reflect.New(pslice.Type().Elem()) | |
254 | if err = dec.decodeValue(v); err != nil { | |
255 | return err | |
257 | if index < slice.Len() { | |
258 | v := slice.Index(index) | |
259 | if v.Kind() == reflect.Interface { | |
260 | v = v.Elem() | |
261 | } | |
262 | if v.Kind() != reflect.Ptr { | |
263 | return errors.New("error: cannot write to non-pointer array element") | |
264 | } | |
265 | if err = dec.decodeValue(v); err != nil { | |
266 | return err | |
267 | } | |
268 | } else { | |
269 | v := reflect.New(slice.Type().Elem()) | |
270 | if err = dec.decodeValue(v); err != nil { | |
271 | return err | |
272 | } | |
273 | slice = reflect.Append(slice, v.Elem()) | |
256 | 274 | } |
257 | ||
258 | slice = reflect.Append(slice, v.Elem()) | |
259 | 275 | |
260 | 276 | // </value> |
261 | 277 | if err = dec.Skip(); err != nil { |
262 | 278 | return err |
263 | 279 | } |
280 | index++ | |
264 | 281 | case xml.EndElement: |
265 | pslice.Set(slice) | |
266 | val.Set(pslice) | |
282 | val.Set(slice) | |
267 | 283 | break DataLoop |
268 | 284 | } |
269 | 285 | } |
320 | 336 | val.SetString(str) |
321 | 337 | } |
322 | 338 | case "dateTime.iso8601": |
323 | t, err := time.Parse(iso8601, string(data)) | |
339 | var t time.Time | |
340 | var err error | |
341 | ||
342 | for _, layout := range timeLayouts { | |
343 | t, err = time.Parse(layout, string(data)) | |
344 | if err == nil { | |
345 | break | |
346 | } | |
347 | } | |
324 | 348 | if err != nil { |
325 | 349 | return err |
326 | 350 | } |
26 | 26 | ptr interface{} |
27 | 27 | xml string |
28 | 28 | }{ |
29 | // int, i4, i8 | |
30 | {0, new(*int), "<value><int></int></value>"}, | |
29 | 31 | {100, new(*int), "<value><int>100</int></value>"}, |
32 | {389451, new(*int), "<value><i4>389451</i4></value>"}, | |
30 | 33 | {int64(45659074), new(*int64), "<value><i8>45659074</i8></value>"}, |
34 | ||
35 | // string | |
31 | 36 | {"Once upon a time", new(*string), "<value><string>Once upon a time</string></value>"}, |
32 | 37 | {"Mike & Mick <London, UK>", new(*string), "<value><string>Mike & Mick <London, UK></string></value>"}, |
33 | 38 | {"Once upon a time", new(*string), "<value>Once upon a time</value>"}, |
39 | ||
40 | // base64 | |
34 | 41 | {"T25jZSB1cG9uIGEgdGltZQ==", new(*string), "<value><base64>T25jZSB1cG9uIGEgdGltZQ==</base64></value>"}, |
42 | ||
43 | // boolean | |
35 | 44 | {true, new(*bool), "<value><boolean>1</boolean></value>"}, |
36 | 45 | {false, new(*bool), "<value><boolean>0</boolean></value>"}, |
46 | ||
47 | // double | |
37 | 48 | {12.134, new(*float32), "<value><double>12.134</double></value>"}, |
38 | 49 | {-12.134, new(*float32), "<value><double>-12.134</double></value>"}, |
39 | {time.Unix(1386622812, 0).UTC(), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12</dateTime.iso8601></value>"}, | |
50 | ||
51 | // datetime.iso8601 | |
52 | {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12</dateTime.iso8601></value>"}, | |
53 | {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12Z</dateTime.iso8601></value>"}, | |
54 | {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12-01:00</dateTime.iso8601></value>"}, | |
55 | {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "<value><dateTime.iso8601>20131209T21:00:12+01:00</dateTime.iso8601></value>"}, | |
56 | {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12</dateTime.iso8601></value>"}, | |
57 | {_time("2013-12-09T21:00:12Z"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12Z</dateTime.iso8601></value>"}, | |
58 | {_time("2013-12-09T21:00:12-01:00"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12-01:00</dateTime.iso8601></value>"}, | |
59 | {_time("2013-12-09T21:00:12+01:00"), new(*time.Time), "<value><dateTime.iso8601>2013-12-09T21:00:12+01:00</dateTime.iso8601></value>"}, | |
60 | ||
61 | // array | |
40 | 62 | {[]int{1, 5, 7}, new(*[]int), "<value><array><data><value><int>1</int></value><value><int>5</int></value><value><int>7</int></value></data></array></value>"}, |
63 | {[]interface{}{"A", "5"}, new(interface{}), "<value><array><data><value><string>A</string></value><value><string>5</string></value></data></array></value>"}, | |
64 | {[]interface{}{"A", int64(5)}, new(interface{}), "<value><array><data><value><string>A</string></value><value><int>5</int></value></data></array></value>"}, | |
65 | ||
66 | // struct | |
41 | 67 | {book{"War and Piece", 20}, new(*book), "<value><struct><member><name>Title</name><value><string>War and Piece</string></value></member><member><name>Amount</name><value><int>20</int></value></member></struct></value>"}, |
42 | 68 | {bookUnexported{}, new(*bookUnexported), "<value><struct><member><name>title</name><value><string>War and Piece</string></value></member><member><name>amount</name><value><int>20</int></value></member></struct></value>"}, |
43 | {0, new(*int), "<value><int></int></value>"}, | |
44 | {[]interface{}{"A", "5"}, new(interface{}), "<value><array><data><value><string>A</string></value><value><string>5</string></value></data></array></value>"}, | |
45 | //{map[string]interface{}{"Name": "John Smith", | |
46 | // "Age": 6, | |
47 | // "Wight": []interface{}{66.67, 100.5}}, | |
48 | // new(interface{}), "<value><struct><member><name>Name</name><value><string>John Smith</string></value></member><member><name>Age</name><value><int>6</int></value></member><member><name>Wight</name><value><array><data><value><double>66.67</double></value><value><double>100.5</double></value></data></array></value></member></struct></value>"}, | |
49 | 69 | {map[string]interface{}{"Name": "John Smith"}, new(interface{}), "<value><struct><member><name>Name</name><value><string>John Smith</string></value></member></struct></value>"}, |
70 | {map[string]interface{}{}, new(interface{}), "<value><struct></struct></value>"}, | |
71 | } | |
72 | ||
73 | func _time(s string) time.Time { | |
74 | t, err := time.Parse(time.RFC3339, s) | |
75 | if err != nil { | |
76 | panic(fmt.Sprintf("time parsing error: %v", err)) | |
77 | } | |
78 | return t | |
50 | 79 | } |
51 | 80 | |
52 | 81 | func Test_unmarshal(t *testing.T) { |
90 | 119 | func Test_typeMismatchError(t *testing.T) { |
91 | 120 | var s string |
92 | 121 | |
93 | tt := unmarshalTests[0] | |
122 | encoded := "<value><int>100</int></value>" | |
94 | 123 | var err error |
95 | 124 | |
96 | if err = unmarshal([]byte(tt.xml), &s); err == nil { | |
125 | if err = unmarshal([]byte(encoded), &s); err == nil { | |
97 | 126 | t.Fatal("unmarshal error: expected error, but didn't get it") |
98 | 127 | } |
99 | 128 | |
107 | 136 | |
108 | 137 | if err := unmarshal([]byte("<value/>"), &v); err != nil { |
109 | 138 | t.Fatalf("unmarshal error: %v", err) |
139 | } | |
140 | } | |
141 | ||
142 | const structEmptyXML = ` | |
143 | <value> | |
144 | <struct> | |
145 | </struct> | |
146 | </value> | |
147 | ` | |
148 | ||
149 | func Test_unmarshalEmptyStruct(t *testing.T) { | |
150 | var v interface{} | |
151 | if err := unmarshal([]byte(structEmptyXML), &v); err != nil { | |
152 | t.Fatal(err) | |
153 | } | |
154 | if v == nil { | |
155 | t.Fatalf("got nil map") | |
156 | } | |
157 | } | |
158 | ||
159 | const arrayValueXML = ` | |
160 | <value> | |
161 | <array> | |
162 | <data> | |
163 | <value><int>234</int></value> | |
164 | <value><boolean>1</boolean></value> | |
165 | <value><string>Hello World</string></value> | |
166 | <value><string>Extra Value</string></value> | |
167 | </data> | |
168 | </array> | |
169 | </value> | |
170 | ` | |
171 | ||
172 | func Test_unmarshalExistingArray(t *testing.T) { | |
173 | ||
174 | var ( | |
175 | v1 int | |
176 | v2 bool | |
177 | v3 string | |
178 | ||
179 | v = []interface{}{&v1, &v2, &v3} | |
180 | ) | |
181 | if err := unmarshal([]byte(arrayValueXML), &v); err != nil { | |
182 | t.Fatal(err) | |
183 | } | |
184 | ||
185 | // check pre-existing values | |
186 | if want := 234; v1 != want { | |
187 | t.Fatalf("want %d, got %d", want, v1) | |
188 | } | |
189 | if want := true; v2 != want { | |
190 | t.Fatalf("want %t, got %t", want, v2) | |
191 | } | |
192 | if want := "Hello World"; v3 != want { | |
193 | t.Fatalf("want %s, got %s", want, v3) | |
194 | } | |
195 | // check the appended result | |
196 | if n := len(v); n != 4 { | |
197 | t.Fatalf("missing appended result") | |
198 | } | |
199 | if got, ok := v[3].(string); !ok || got != "Extra Value" { | |
200 | t.Fatalf("got %s, want %s", got, "Extra Value") | |
110 | 201 | } |
111 | 202 | } |
112 | 203 |
4 | 4 | "encoding/xml" |
5 | 5 | "fmt" |
6 | 6 | "reflect" |
7 | "sort" | |
7 | 8 | "strconv" |
9 | "strings" | |
8 | 10 | "time" |
9 | 11 | ) |
12 | ||
13 | // Base64 represents value in base64 encoding | |
14 | type Base64 string | |
10 | 15 | |
11 | 16 | type encodeFunc func(reflect.Value) ([]byte, error) |
12 | 17 | |
50 | 55 | b = []byte(fmt.Sprintf("<i4>%s</i4>", strconv.FormatUint(val.Uint(), 10))) |
51 | 56 | case reflect.Float32, reflect.Float64: |
52 | 57 | b = []byte(fmt.Sprintf("<double>%s</double>", |
53 | strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()))) | |
58 | strconv.FormatFloat(val.Float(), 'f', -1, val.Type().Bits()))) | |
54 | 59 | case reflect.Bool: |
55 | 60 | if val.Bool() { |
56 | 61 | b = []byte("<boolean>1</boolean>") |
78 | 83 | return []byte(fmt.Sprintf("<value>%s</value>", string(b))), nil |
79 | 84 | } |
80 | 85 | |
81 | func encodeStruct(val reflect.Value) ([]byte, error) { | |
86 | func encodeStruct(structVal reflect.Value) ([]byte, error) { | |
82 | 87 | var b bytes.Buffer |
83 | 88 | |
84 | 89 | b.WriteString("<struct>") |
85 | 90 | |
86 | t := val.Type() | |
87 | for i := 0; i < t.NumField(); i++ { | |
88 | b.WriteString("<member>") | |
89 | f := t.Field(i) | |
91 | structType := structVal.Type() | |
92 | for i := 0; i < structType.NumField(); i++ { | |
93 | fieldVal := structVal.Field(i) | |
94 | fieldType := structType.Field(i) | |
90 | 95 | |
91 | name := f.Tag.Get("xmlrpc") | |
96 | name := fieldType.Tag.Get("xmlrpc") | |
97 | // if the tag has the omitempty property, skip it | |
98 | if strings.HasSuffix(name, ",omitempty") && isZero(fieldVal) { | |
99 | continue | |
100 | } | |
101 | name = strings.TrimSuffix(name, ",omitempty") | |
92 | 102 | if name == "" { |
93 | name = f.Name | |
103 | name = fieldType.Name | |
94 | 104 | } |
95 | b.WriteString(fmt.Sprintf("<name>%s</name>", name)) | |
96 | 105 | |
97 | p, err := encodeValue(val.FieldByName(f.Name)) | |
106 | p, err := encodeValue(fieldVal) | |
98 | 107 | if err != nil { |
99 | 108 | return nil, err |
100 | 109 | } |
110 | ||
111 | b.WriteString("<member>") | |
112 | b.WriteString(fmt.Sprintf("<name>%s</name>", name)) | |
101 | 113 | b.Write(p) |
102 | ||
103 | 114 | b.WriteString("</member>") |
104 | 115 | } |
105 | 116 | |
107 | 118 | |
108 | 119 | return b.Bytes(), nil |
109 | 120 | } |
121 | ||
122 | var sortMapKeys bool | |
110 | 123 | |
111 | 124 | func encodeMap(val reflect.Value) ([]byte, error) { |
112 | 125 | var t = val.Type() |
120 | 133 | b.WriteString("<struct>") |
121 | 134 | |
122 | 135 | keys := val.MapKeys() |
136 | ||
137 | if sortMapKeys { | |
138 | sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() }) | |
139 | } | |
123 | 140 | |
124 | 141 | for i := 0; i < val.Len(); i++ { |
125 | 142 | key := keys[i] |
16 | 16 | {false, "<value><boolean>0</boolean></value>"}, |
17 | 17 | {12.134, "<value><double>12.134</double></value>"}, |
18 | 18 | {-12.134, "<value><double>-12.134</double></value>"}, |
19 | {738777323.0, "<value><double>738777323</double></value>"}, | |
19 | 20 | {time.Unix(1386622812, 0).UTC(), "<value><dateTime.iso8601>20131209T21:00:12</dateTime.iso8601></value>"}, |
20 | 21 | {[]interface{}{1, "one"}, "<value><array><data><value><int>1</int></value><value><string>one</string></value></data></array></value>"}, |
21 | 22 | {&struct { |
27 | 28 | }{}, "<value><struct><member><name>value</name><value/></member></struct></value>"}, |
28 | 29 | { |
29 | 30 | map[string]interface{}{"title": "War and Piece", "amount": 20}, |
30 | "<value><struct><member><name>title</name><value><string>War and Piece</string></value></member><member><name>amount</name><value><int>20</int></value></member></struct></value>", | |
31 | "<value><struct><member><name>amount</name><value><int>20</int></value></member><member><name>title</name><value><string>War and Piece</string></value></member></struct></value>", | |
31 | 32 | }, |
32 | 33 | { |
33 | 34 | map[string]interface{}{ |
35 | 36 | "Age": 6, |
36 | 37 | "Wight": []float32{66.67, 100.5}, |
37 | 38 | "Dates": map[string]interface{}{"Birth": time.Date(1829, time.November, 10, 23, 0, 0, 0, time.UTC), "Death": time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}}, |
38 | "<value><struct><member><name>Name</name><value><string>John Smith</string></value></member><member><name>Age</name><value><int>6</int></value></member><member><name>Wight</name><value><array><data><value><double>66.67</double></value><value><double>100.5</double></value></data></array></value></member><member><name>Dates</name><value><struct><member><name>Birth</name><value><dateTime.iso8601>18291110T23:00:00</dateTime.iso8601></value></member><member><name>Death</name><value><dateTime.iso8601>20091110T23:00:00</dateTime.iso8601></value></member></struct></value></member></struct></value>", | |
39 | "<value><struct><member><name>Age</name><value><int>6</int></value></member><member><name>Dates</name><value><struct><member><name>Birth</name><value><dateTime.iso8601>18291110T23:00:00</dateTime.iso8601></value></member><member><name>Death</name><value><dateTime.iso8601>20091110T23:00:00</dateTime.iso8601></value></member></struct></value></member><member><name>Name</name><value><string>John Smith</string></value></member><member><name>Wight</name><value><array><data><value><double>66.67</double></value><value><double>100.5</double></value></data></array></value></member></struct></value>", | |
39 | 40 | }, |
41 | {&struct { | |
42 | Title string | |
43 | Amount int | |
44 | Author string `xmlrpc:"author,omitempty"` | |
45 | }{ | |
46 | Title: "War and Piece", Amount: 20, | |
47 | }, "<value><struct><member><name>Title</name><value><string>War and Piece</string></value></member><member><name>Amount</name><value><int>20</int></value></member></struct></value>"}, | |
48 | {&struct { | |
49 | Title string | |
50 | Amount int | |
51 | Author string `xmlrpc:"author,omitempty"` | |
52 | }{ | |
53 | Title: "War and Piece", Amount: 20, Author: "Leo Tolstoy", | |
54 | }, "<value><struct><member><name>Title</name><value><string>War and Piece</string></value></member><member><name>Amount</name><value><int>20</int></value></member><member><name>author</name><value><string>Leo Tolstoy</string></value></member></struct></value>"}, | |
55 | {&struct { | |
56 | }{}, "<value><struct></struct></value>"}, | |
40 | 57 | } |
41 | 58 | |
42 | 59 | func Test_marshal(t *testing.T) { |
60 | sortMapKeys = true | |
61 | ||
43 | 62 | for _, tt := range marshalTests { |
44 | 63 | b, err := marshal(tt.value) |
45 | 64 | if err != nil { |
0 | package xmlrpc | |
1 | ||
2 | import ( | |
3 | "math" | |
4 | . "reflect" | |
5 | ) | |
6 | ||
7 | func isZero(v Value) bool { | |
8 | switch v.Kind() { | |
9 | case Bool: | |
10 | return !v.Bool() | |
11 | case Int, Int8, Int16, Int32, Int64: | |
12 | return v.Int() == 0 | |
13 | case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: | |
14 | return v.Uint() == 0 | |
15 | case Float32, Float64: | |
16 | return math.Float64bits(v.Float()) == 0 | |
17 | case Complex64, Complex128: | |
18 | c := v.Complex() | |
19 | return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 | |
20 | case Array: | |
21 | for i := 0; i < v.Len(); i++ { | |
22 | if !isZero(v.Index(i)) { | |
23 | return false | |
24 | } | |
25 | } | |
26 | return true | |
27 | case Chan, Func, Interface, Map, Ptr, Slice, UnsafePointer: | |
28 | return v.IsNil() | |
29 | case String: | |
30 | return v.Len() == 0 | |
31 | case Struct: | |
32 | for i := 0; i < v.NumField(); i++ { | |
33 | if !isZero(v.Field(i)) { | |
34 | return false | |
35 | } | |
36 | } | |
37 | return true | |
38 | default: | |
39 | // This should never happens, but will act as a safeguard for | |
40 | // later, as a default value doesn't makes sense here. | |
41 | panic(&ValueError{"reflect.Value.IsZero", v.Kind()}) | |
42 | } | |
43 | } |
0 | 0 | package xmlrpc |
1 | 1 | |
2 | 2 | import ( |
3 | "fmt" | |
3 | 4 | "regexp" |
4 | 5 | ) |
5 | 6 | |
7 | 8 | faultRx = regexp.MustCompile(`<fault>(\s|\S)+</fault>`) |
8 | 9 | ) |
9 | 10 | |
10 | type failedResponse struct { | |
11 | Code int `xmlrpc:"faultCode"` | |
12 | Error string `xmlrpc:"faultString"` | |
11 | // FaultError is returned from the server when an invalid call is made | |
12 | type FaultError struct { | |
13 | Code int `xmlrpc:"faultCode"` | |
14 | String string `xmlrpc:"faultString"` | |
13 | 15 | } |
14 | 16 | |
15 | func (r *failedResponse) err() error { | |
16 | return &xmlrpcError{ | |
17 | code: r.Code, | |
18 | err: r.Error, | |
19 | } | |
17 | // Error implements the error interface | |
18 | func (e FaultError) Error() string { | |
19 | return fmt.Sprintf("Fault(%d): %s", e.Code, e.String) | |
20 | 20 | } |
21 | 21 | |
22 | type Response struct { | |
23 | data []byte | |
22 | type Response []byte | |
23 | ||
24 | func (r Response) Err() error { | |
25 | if !faultRx.Match(r) { | |
26 | return nil | |
27 | } | |
28 | var fault FaultError | |
29 | if err := unmarshal(r, &fault); err != nil { | |
30 | return err | |
31 | } | |
32 | return fault | |
24 | 33 | } |
25 | 34 | |
26 | func NewResponse(data []byte) *Response { | |
27 | return &Response{ | |
28 | data: data, | |
29 | } | |
30 | } | |
31 | ||
32 | func (r *Response) Failed() bool { | |
33 | return faultRx.Match(r.data) | |
34 | } | |
35 | ||
36 | func (r *Response) Err() error { | |
37 | failedResp := new(failedResponse) | |
38 | if err := unmarshal(r.data, failedResp); err != nil { | |
39 | return err | |
40 | } | |
41 | ||
42 | return failedResp.err() | |
43 | } | |
44 | ||
45 | func (r *Response) Unmarshal(v interface{}) error { | |
46 | if err := unmarshal(r.data, v); err != nil { | |
35 | func (r Response) Unmarshal(v interface{}) error { | |
36 | if err := unmarshal(r, v); err != nil { | |
47 | 37 | return err |
48 | 38 | } |
49 | 39 |
27 | 27 | </methodResponse>` |
28 | 28 | |
29 | 29 | func Test_failedResponse(t *testing.T) { |
30 | resp := NewResponse([]byte(faultRespXml)) | |
31 | ||
32 | if !resp.Failed() { | |
33 | t.Fatal("Failed() error: expected true, got false") | |
34 | } | |
30 | resp := Response([]byte(faultRespXml)) | |
35 | 31 | |
36 | 32 | if resp.Err() == nil { |
37 | 33 | t.Fatal("Err() error: expected error, got nil") |
38 | 34 | } |
39 | 35 | |
40 | err := resp.Err().(*xmlrpcError) | |
41 | if err.code != 410 && err.err != "You must log in before using this part of Bugzilla." { | |
36 | fault := resp.Err().(FaultError) | |
37 | if fault.Code != 410 && fault.String != "You must log in before using this part of Bugzilla." { | |
42 | 38 | t.Fatal("Err() error: got wrong error") |
43 | 39 | } |
44 | 40 | } |
64 | 60 | </params> |
65 | 61 | </methodResponse>` |
66 | 62 | |
63 | func Test_responseWithEmptyValue(t *testing.T) { | |
64 | resp := Response([]byte(emptyValResp)) | |
67 | 65 | |
68 | func Test_responseWithEmptyValue(t *testing.T) { | |
69 | resp := NewResponse([]byte(emptyValResp)) | |
70 | ||
71 | result := struct{ | |
72 | User string `xmlrpc:"user"` | |
66 | result := struct { | |
67 | User string `xmlrpc:"user"` | |
73 | 68 | Token string `xmlrpc:"token"` |
74 | 69 | }{} |
75 | 70 |
14 | 14 | x + y |
15 | 15 | end |
16 | 16 | |
17 | def error | |
18 | raise XMLRPC::FaultException.new(500, "Server error") | |
19 | end | |
17 | def error | |
18 | raise XMLRPC::FaultException.new(500, "Server error") | |
19 | end | |
20 | 20 | end |
21 | 21 | |
22 | 22 | server = XMLRPC::Server.new 5001, 'localhost' |
0 | package xmlrpc | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | ) | |
5 | ||
6 | // xmlrpcError represents errors returned on xmlrpc request. | |
7 | type xmlrpcError struct { | |
8 | code int | |
9 | err string | |
10 | } | |
11 | ||
12 | // Error() method implements Error interface | |
13 | func (e *xmlrpcError) Error() string { | |
14 | return fmt.Sprintf("error: \"%s\" code: %d", e.err, e.code) | |
15 | } | |
16 | ||
17 | // Base64 represents value in base64 encoding | |
18 | type Base64 string |