166 lines
3.3 KiB
Go
Executable File
166 lines
3.3 KiB
Go
Executable File
package eaclient
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"unicode"
|
|
|
|
"github.com/YoshihikoAbe/avsproperty"
|
|
)
|
|
|
|
type clientError string
|
|
|
|
func (err clientError) Error() string {
|
|
return "eaclient: " + string(err)
|
|
}
|
|
|
|
type CompressType int
|
|
|
|
const (
|
|
CompressDisable CompressType = iota
|
|
CompressNone
|
|
CompressLZ
|
|
)
|
|
|
|
func (c *CompressType) UnmarshalText(b []byte) error {
|
|
switch s := string(bytes.ToLower(b)); s {
|
|
case "":
|
|
fallthrough
|
|
case "disable":
|
|
*c = CompressDisable
|
|
|
|
case "none":
|
|
*c = CompressNone
|
|
|
|
case "lz":
|
|
fallthrough
|
|
case "lz77":
|
|
*c = CompressLZ
|
|
|
|
default:
|
|
return clientError("invalid compress type: " + s)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type FormatType int
|
|
|
|
const (
|
|
FormatBinary FormatType = iota
|
|
FormatXML
|
|
)
|
|
|
|
func (f *FormatType) UnmarshalText(b []byte) error {
|
|
switch s := string(bytes.ToLower(b)); s {
|
|
case "":
|
|
fallthrough
|
|
case "binary":
|
|
*f = FormatBinary
|
|
|
|
case "xml":
|
|
*f = FormatXML
|
|
|
|
default:
|
|
return clientError("invalid format type: " + s)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Service struct {
|
|
URL string `yaml:"url"`
|
|
Host string `yaml:"host"`
|
|
Obfuscate bool `yaml:"obfuscate"`
|
|
Compress CompressType `yaml:"compress"`
|
|
Format FormatType `yaml:"format"`
|
|
Encoding string `yaml:"encoding"`
|
|
}
|
|
|
|
type Client struct {
|
|
Model string `yaml:"model"`
|
|
Srcid string `yaml:"srcid"`
|
|
UserAgent string `yaml:"useragent"`
|
|
DisableQuery bool `yaml:"disable_query"`
|
|
HTTP http.Client `yaml:"-"`
|
|
}
|
|
|
|
func (client *Client) Send(svc Service, call *avsproperty.Node) (*avsproperty.Property, error) {
|
|
if !validModel(client.Model) {
|
|
return nil, clientError("invalid character in model")
|
|
}
|
|
|
|
if call == nil {
|
|
return nil, clientError("call node is nil")
|
|
}
|
|
if call.Name().String() != "call" {
|
|
return nil, clientError("root node's name is not \"call\"")
|
|
}
|
|
if len(call.Children()) != 1 {
|
|
return nil, clientError("call node has an invalid number of children")
|
|
}
|
|
|
|
module := call.Children()[0]
|
|
method := module.AttributeValue("method")
|
|
if method == "" {
|
|
return nil, clientError("module node does not contain a method attribute")
|
|
}
|
|
|
|
if !client.DisableQuery {
|
|
// this isn't used for routing on real e-amusement,
|
|
// but it makes the logs look more authentic
|
|
svc.URL += fmt.Sprintf("?model=%s&f=%s.%s", client.Model, module.Name(), method)
|
|
}
|
|
|
|
call.SetAttribute("srcid", client.Srcid)
|
|
call.SetAttribute("model", client.Model)
|
|
|
|
prop := &avsproperty.Property{
|
|
Root: call,
|
|
}
|
|
err := client.do(prop, svc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return prop, nil
|
|
}
|
|
|
|
func (client *Client) do(prop *avsproperty.Property, svc Service) error {
|
|
req, err := EncodeRequest(prop, svc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if s := client.UserAgent; s != "" {
|
|
req.Header.Set("User-Agent", s)
|
|
} else {
|
|
req.Header.Set("User-Agent", "EAMUSE.XRPC/1.0")
|
|
}
|
|
|
|
resp, err := client.HTTP.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if err := DecodeResponse(prop, resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
if prop.Root == nil {
|
|
return clientError("empty response property")
|
|
}
|
|
if prop.Root.Name().String() != "response" {
|
|
return clientError("name of root node in response property is not \"response\"")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validModel(s string) bool {
|
|
for _, r := range s {
|
|
if !unicode.In(r, unicode.Number, unicode.Letter) && r != ':' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|