From 55a9e709990b22622c84384063290430b8057afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Gul=C3=A1csi?= Date: Fri, 18 Dec 2015 17:10:16 +0100 Subject: [PATCH 1/2] Rewrite to use io.Reader and io.Writer Helps with big responses. For backward compatibility, EncodeClientRequest does not change, just introduce a new EncodeClientRequestW(io.Writer, string, interface{}) error function. --- xml/client.go | 23 +++--- xml/fault.go | 12 +-- xml/pool.go | 26 ++++++ xml/rpc2xml.go | 197 ++++++++++++++++++++++++++++++-------------- xml/rpc2xml_test.go | 22 +++-- xml/server.go | 14 ++-- xml/xml2rpc.go | 28 ++++--- xml/xml2rpc_test.go | 13 +-- xml/xml_test.go | 26 +++--- 9 files changed, 237 insertions(+), 124 deletions(-) create mode 100644 xml/pool.go diff --git a/xml/client.go b/xml/client.go index d8cb0a4..3ea04aa 100644 --- a/xml/client.go +++ b/xml/client.go @@ -4,23 +4,24 @@ package xml -import ( - "io" - "io/ioutil" -) +import "io" // EncodeClientRequest encodes parameters for a XML-RPC client request. func EncodeClientRequest(method string, args interface{}) ([]byte, error) { - xml, err := rpcRequest2XML(method, args) - return []byte(xml), err + buf := bufPool.Get() + defer bufPool.Put(buf) + err := EncodeClientRequestW(buf, method, args) + return buf.Bytes(), err +} + +// EncodeClientRequestW encodes parameters for an XML-RPC client request +// into the provided io.Writer. +func EncodeClientRequestW(w io.Writer, method string, args interface{}) error { + return rpcRequest2XML(w, method, args) } // DecodeClientResponse decodes the response body of a client request into // the interface reply. func DecodeClientResponse(r io.Reader, reply interface{}) error { - rawxml, err := ioutil.ReadAll(r) - if err != nil { - return FaultSystemError - } - return xml2RPC(string(rawxml), reply) + return xml2RPC(r, reply) } diff --git a/xml/fault.go b/xml/fault.go index f241353..e389675 100644 --- a/xml/fault.go +++ b/xml/fault.go @@ -6,6 +6,7 @@ package xml import ( "fmt" + "io" ) // Default Faults @@ -33,11 +34,12 @@ func (f Fault) Error() string { // Fault2XML is a quick 'marshalling' replacemnt for the Fault case. func fault2XML(fault Fault) string { - buffer := "" - xml, _ := rpc2XML(fault) - buffer += xml - buffer += "" - return buffer + buf := bufPool.Get() + defer bufPool.Put(buf) + io.WriteString(buf, "") + _ = rpc2XML(buf, fault) + io.WriteString(buf, "") + return buf.String() } type faultValue struct { diff --git a/xml/pool.go b/xml/pool.go new file mode 100644 index 0000000..d51b94d --- /dev/null +++ b/xml/pool.go @@ -0,0 +1,26 @@ +// Copyright 2015 Tamás Gulácsi +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xml + +import ( + "bytes" + "sync" +) + +var bufPool = &bufferPool{ + Pool: sync.Pool{New: func() interface{} { return bytes.NewBuffer(make([]byte, 0, 1024)) }}, +} + +type bufferPool struct { + sync.Pool +} + +func (p *bufferPool) Get() *bytes.Buffer { + return p.Pool.Get().(*bytes.Buffer) +} +func (p *bufferPool) Put(b *bytes.Buffer) { + b.Reset() + p.Pool.Put(b) +} diff --git a/xml/rpc2xml.go b/xml/rpc2xml.go index f073847..42ebde1 100644 --- a/xml/rpc2xml.go +++ b/xml/rpc2xml.go @@ -7,96 +7,118 @@ package xml import ( "encoding/base64" "fmt" + "io" "reflect" "strings" "time" ) -func rpcRequest2XML(method string, rpc interface{}) (string, error) { - buffer := "" - buffer += method - buffer += "" - params, err := rpcParams2XML(rpc) - buffer += params - buffer += "" - return buffer, err +func rpcRequest2XML(w io.Writer, method string, rpc interface{}) error { + ew := NewErrWriter(w) + fmt.Fprintf(ew, "%s", method) + err := rpcParams2XML(ew, rpc) + io.WriteString(ew, "") + if err != nil { + return err + } + return ew.Err() } -func rpcResponse2XML(rpc interface{}) (string, error) { - buffer := "" - params, err := rpcParams2XML(rpc) - buffer += params - buffer += "" - return buffer, err +func rpcResponse2XML(w io.Writer, rpc interface{}) error { + ew := NewErrWriter(w) + io.WriteString(ew, "") + err := rpcParams2XML(ew, rpc) + io.WriteString(ew, "") + if err != nil { + return err + } + return ew.Err() } -func rpcParams2XML(rpc interface{}) (string, error) { +func rpcParams2XML(w io.Writer, rpc interface{}) error { + ew := NewErrWriter(w) + io.WriteString(ew, "") var err error - buffer := "" for i := 0; i < reflect.ValueOf(rpc).Elem().NumField(); i++ { - var xml string - buffer += "" - xml, err = rpc2XML(reflect.ValueOf(rpc).Elem().Field(i).Interface()) - buffer += xml - buffer += "" + io.WriteString(ew, "") + err = rpc2XML(ew, reflect.ValueOf(rpc).Elem().Field(i).Interface()) + io.WriteString(ew, "") + if err != nil { + break + } + } + io.WriteString(ew, "") + if err != nil { + return err } - buffer += "" - return buffer, err + return ew.Err() } -func rpc2XML(value interface{}) (string, error) { - out := "" +func rpc2XML(w io.Writer, value interface{}) error { + ew := NewErrWriter(w) + io.WriteString(ew, "") + var err error switch reflect.ValueOf(value).Kind() { case reflect.Int: - out += fmt.Sprintf("%d", value.(int)) + fmt.Fprintf(ew, "%d", value.(int)) case reflect.Float64: - out += fmt.Sprintf("%f", value.(float64)) + fmt.Fprintf(ew, "%f", value.(float64)) case reflect.String: - out += string2XML(value.(string)) + err = string2XML(ew, value.(string)) case reflect.Bool: - out += bool2XML(value.(bool)) + err = bool2XML(ew, value.(bool)) case reflect.Struct: if reflect.TypeOf(value).String() != "time.Time" { - out += struct2XML(value) + err = struct2XML(ew, value) } else { - out += time2XML(value.(time.Time)) + err = time2XML(ew, value.(time.Time)) } case reflect.Slice, reflect.Array: // FIXME: is it the best way to recognize '[]byte'? if reflect.TypeOf(value).String() != "[]uint8" { - out += array2XML(value) + err = array2XML(ew, value) } else { - out += base642XML(value.([]byte)) + err = base642XML(ew, value.([]byte)) } case reflect.Ptr: if reflect.ValueOf(value).IsNil() { - out += "" + io.WriteString(ew, "") } } - out += "" - return out, nil + io.WriteString(ew, "") + if err != nil { + return err + } + return ew.Err() } -func bool2XML(value bool) string { +func bool2XML(w io.Writer, value bool) error { var b string if value { b = "1" } else { b = "0" } - return fmt.Sprintf("%s", b) + _, err := fmt.Fprintf(w, "%s", b) + return err } -func string2XML(value string) string { - value = strings.Replace(value, "&", "&", -1) - value = strings.Replace(value, "\"", """, -1) - value = strings.Replace(value, "<", "<", -1) - value = strings.Replace(value, ">", ">", -1) - return fmt.Sprintf("%s", value) +var strRepl = strings.NewReplacer( + "&", "&", + `"`, """, + "<", "<", + ">", ">", +) + +func string2XML(w io.Writer, value string) error { + _, err := fmt.Fprintf(w, "%s", strRepl.Replace(value)) + return err } -func struct2XML(value interface{}) (out string) { - out += "" +func struct2XML(w io.Writer, value interface{}) error { + ew := NewErrWriter(w) + io.WriteString(ew, "") + var err error for i := 0; i < reflect.TypeOf(value).NumField(); i++ { field := reflect.ValueOf(value).Field(i) field_type := reflect.TypeOf(value).Field(i) @@ -106,25 +128,37 @@ func struct2XML(value interface{}) (out string) { } else { name = field_type.Name } - field_value, _ := rpc2XML(field.Interface()) - field_name := fmt.Sprintf("%s", name) - out += fmt.Sprintf("%s%s", field_name, field_value) + fmt.Fprintf(ew, "%s", name) + err = rpc2XML(ew, field.Interface()) + io.WriteString(ew, "") + if err != nil { + break + } + } + io.WriteString(ew, "") + if err != nil { + return err } - out += "" - return + return ew.Err() } -func array2XML(value interface{}) (out string) { - out += "" +func array2XML(w io.Writer, value interface{}) error { + ew := NewErrWriter(w) + io.WriteString(ew, "") + var err error for i := 0; i < reflect.ValueOf(value).Len(); i++ { - item_xml, _ := rpc2XML(reflect.ValueOf(value).Index(i).Interface()) - out += item_xml + if err = rpc2XML(ew, reflect.ValueOf(value).Index(i).Interface()); err != nil { + break + } + } + io.WriteString(ew, "") + if err != nil { + return err } - out += "" - return + return ew.Err() } -func time2XML(t time.Time) string { +func time2XML(w io.Writer, t time.Time) error { /* // TODO: find out whether we need to deal // here with TZ @@ -136,12 +170,51 @@ func time2XML(t time.Time) string { tz = fmt.Sprintf("%03d00", offset / 3600 ) } */ - return fmt.Sprintf("%04d%02d%02dT%02d:%02d:%02d", + _, err := fmt.Fprintf(w, "%04d%02d%02dT%02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) + return err +} + +func base642XML(w io.Writer, data []byte) error { + ew := NewErrWriter(w) + _, _ = io.WriteString(ew, "") + bw := base64.NewEncoder(base64.StdEncoding, ew) + bw.Write(data) + err := bw.Close() + io.WriteString(ew, "") + if err != nil { + return err + } + return ew.Err() +} + +var _ = io.Writer((*errWriter)(nil)) + +type errWriter struct { + w io.Writer + err error +} + +func NewErrWriter(w io.Writer) *errWriter { + if w == nil { + return nil + } + if ew, ok := w.(*errWriter); ok { + return ew + } + return &errWriter{w: w} +} + +func (ew *errWriter) Write(p []byte) (int, error) { + if ew.err != nil { + return 0, ew.err + } + var n int + n, ew.err = ew.w.Write(p) + return n, ew.err } -func base642XML(data []byte) string { - str := base64.StdEncoding.EncodeToString(data) - return fmt.Sprintf("%s", str) +func (ew *errWriter) Err() error { + return ew.err } diff --git a/xml/rpc2xml_test.go b/xml/rpc2xml_test.go index 2904b39..a28a54f 100644 --- a/xml/rpc2xml_test.go +++ b/xml/rpc2xml_test.go @@ -27,15 +27,15 @@ type StructRpc2Xml struct { func TestRPC2XML(t *testing.T) { req := &StructRpc2Xml{123, 3.145926, "Hello, World!", false, SubStructRpc2Xml{42, "I'm Bar", []int{1, 2, 3}}, time.Date(2012, time.July, 17, 14, 8, 55, 0, time.Local), []byte("you can't read this!")} - xml, err := rpcRequest2XML("Some.Method", req) + buf := bufPool.Get() + defer bufPool.Put(buf) + err := rpcRequest2XML(buf, "Some.Method", req) if err != nil { t.Error("RPC2XML conversion failed", err) } expected := "Some.Method1233.145926Hello, World!0Foo42BarI'm BarData12320120717T14:08:55eW91IGNhbid0IHJlYWQgdGhpcyE=" - if xml != expected { - t.Error("RPC2XML conversion failed") - t.Error("Expected", expected) - t.Error("Got", xml) + if xml := buf.String(); xml != expected { + t.Errorf("RPC2XML conversion failed:\n\tExpected\n%s\n\tgot\n%s", expected, xml) } } @@ -45,12 +45,14 @@ type StructSpecialCharsRpc2Xml struct { func TestRPC2XMLSpecialChars(t *testing.T) { req := &StructSpecialCharsRpc2Xml{" & \" < > "} - xml, err := rpcResponse2XML(req) + buf := bufPool.Get() + defer bufPool.Put(buf) + err := rpcResponse2XML(buf, req) if err != nil { t.Error("RPC2XML conversion failed", err) } expected := " & " < > " - if xml != expected { + if xml := buf.String(); xml != expected { t.Error("RPC2XML Special chars conversion failed") t.Error("Expected", expected) t.Error("Got", xml) @@ -63,12 +65,14 @@ type StructNilRpc2Xml struct { func TestRpc2XmlNil(t *testing.T) { req := &StructNilRpc2Xml{nil} - xml, err := rpcResponse2XML(req) + buf := bufPool.Get() + defer bufPool.Put(buf) + err := rpcResponse2XML(buf, req) if err != nil { t.Error("RPC2XML conversion failed", err) } expected := "" - if xml != expected { + if xml := buf.String(); xml != expected { t.Error("RPC2XML Special chars conversion failed") t.Error("Expected", expected) t.Error("Got", xml) diff --git a/xml/server.go b/xml/server.go index 733b936..6ea2482 100644 --- a/xml/server.go +++ b/xml/server.go @@ -7,8 +7,10 @@ package xml import ( "encoding/xml" "fmt" + "io" "io/ioutil" "net/http" + "strings" "github.com/gorilla/rpc" ) @@ -84,7 +86,7 @@ func (c *CodecRequest) Method() (string, error) { // args is the pointer to the Service.Args structure // it gets populated from temporary XML structure func (c *CodecRequest) ReadRequest(args interface{}) error { - c.err = xml2RPC(c.request.rawxml, args) + c.err = xml2RPC(strings.NewReader(c.request.rawxml), args) return nil } @@ -93,7 +95,6 @@ func (c *CodecRequest) ReadRequest(args interface{}) error { // response is the pointer to the Service.Response structure // it gets encoded into the XML-RPC xml string func (c *CodecRequest) WriteResponse(w http.ResponseWriter, response interface{}, methodErr error) error { - var xmlstr string if c.err != nil { var fault Fault switch c.err.(type) { @@ -103,12 +104,11 @@ func (c *CodecRequest) WriteResponse(w http.ResponseWriter, response interface{} fault = FaultApplicationError fault.String += fmt.Sprintf(": %v", c.err) } - xmlstr = fault2XML(fault) - } else { - xmlstr, _ = rpcResponse2XML(response) + w.Header().Set("Content-Type", "text/xml; charset=utf-8") + io.WriteString(w, fault2XML(fault)) + return nil } w.Header().Set("Content-Type", "text/xml; charset=utf-8") - w.Write([]byte(xmlstr)) - return nil + return rpcResponse2XML(w, response) } diff --git a/xml/xml2rpc.go b/xml/xml2rpc.go index b0d798d..8bb2df0 100644 --- a/xml/xml2rpc.go +++ b/xml/xml2rpc.go @@ -5,18 +5,19 @@ package xml import ( - "bytes" "encoding/base64" "encoding/xml" "fmt" + "io" "reflect" "strconv" "time" "unicode" "unicode/utf8" - "code.google.com/p/go-charset/charset" - _ "code.google.com/p/go-charset/data" + "gopkg.in/errgo.v1" + + "golang.org/x/net/html/charset" ) // Types used for unmarshalling @@ -48,11 +49,13 @@ type member struct { Value value `xml:"value"` } -func xml2RPC(xmlraw string, rpc interface{}) error { +func xml2RPC(r io.Reader, rpc interface{}) error { // Unmarshal raw XML into the temporal structure var ret response - decoder := xml.NewDecoder(bytes.NewReader([]byte(xmlraw))) - decoder.CharsetReader = charset.NewReader + decoder := xml.NewDecoder(r) + decoder.CharsetReader = func(enc string, r io.Reader) (io.Reader, error) { + return charset.NewReader(r, enc) + } err := decoder.Decode(&ret) if err != nil { return FaultDecode @@ -103,7 +106,7 @@ func getFaultResponse(fault faultValue) Fault { func value2Field(value value, field *reflect.Value) error { if !field.CanSet() { - return FaultApplicationError + return errgo.Notef(FaultApplicationError, "%#v [%T] is not setable", field, field) } var ( @@ -129,7 +132,7 @@ func value2Field(value value, field *reflect.Value) error { case len(value.Struct) != 0: if field.Kind() != reflect.Struct { fault := FaultInvalidParams - fault.String += fmt.Sprintf("structure fields mismatch: %s != %s", field.Kind(), reflect.Struct.String()) + fault.String += fmt.Sprintf(": structure fields mismatch: %s != %s", field.Kind(), reflect.Struct.String()) return fault } s := value.Struct @@ -143,7 +146,7 @@ func value2Field(value value, field *reflect.Value) error { case len(value.Array) != 0: a := value.Array f := *field - slice := reflect.MakeSlice(reflect.TypeOf(f.Interface()), + slice := reflect.MakeSlice(f.Type(), //reflect.TypeOf(f.Interface()), len(a), len(a)) for i := 0; i < len(a); i++ { item := slice.Index(i) @@ -161,11 +164,10 @@ func value2Field(value value, field *reflect.Value) error { } if val != nil { - if reflect.TypeOf(val) != reflect.TypeOf(field.Interface()) { + rv := reflect.ValueOf(val) + if !rv.Type().AssignableTo(field.Type()) { fault := FaultInvalidParams - fault.String += fmt.Sprintf(": fields type mismatch: %s != %s", - reflect.TypeOf(val), - reflect.TypeOf(field.Interface())) + fault.String += fmt.Sprintf(": fields type mismatch: %s != %s", reflect.TypeOf(val), field.Type()) return fault } diff --git a/xml/xml2rpc_test.go b/xml/xml2rpc_test.go index 751dad6..f60f64b 100644 --- a/xml/xml2rpc_test.go +++ b/xml/xml2rpc_test.go @@ -6,6 +6,7 @@ package xml import ( "reflect" + "strings" "testing" "time" ) @@ -28,7 +29,7 @@ type StructXml2Rpc struct { func TestXML2RPC(t *testing.T) { req := new(StructXml2Rpc) - err := xml2RPC("Some.Method1233.145926Hello, World!0Foo42BarI'm BarData12320120717T14:08:55eW91IGNhbid0IHJlYWQgdGhpcyE=", req) + err := xml2RPC(strings.NewReader("Some.Method1233.145926Hello, World!0Foo42BarI'm BarData12320120717T14:08:55eW91IGNhbid0IHJlYWQgdGhpcyE="), req) if err != nil { t.Error("XML2RPC conversion failed", err) } @@ -46,7 +47,7 @@ type StructSpecialCharsXml2Rpc struct { func TestXML2RPCSpecialChars(t *testing.T) { req := new(StructSpecialCharsXml2Rpc) - err := xml2RPC(" & " < > ", req) + err := xml2RPC(strings.NewReader(" & " < > "), req) if err != nil { t.Error("XML2RPC conversion failed", err) } @@ -64,7 +65,7 @@ type StructNilXml2Rpc struct { func TestXML2RPCNil(t *testing.T) { req := new(StructNilXml2Rpc) - err := xml2RPC("", req) + err := xml2RPC(strings.NewReader(""), req) if err != nil { t.Error("XML2RPC conversion failed", err) } @@ -88,7 +89,7 @@ type StructXml2RpcHelloArgs struct { func TestXML2RPCLowercasedMethods(t *testing.T) { req := new(StructXml2RpcHelloArgs) - err := xml2RPC("string1I'm a first stringstring2I'm a second stringid1", req) + err := xml2RPC(strings.NewReader("string1I'm a first stringstring2I'm a second stringid1"), req) if err != nil { t.Error("XML2RPC conversion failed", err) } @@ -113,7 +114,7 @@ Requiredattribute'user'notfound: [{'User',"gggg"},{'Host',"sss.com"},{'Password',"ssddfsdf"}] ` - err := xml2RPC(data, req) + err := xml2RPC(strings.NewReader(data), req) fault, ok := err.(Fault) if !ok { @@ -140,7 +141,7 @@ Requiredattribute'user'notfound: [{'User',"Öñä"},{'Host',"sss.com"},{'Password',"ssddfsdf"}] ` - err := xml2RPC(data, req) + err := xml2RPC(strings.NewReader(data), req) fault, ok := err.(Fault) if !ok { diff --git a/xml/xml_test.go b/xml/xml_test.go index d391224..3bf021a 100644 --- a/xml/xml_test.go +++ b/xml/xml_test.go @@ -116,9 +116,8 @@ func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) e t.Fatal("Expected to be registered:", method) } - buf, _ := EncodeClientRequest(method, req) - body := bytes.NewBuffer(buf) - r, _ := http.NewRequest("POST", "http://localhost:8080/", body) + b, _ := EncodeClientRequest(method, req) + r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewReader(b)) r.Header.Set("Content-Type", "text/xml") w := httptest.NewRecorder() @@ -129,26 +128,29 @@ func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) e func TestRPC2XMLConverter(t *testing.T) { req := &Service1Request{4, 2} - xml, err := rpcRequest2XML("Some.Method", req) + buf := bufPool.Get() + defer bufPool.Put(buf) + err := rpcRequest2XML(buf, "Some.Method", req) if err != nil { t.Error("RPC2XML conversion failed", err) } expected := "Some.Method42" - if xml != expected { + if xml := buf.String(); xml != expected { t.Error("RPC2XML conversion failed") t.Error("Expected", expected) t.Error("Got", xml) } req2 := &Service2Request{"Johnny", 33, true} - xml, err = rpcRequest2XML("Some.Method", req2) + buf.Reset() + err = rpcRequest2XML(buf, "Some.Method", req2) if err != nil { t.Error("RPC2XML conversion failed", err) } expected = "Some.MethodJohnny331" - if xml != expected { + if xml := buf.String(); xml != expected { t.Error("RPC2XML conversion failed") t.Error("Expected", expected) t.Error("Got", xml) @@ -157,26 +159,28 @@ func TestRPC2XMLConverter(t *testing.T) { address := Address{221, "Baker str.", "London"} person := Person{"Johnny", "Doe", 33, address} req3 := &Service3Request{person} - xml, err = rpcRequest2XML("Some.Method", req3) + buf.Reset() + err = rpcRequest2XML(buf, "Some.Method", req3) if err != nil { t.Error("RPC2XML conversion failed", err) } expected = "Some.MethodNameJohnnySurnameDoeAge33AddressNumber221StreetBaker str.CountryLondon" - if xml != expected { + if xml := buf.String(); xml != expected { t.Error("RPC2XML conversion failed") t.Error("Expected", expected) t.Error("Got", xml) } res := &Service1Response{42} - xml, err = rpcResponse2XML(res) + buf.Reset() + err = rpcResponse2XML(buf, res) if err != nil { t.Error("RPC2XML conversion failed", err) } expected = "42" - if xml != expected { + if xml := buf.String(); xml != expected { t.Error("RPC2XML conversion failed") t.Error("Expected", expected) t.Error("Got", xml) From 7d28d52c193b870e8633953471ea04d097b05cc5 Mon Sep 17 00:00:00 2001 From: Tamas Gulacsi Date: Wed, 15 Jun 2016 19:25:37 +0200 Subject: [PATCH 2/2] add map[string]interface{} --- xml/rpc2xml.go | 42 ++++++++++++++++++++++++++++++++++++------ xml/xml2rpc.go | 37 ++++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/xml/rpc2xml.go b/xml/rpc2xml.go index 42ebde1..adeb1e1 100644 --- a/xml/rpc2xml.go +++ b/xml/rpc2xml.go @@ -6,6 +6,7 @@ package xml import ( "encoding/base64" + "encoding/xml" "fmt" "io" "reflect" @@ -39,12 +40,28 @@ func rpcParams2XML(w io.Writer, rpc interface{}) error { ew := NewErrWriter(w) io.WriteString(ew, "") var err error - for i := 0; i < reflect.ValueOf(rpc).Elem().NumField(); i++ { - io.WriteString(ew, "") - err = rpc2XML(ew, reflect.ValueOf(rpc).Elem().Field(i).Interface()) - io.WriteString(ew, "") - if err != nil { - break + if m, ok := rpc.(map[string]interface{}); ok { + io.WriteString(w, "") + for k, v := range m { + fmt.Fprintf(w, "") + xml.EscapeText(w, []byte(k)) + fmt.Fprintf(w, "") + err = rpc2XML(w, v) + io.WriteString(w, "") + if err != nil { + break + } + } + io.WriteString(w, "") + + } else { + for i := 0; i < reflect.ValueOf(rpc).Elem().NumField(); i++ { + io.WriteString(ew, "") + err = rpc2XML(ew, reflect.ValueOf(rpc).Elem().Field(i).Interface()) + io.WriteString(ew, "") + if err != nil { + break + } } } io.WriteString(ew, "") @@ -73,6 +90,19 @@ func rpc2XML(w io.Writer, value interface{}) error { } else { err = time2XML(ew, value.(time.Time)) } + case reflect.Map: + fmt.Fprintf(ew, "") + for k, v := range value.(map[string]interface{}) { + fmt.Fprintf(ew, "") + xml.EscapeText(ew, []byte(k)) + fmt.Fprintf(ew, "") + err = rpc2XML(ew, v) + fmt.Fprintf(ew, "") + if err != nil { + break + } + } + fmt.Fprintf(ew, "") case reflect.Slice, reflect.Array: // FIXME: is it the best way to recognize '[]byte'? if reflect.TypeOf(value).String() != "[]uint8" { diff --git a/xml/xml2rpc.go b/xml/xml2rpc.go index cfb9365..394d00a 100644 --- a/xml/xml2rpc.go +++ b/xml/xml2rpc.go @@ -64,18 +64,33 @@ func xml2RPC(r io.Reader, rpc interface{}) error { return getFaultResponse(ret.Fault) } - // Structures should have equal number of fields - if reflect.TypeOf(rpc).Elem().NumField() != len(ret.Params) { - return FaultWrongArgumentsNumber - } + if len(ret.Params) == 1 { + if m, ok := rpc.(map[string]interface{}); ok { + for _, member := range ret.Params[0].Value.Struct { + var field interface{} + rv := reflect.ValueOf(&field) + err = value2Field(member.Value, &rv) + m[member.Name] = rv.Interface() + if err != nil { + return err + } + } + return nil + } + } else { + // Structures should have equal number of fields + if reflect.TypeOf(rpc).Elem().NumField() != len(ret.Params) { + return FaultWrongArgumentsNumber + } - // Now, convert temporal structure into the - // passed rpc variable, according to it's structure - for i, param := range ret.Params { - field := reflect.ValueOf(rpc).Elem().Field(i) - err = value2Field(param.Value, &field) - if err != nil { - return err + // Now, convert temporal structure into the + // passed rpc variable, according to it's structure + for i, param := range ret.Params { + field := reflect.ValueOf(rpc).Elem().Field(i) + err = value2Field(param.Value, &field) + if err != nil { + return err + } } }