blob: 99b47a12ce4acef4e11068944418793a189c6187 [file] [log] [blame]
package wsclient
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"sync"
"time"
"crypto/x509"
"gopkg.in/resty.v1"
log "github.com/sirupsen/logrus"
"github.com/franela/goreq"
)
var (
// ErrBadCAPEM 错误的 CA 文件
ErrBadCAPEM = errors.New("不正确的PEM文件")
)
var connectionTimeout = time.Duration(500) * time.Millisecond
// WebSession web session object
type WebSession struct {
// AppId app id
AppID string
// TermId term id
TermID string
// Appsecret secret
AppSecret string
// BaseUrl base url
BaseURL string
// DefaultTimeout default time
DefaultTimeout int
sessionKey string
jwt string
expiredAt int64
sslVerify bool
startTime int64
httpConnectionPool sync.Pool
}
func safeGetJSONInt(value interface{}) int {
if value == nil {
return 0
}
s := fmt.Sprintf("%v", value)
i, err := strconv.Atoi(s)
if err != nil {
return 0
}
return i
}
// ServiceResponse service response object
type ServiceResponse struct {
// RetCode return code
RetCode int
// RetMsg return message
RetMsg string
// Result return data
Result map[string]interface{}
}
// NewServiceResponseFromJSON parse json response data
func NewServiceResponseFromJSON(jsonData interface{}) *ServiceResponse {
if jsonData == nil {
return nil
}
res := &ServiceResponse{}
res.Result = jsonData.(map[string]interface{})
res.RetCode = safeGetJSONInt(res.Result["retcode"])
res.RetMsg = res.GetStrValue("retmsg")
return res
}
// GetIntValue get int value
func (r *ServiceResponse) GetIntValue(name string) int {
return safeGetJSONInt(r.Result[name])
}
// GetStrValue get string value
func (r *ServiceResponse) GetStrValue(name string) string {
s, ok := r.Result[name]
if ok {
return fmt.Sprintf("%v", s)
}
return ""
}
// GetInterfaceValue get value as interface
func (r *ServiceResponse) GetInterfaceValue(name string) interface{} {
s, ok := r.Result[name]
if ok {
return s
}
return nil
}
// GetFloatValue get float value
func (r *ServiceResponse) GetFloatValue(name string) float64 {
if s, ok := r.Result[name]; ok {
t := fmt.Sprintf("%v", s)
f, err := strconv.ParseFloat(t, 64)
if err != nil {
return 0.0
}
return f
}
return 0.0
}
// doGet send GET request
func (w *WebSession) doGet(uri string, params map[string]string,
timeout int) (*resty.Response, error) {
fullURL := w.BaseURL + uri
resty.SetTimeout(time.Duration(timeout) * time.Second)
resp, err := resty.R().
SetQueryParams(params).
Get(fullURL)
return resp, err
}
// GetTimestamp get time stamp format 20160103133455
func (w *WebSession) GetTimestamp() string {
t := time.Now()
return fmt.Sprintf("%04d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(),
t.Hour(), t.Minute(), t.Second())
}
// SignWithKey sign with key
func (w *WebSession) SignWithKey(key, message string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(message))
res := mac.Sum(nil)
return hex.EncodeToString(res)
}
// Sign sign data
func (w *WebSession) Sign(message string) string {
return w.SignWithKey(w.AppSecret, message)
}
func newTransport(baseurl string, sslVerify bool) *http.Transport {
var transport http.Transport
if strings.HasPrefix(baseurl, "https://") {
var b bool
if sslVerify {
b = false
} else {
b = true
}
transport = http.Transport{MaxIdleConnsPerHost: 0,
TLSClientConfig: &tls.Config{InsecureSkipVerify: b},
TLSHandshakeTimeout: time.Duration(1) * time.Second,
Dial: func(network, addr string) (net.Conn, error) {
defaultTimeout := time.Duration(1) * time.Second
return net.DialTimeout(network, addr, defaultTimeout)
}}
} else if strings.HasPrefix(baseurl, "http://") {
transport = http.Transport{MaxIdleConnsPerHost: 0,
Dial: func(network, addr string) (net.Conn, error) {
defaultTimeout := time.Duration(1) * time.Second
return net.DialTimeout(network, addr, defaultTimeout)
}}
}
log.Debugf("创建新连接")
return &transport
}
// doPost send POST request
func (w *WebSession) doPost(uri string, param map[string]string) (*resty.Response, error) {
param["app_id"] = w.AppID
param["term_id"] = w.TermID
param["sign_method"] = "HMAC"
param["session_key"] = w.sessionKey
ts := w.GetTimestamp()
param["timestamp"] = ts
param["sign"] = w.Sign(w.AppID + w.TermID + w.sessionKey + ts)
fullURL := w.BaseURL + uri
r, err := resty.R().
SetHeader("Content-Type", "application/json").
SetBody(param).
Post(fullURL)
if err != nil || r.StatusCode() != 200 {
log.Errorf("Status=%v, err=%v", r.StatusCode(), err)
}
return r, err
}
// Auth authorization
func (w *WebSession) Auth() error {
token, err := w.getAuthToken()
if err != nil {
return err
}
err = w.getAppAccessKey(token)
if err != nil {
return err
}
return nil
}
func (w *WebSession) updateAuth() {
for {
log.Debugf("设备: %v 正在尝试重新连接登录验证!", w.AppID)
err := w.Auth()
if err == nil {
return
}
time.Sleep(time.Second * 10)
}
}
// NewSession new session object
func NewSession(appid, appsecret, termid, baseurl string, timeout int, sslVerify bool) *WebSession {
return &WebSession{
AppID: appid,
AppSecret: appsecret,
TermID: termid,
BaseURL: baseurl,
DefaultTimeout: timeout,
sslVerify: sslVerify,
}
}
// NewSessionWithCA new session ca
func NewSessionWithCA(appID, appSecret, termID, baseURL string,
timeout int, ca []byte) (*WebSession, error) {
certs := x509.NewCertPool()
if !certs.AppendCertsFromPEM(ca) {
return nil, ErrBadCAPEM
}
resty.SetTLSClientConfig(&tls.Config{
InsecureSkipVerify: true,
RootCAs: certs,
})
return &WebSession{
AppID: appID,
AppSecret: appSecret,
TermID: termID,
BaseURL: baseURL,
DefaultTimeout: timeout,
sslVerify: true,
}, nil
}
func (w *WebSession) getAuthToken() (string, error) {
type FormJSON struct {
AppID string `json:"app_id"`
TermID string `json:"term_id"`
AccessToken string `json:"token"`
}
uri := "/auth/gettoken"
params := make(map[string]string)
params["appid"] = w.AppID
r, err := w.doGet(uri, params, 10)
if err != nil {
return "", err
}
if r.StatusCode() != 200 {
return "", errors.New("请求失败")
}
body := r.Body()
s := &FormJSON{}
err = json.Unmarshal(body, &s)
if err != nil {
log.Errorf("json unmarshal err %v", err)
return "", errors.New("解析失败")
}
return s.AccessToken, nil
}
func (w *WebSession) getAppAccessKey(token string) error {
type FormJSON struct {
AppID string `json:"app_id"`
TermID string `json:"term_id"`
SessionKey string `json:"session_key"`
CardKey string `json:"card_key"`
Jwt string `json:"jwt"`
ExpiredAt string `json:"expiredAt"`
}
uri := "/auth/authentication"
params := make(map[string]string)
params["appid"] = w.AppID
params["timestamp"] = w.GetTimestamp()
params["sign"] = w.Sign(token)
params["sign_method"] = "HMAC-SHA256"
r, err := w.doGet(uri, params, 10)
if err != nil {
log.Errorf("err = %v\n", err)
return err
}
if r.StatusCode() != 200 {
log.Errorf(" errcode = %v\n", r.StatusCode())
return fmt.Errorf("code %v", r.StatusCode())
}
body := r.Body()
s := &FormJSON{}
err = json.Unmarshal(body, &s)
if err != nil {
log.Errorf("json unmarshal err %v", err)
return err
}
w.jwt = s.Jwt
//JWT is valid for 20 minutes
w.expiredAt = 20 * 60
w.startTime = time.Now().Unix()
return nil
}
func (w *WebSession) updateJwt() error {
type FormJSON struct {
AppID string `json:"app_id"`
Jwt string `json:"jwt"`
}
nowTime := time.Now().Unix()
if nowTime-w.startTime >= w.expiredAt {
uri := "/auth/refresh"
params := make(map[string]string)
params["appid"] = w.AppID
fullURL := w.BaseURL + uri
log.Debugf("CallService: %v", fullURL)
r, err := resty.R().
SetHeader("Authorization", "Bearer "+w.jwt).
Get(fullURL)
if err != nil || r.StatusCode() != 200 {
log.Errorf("Status=%v, err=%v", r.StatusCode(), err)
return err
}
body := r.Body()
s := &FormJSON{}
err = json.Unmarshal(body, &s)
if err != nil {
log.Errorf("json unmarshal err %v", err)
return err
}
w.jwt = s.Jwt
w.startTime = nowTime
}
return nil
}
// CallYKTApi call ykt api function
func (w *WebSession) CallYKTApi(request *MessageWriter) (*MessageReader, error) {
callData := request.Serialize()
params := make(map[string]string)
params["funcdata"] = callData
r, err := w.doPost("/ecardservice/ecardapi", params)
if err != nil {
log.Errorf(" err = %v\n", err)
return nil, err
}
if r.StatusCode() != 200 {
return nil, fmt.Errorf("Request StatusCode:%v", r.StatusCode())
}
return NewMessageReader(r.Body()), nil
}
// CallService call epay service
func (w *WebSession) CallService(path string, params map[string]interface{},
signField []string, timeout int) (response *ServiceResponse, err error) {
return w.CallService2(path, params, timeout, signField...)
}
// SetConnectionTimeout set global connection timeout
func SetConnectionTimeout(ms int) {
connectionTimeout = time.Duration(ms) * time.Millisecond
resty.SetTimeout(connectionTimeout)
}
// CallService2 call epay service
func (w *WebSession) CallService2(path string, params map[string]interface{},
timeout int, signField ...string) (response *ServiceResponse, err error) {
err = nil
if err = w.updateJwt(); err != nil {
log.Errorf("updateJwt err = %v", err)
return
}
formData := make(map[string]string)
if params != nil {
for k, v := range params {
formData[k] = fmt.Sprintf("%v", v)
}
}
formData["app_id"] = w.AppID
formData["term_id"] = w.TermID
ts := w.GetTimestamp()
formData["timestamp"] = ts
fullURL := w.BaseURL + path
log.Debugf("CallService: %v", fullURL)
r, err := resty.R().
SetHeader("Authorization", "Bearer "+w.jwt).
SetHeader("Accept", "application/json").
SetFormData(formData).
Post(fullURL)
if err != nil {
log.Errorf("Status=%v, err=%v", r, err)
return
}
if r.StatusCode() == 403 || r.StatusCode() == 401 {
w.updateAuth()
}
if r.StatusCode() != 200 {
log.Errorf("Request Error %v", r.StatusCode())
err = fmt.Errorf("Request Error, StatusCode : %v", r.StatusCode())
return
}
var s interface{}
decoder := json.NewDecoder(bytes.NewBuffer(r.Body()))
decoder.UseNumber() // 此处能够保证bigint的精度
err = decoder.Decode(&s)
// err = json.Unmarshal(r.Body(), &s)
if err != nil {
log.Errorf("json unmarshal err %v", err)
return
}
response = NewServiceResponseFromJSON(s)
return
}