diff --git a/.gitignore b/.gitignore index 66fd13c9..d4d4f0d4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ # Dependency directories (remove the comment below to include it) # vendor/ +/.idea/ \ No newline at end of file diff --git a/discover-eureka/config.go b/discover-eureka/config.go new file mode 100644 index 00000000..c491b75b --- /dev/null +++ b/discover-eureka/config.go @@ -0,0 +1,14 @@ +package discover_eureka + + +type Config struct { + Name string `json:"name"` + Driver string `json:"driver"` + Labels map[string]string + Config AccessConfig `json:"config"` +} + +type AccessConfig struct { + Address []string + Params map[string]string +} diff --git a/discover-eureka/driver.go b/discover-eureka/driver.go new file mode 100644 index 00000000..47bde588 --- /dev/null +++ b/discover-eureka/driver.go @@ -0,0 +1,46 @@ +package discover_eureka + +import ( + "errors" + "fmt" + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/discovery" + "reflect" +) + +const ( + driverName = "eureka" +) +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type + params map[string]string +} + +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]interface{}) (eosc.IWorker, error) { + cfg, ok := v.(*Config) + if !ok { + return nil, errors.New(fmt.Sprintf("error struct type: %s, need struct type: %s", eosc.TypeNameOf(v), d.configType)) + } + return &eureka{ + id: id, + name: name, + address: cfg.Config.Address, + params: cfg.Config.Params, + labels: cfg.Labels, + services: discovery.NewServices(), + context: nil, + cancelFunc: nil, + }, nil + +} + diff --git a/discover-eureka/eureka.go b/discover-eureka/eureka.go new file mode 100644 index 00000000..0958b1b3 --- /dev/null +++ b/discover-eureka/eureka.go @@ -0,0 +1,176 @@ +package discover_eureka + +import ( + "context" + "encoding/xml" + "fmt" + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/discovery" + "io/ioutil" + "net/http" + "strings" + "time" +) + +type eureka struct { + id string + name string + address []string + params map[string]string + labels map[string]string + services discovery.IServices + context context.Context + cancelFunc context.CancelFunc +} + +func (e *eureka) GetApp(serviceName string) (discovery.IApp, error) { + app, err := e.Create(serviceName) + if err != nil { + return nil, err + } + err = e.services.Set(serviceName, app.Id(), app) + if err != nil { + return nil, err + } + return app, nil +} +func (e *eureka) Create(serviceName string) (discovery.IApp, error) { + nodes, err := e.GetNodeList(serviceName) + if err != nil { + return nil, err + } + attrs := make(discovery.Attrs) + app := discovery.NewApp(nil, e, attrs, nodes) + return app, nil +} + +func (e *eureka) Remove(id string) error { + return e.services.Remove(id) +} + +func (e *eureka) Id() string { + return e.id +} + +func (e *eureka) Start() error { + ctx, cancelFunc := context.WithCancel(context.Background()) + e.context = ctx + e.cancelFunc = cancelFunc + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + EXIT: + for { + select { + case <-ctx.Done(): + break EXIT + case <-ticker.C: + { + keys := e.services.AppKeys() + for _, serviceName := range keys { + res, err := e.GetNodeList(serviceName) + if err != nil { + continue + } + nodes := make([]discovery.INode, len(res)) + for _, v := range res { + nodes = append(nodes, v) + } + e.services.Update(serviceName, nodes) + } + } + } + + } + }() + return nil +} + +func (e *eureka) Reset(conf interface{}, workers map[eosc.RequireId]interface{}) error { + cfg, ok := conf.(*Config) + if !ok { + return fmt.Errorf("need %s,now %s:%w", eosc.TypeNameOf((*Config)(nil)), eosc.TypeNameOf(conf), eosc.ErrorStructType) + } + e.address = cfg.Config.Address + e.params = cfg.Config.Params + e.labels = cfg.Labels + return nil +} + +func (e *eureka) Stop() error { + e.cancelFunc() + return nil +} + +func (e *eureka) CheckSkill(skill string) bool { + return discovery.CheckSkill(skill) +} + +func (e *eureka) GetNodeList(serviceName string) (map[string]discovery.INode, error) { + nodes := make(map[string]discovery.INode) + for _, addr := range e.address { + // 获取每个ip中指定服务名的实例列表 + app, err := e.GetApplication(addr, serviceName) + if err != nil { + return nil, err + } + for _, ins := range app.Instances { + if ins.Status != EurekaStatusUp { + continue + } + port := 0 + if ins.Port.Enabled { + port = ins.Port.Port + } else if ins.SecurePort.Enabled { + port = ins.SecurePort.Port + } + label := map[string]string{ + "app": ins.App, + "hostName": ins.HostName, + } + //for k, v := range ins.Metadata { + // label[k] = v + //} + node := discovery.NewNode(label, ins.InstanceID, ins.IPAddr, port) + if _, ok := nodes[node.Id()]; ok { + continue + } + nodes[node.Id()] = node + } + } + return nodes, nil +} + +func (e *eureka) GetApplication(addr, serviceName string) (*Application, error) { + + if !strings.Contains(addr, "http://") && !strings.Contains(addr, "https://") { + addr = fmt.Sprintf("http://%s", addr) + if v, ok := e.labels["schema"]; ok { + if v == "https" { + addr = fmt.Sprintf("https://%s", addr) + } + } + } + addr = fmt.Sprintf("%s/apps/%s", addr, serviceName) + res, err := http.Get(addr) + if err != nil { + return nil, err + } + respBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + err = res.Body.Close() + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusOK { + return nil, err + } + var application = &Application{} + err = xml.Unmarshal(respBody, application) + if err != nil { + return nil, err + } + return application, err +} diff --git a/discover-eureka/eureka_request.go b/discover-eureka/eureka_request.go new file mode 100644 index 00000000..f0951797 --- /dev/null +++ b/discover-eureka/eureka_request.go @@ -0,0 +1,80 @@ +package discover_eureka +const EurekaStatusUp = "UP" + +//Application application +type Application struct { + Name string `xml:"name"` + Instances []InstanceInfo `xml:"instance" json:"instance"` +} + +//Instance instance +type Instance struct { + Instance *InstanceInfo `xml:"instance" json:"instance"` +} + +//InstanceInfo instanceInfo +type InstanceInfo struct { + HostName string `xml:"hostName" json:"hostName"` + HomePageURL string `xml:"homePageUrl,omitempty" json:"homePageUrl,omitempty"` + StatusPageURL string `xml:"statusPageUrl" json:"statusPageUrl"` + HealthCheckURL string `xml:"healthCheckUrl,omitempty" json:"healthCheckUrl,omitempty"` + App string `xml:"app" json:"app"` + IPAddr string `xml:"ipAddr" json:"ipAddr"` + VipAddress string `xml:"vipAddress" json:"vipAddress"` + SecureVipAddress string `xml:"secureVipAddress,omitempty" json:"secureVipAddress,omitempty"` + Status string `xml:"status" json:"status"` + Port *Port `xml:"port,omitempty" json:"port,omitempty"` + SecurePort *Port `xml:"securePort,omitempty" json:"securePort,omitempty"` + DataCenterInfo *DataCenterInfo `xml:"dataCenterInfo" json:"dataCenterInfo"` + LeaseInfo *LeaseInfo `xml:"leaseInfo,omitempty" json:"leaseInfo,omitempty"` + IsCoordinatingDiscoveryServer bool `xml:"isCoordinatingDiscoveryServer,omitempty" json:"isCoordinatingDiscoveryServer,omitempty"` + LastUpdatedTimestamp int `xml:"lastUpdatedTimestamp,omitempty" json:"lastUpdatedTimestamp,omitempty"` + LastDirtyTimestamp int `xml:"lastDirtyTimestamp,omitempty" json:"lastDirtyTimestamp,omitempty"` + ActionType string `xml:"actionType,omitempty" json:"actionType,omitempty"` + Overriddenstatus string `xml:"overriddenstatus,omitempty" json:"overriddenstatus,omitempty"` + CountryID int `xml:"countryId,omitempty" json:"countryId,omitempty"` + InstanceID string `xml:"instanceId" json:"instanceId"` + AppName string `xml:"appName,omitempty" json:"appName,omitempty"` + AppGroupName string `xml:"appGroupName,omitempty" json:"appGroupName,omitempty"` +} + + + +//Port port +type Port struct { + Port int `xml:",chardata" json:"$"` + Enabled bool `xml:"enabled,attr" json:"@enabled"` +} + +//DataCenterInfo dataCenterInfo +type DataCenterInfo struct { + Name string `xml:"name" json:"name"` + Class string `xml:"class,attr" json:"@class"` + Metadata *DataCenterMetadata `xml:"metadata,omitempty" json:"metadata,omitempty"` +} + +//DataCenterMetadata dataCenterMetaData +type DataCenterMetadata struct { + AmiLaunchIndex string `xml:"ami-launch-index,omitempty" json:"ami-launch-index,omitempty"` + LocalHostname string `xml:"local-hostname,omitempty" json:"local-hostname,omitempty"` + AvailabilityZone string `xml:"availability-zone,omitempty" json:"availability-zone,omitempty"` + InstanceID string `xml:"instance-id,omitempty" json:"instance-id,omitempty"` + PublicIpv4 string `xml:"public-ipv4,omitempty" json:"public-ipv4,omitempty"` + PublicHostname string `xml:"public-hostname,omitempty" json:"public-hostname,omitempty"` + AmiManifestPath string `xml:"ami-manifest-path,omitempty" json:"ami-manifest-path,omitempty"` + LocalIpv4 string `xml:"local-ipv4,omitempty" json:"local-ipv4,omitempty"` + Hostname string `xml:"hostname,omitempty" json:"hostname,omitempty"` + AmiID string `xml:"ami-id,omitempty" json:"ami-id,omitempty"` + InstanceType string `xml:"instance-type,omitempty" json:"instance-type,omitempty"` +} + +//LeaseInfo leaseInfo +type LeaseInfo struct { + EvictionDurationInSecs uint `xml:"evictionDurationInSecs,omitempty" json:"evictionDurationInSecs,omitempty"` + RenewalIntervalInSecs int `xml:"renewalIntervalInSecs,omitempty" json:"renewalIntervalInSecs,omitempty"` + DurationInSecs int `xml:"durationInSecs,omitempty" json:"durationInSecs,omitempty"` + RegistrationTimestamp int `xml:"registrationTimestamp,omitempty" json:"registrationTimestamp,omitempty"` + LastRenewalTimestamp int `xml:"lastRenewalTimestamp,omitempty" json:"lastRenewalTimestamp,omitempty"` + EvictionTimestamp int `xml:"evictionTimestamp,omitempty" json:"evictionTimestamp,omitempty"` + ServiceUpTimestamp int `xml:"serviceUpTimestamp,omitempty" json:"serviceUpTimestamp,omitempty"` +} diff --git a/discover-eureka/eureka_test.go b/discover-eureka/eureka_test.go new file mode 100644 index 00000000..cc025b40 --- /dev/null +++ b/discover-eureka/eureka_test.go @@ -0,0 +1,34 @@ +package discover_eureka + +import ( + "fmt" + "github.com/eolinker/goku-eosc/discovery" + "testing" +) + +func TestGetApp(t *testing.T) { + serviceName := "DEMO" + e := &eureka{ + id: "1", + name: "eolinker", + address: []string{ + "http://39.108.94.48:8761/eureka", + }, + params: map[string]string{ + "username": "test", + "password": "test", + }, + labels: nil, + services: discovery.NewServices(), + context: nil, + cancelFunc: nil, + } + + app, err := e.GetApp(serviceName) + if err!=nil { + fmt.Println("error:", err) + } + for _, node := range app.Nodes(){ + fmt.Println(node.Id()) + } +} diff --git a/discover-eureka/factory.go b/discover-eureka/factory.go new file mode 100644 index 00000000..2095a56e --- /dev/null +++ b/discover-eureka/factory.go @@ -0,0 +1,43 @@ +package discover_eureka + +import ( + "github.com/eolinker/eosc" + "reflect" +) + +func Register() { + eosc.DefaultProfessionDriverRegister.RegisterProfessionDriver("eolinker:goku:discover_eureka", NewFactory()) +} + +type factory struct { + profession string + name string + label string + desc string + params map[string]string +} + +func NewFactory() *factory { + return &factory{} +} + +func (f *factory) ExtendInfo() eosc.ExtendInfo { + return eosc.ExtendInfo{ + ID: "eolinker:goku:discover_eureka", + Group: "eolinker", + Project: "goku", + Name: "eureka", + } +} + +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]string) (eosc.IProfessionDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + params: params, + }, nil +} diff --git a/discover-nacos/config.go b/discover-nacos/config.go new file mode 100644 index 00000000..13f592cd --- /dev/null +++ b/discover-nacos/config.go @@ -0,0 +1,15 @@ +package discover_nacos + + +type Config struct { + Name string `json:"name"` + Driver string `json:"driver"` + Labels map[string]string + Config AccessConfig `json:"config"` +} + +type AccessConfig struct { + Address []string + Params map[string]string +} + diff --git a/discover-nacos/driver.go b/discover-nacos/driver.go new file mode 100644 index 00000000..51d74823 --- /dev/null +++ b/discover-nacos/driver.go @@ -0,0 +1,47 @@ +package discover_nacos + +import ( + "errors" + "fmt" + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/discovery" + "reflect" +) + +const ( + driverName = "nacos" +) +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type + params map[string]string +} + +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]interface{}) (eosc.IWorker, error) { + cfg, ok := v.(*Config) + if !ok { + return nil, errors.New(fmt.Sprintf("error struct type: %s, need struct type: %s", eosc.TypeNameOf(v), d.configType)) + } + return &nacos{ + id: id, + name: name, + address: cfg.Config.Address, + params: cfg.Config.Params, + labels: cfg.Labels, + services: discovery.NewServices(), + context: nil, + cancelFunc: nil, + }, nil + +} + + diff --git a/discover-nacos/factory.go b/discover-nacos/factory.go new file mode 100644 index 00000000..442721c6 --- /dev/null +++ b/discover-nacos/factory.go @@ -0,0 +1,43 @@ +package discover_nacos + +import ( + "github.com/eolinker/eosc" + "reflect" +) + +func Register() { + eosc.DefaultProfessionDriverRegister.RegisterProfessionDriver("eolinker:goku:discover_nacos", NewFactory()) +} + +type factory struct { + profession string + name string + label string + desc string + params map[string]string +} + +func NewFactory() *factory { + return &factory{} +} + +func (f *factory) ExtendInfo() eosc.ExtendInfo { + return eosc.ExtendInfo{ + ID: "eolinker:goku:discover_nacos", + Group: "eolinker", + Project: "goku", + Name: "nacos", + } +} + +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]string) (eosc.IProfessionDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + params: params, + }, nil +} diff --git a/discover-nacos/nacos.go b/discover-nacos/nacos.go new file mode 100644 index 00000000..8900ad2e --- /dev/null +++ b/discover-nacos/nacos.go @@ -0,0 +1,177 @@ +package discover_nacos + +import ( + "context" + "encoding/json" + "fmt" + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/discovery" + "io/ioutil" + "net/http" + "strconv" + "strings" + "time" +) + +const ( + instancePath = "/nacos/v1/ns/instance/list" +) + +type nacos struct { + id string + name string + address []string + params map[string]string + labels map[string]string + services discovery.IServices + context context.Context + cancelFunc context.CancelFunc +} + +// return worker id +func (n *nacos) Id() string { + return n.id +} + +// check worker skill +func (n *nacos) CheckSkill(skill string) bool { + return discovery.CheckSkill(skill) +} + +// start worker +func (n *nacos) Start() error { + ctx, cancelFunc := context.WithCancel(context.Background()) + n.context = ctx + n.cancelFunc = cancelFunc + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + EXIT: + for { + select { + case <-ctx.Done(): + break EXIT + case <-ticker.C: + { + keys := n.services.AppKeys() + for _, serviceName := range keys { + query := n.getParams(serviceName) + res, err := n.GetNodeList(query) + if err != nil { + continue + } + nodes := make([]discovery.INode, len(res)) + for _, v := range res { + nodes = append(nodes, v) + } + n.services.Update(serviceName, nodes) + } + } + } + + } + }() + return nil +} + +// update worker config +func (n *nacos) Reset(conf interface{}, workers map[eosc.RequireId]interface{}) error { + cfg, ok := conf.(*Config) + if !ok { + return fmt.Errorf("need %s,now %s:%w", eosc.TypeNameOf((*Config)(nil)), eosc.TypeNameOf(conf), eosc.ErrorStructType) + } + n.address = cfg.Config.Address + n.params = cfg.Config.Params + n.labels = cfg.Labels + return nil +} + +// stop worker +func (n *nacos) Stop() error { + n.cancelFunc() + return nil +} + +// remove app by app_id +func (n *nacos) Remove(id string) error { + return n.services.Remove(id) +} + +// new app according to serviceName +func (n *nacos) GetApp(serviceName string) (discovery.IApp, error) { + app, err := n.Create(serviceName) + if err != nil { + return nil, err + } + n.services.Set(serviceName, app.Id(), app) + return app, nil +} + +// create nacos app +func (n *nacos) Create(serviceName string) (discovery.IApp, error) { + query := n.getParams(serviceName) + nodes, err := n.GetNodeList(query) + if err != nil { + return nil, err + } + app := discovery.NewApp(nil, n, query, nodes) + return app, nil +} + +// get app node list +func (n *nacos) GetNodeList(query map[string]string) (map[string]discovery.INode, error) { + nodes := make(map[string]discovery.INode) + for _, addr := range n.address { + // 获取每个ip中指定服务名的实例列表 + ins, err := n.GetInstanceList(addr, query) + if err != nil { + return nil, err + } + + for _, host := range ins.Hosts { + label := map[string]string{ + "valid": strconv.FormatBool(host.Valid), + "marked": strconv.FormatBool(host.Marked), + "weight": strconv.FormatFloat(host.Weight, 'f', -1, 64), + } + node := discovery.NewNode(label, host.InstanceId, host.Ip, host.Port) + if _, ok := nodes[node.Id()]; ok { + continue + } + nodes[node.Id()] = node + } + } + return nodes, nil +} + +// get app instance list +func (n *nacos) GetInstanceList(addr string, query map[string]string) (*Instance, error) { + addr = addr + instancePath + if !strings.HasPrefix(addr, "http://") && !strings.HasPrefix(addr, "https://") { + addr = fmt.Sprintf("http://%s", addr) + if v,ok := n.labels["schema"]; ok { + if v == "https" { + addr = fmt.Sprintf("https://%s", addr) + } + } + } + response, err := SendRequest(http.MethodGet, addr, query, nil) + if err != nil { + return nil, err + } + // 解析响应数据 + rawResponseData, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + err = response.Body.Close() + if err != nil { + return nil, err + } + var instance = &Instance{} + err = json.Unmarshal(rawResponseData, instance) + if err != nil { + return nil, err + } + return instance, nil +} diff --git a/discover-nacos/nacos_request.go b/discover-nacos/nacos_request.go new file mode 100644 index 00000000..ea435b11 --- /dev/null +++ b/discover-nacos/nacos_request.go @@ -0,0 +1,70 @@ +package discover_nacos + +import ( + "encoding/json" + "io" + "net/http" + "net/url" + "strings" +) + +// nacos 实例结构 +type Instance struct { + Hosts []struct { + Valid bool `json:"valid"` + Marked bool `json:"marked"` + InstanceId string `json:"instanceId"` + Port int `json:"port"` + Ip string `json:"ip"` + Weight float64 `json:"weight"` + } +} + +func MapToJson(param map[string]interface{}) string { + dataType, _ := json.Marshal(param) + dataString := string(dataType) + return dataString +} + +func SendRequest(method string, request string, query map[string]string, body map[string]string) (*http.Response, error) { + // 构造url参数字符串 + paramsUrl := url.Values{} + for key, value := range query { + paramsUrl.Add(key, value) + } + // 更新url + paramsUrlString := paramsUrl.Encode() + if paramsUrlString != "" { + request = request + "?" + paramsUrlString + } + var bodyReader io.Reader + // 构造urlencoded请求体 + if method == http.MethodPost || method == http.MethodPut { + bodyUrl := url.Values{} + for key, value := range body { + bodyUrl.Add(key, value) + } + bodyUrlString := bodyUrl.Encode() + bodyReader = strings.NewReader(bodyUrlString) + } + req, err := http.NewRequest(method, request, bodyReader) + if err != nil { + return nil, err + } + httpClient := &http.Client{} + response, err := httpClient.Do(req) + if err != nil { + return nil, err + } + return response, nil +} +// get nacos query parameters +func (n *nacos) getParams(serviceName string) map[string]string { + query := n.params + query["serviceName"] = serviceName + if _, ok := query["healthyOnly"]; !ok { + query["healthyOnly"] = "true" + } + return query +} + diff --git a/discover-nacos/nacos_test.go b/discover-nacos/nacos_test.go new file mode 100644 index 00000000..b1557393 --- /dev/null +++ b/discover-nacos/nacos_test.go @@ -0,0 +1,27 @@ +package discover_nacos + +import ( + "fmt" + "github.com/eolinker/goku-eosc/discovery" + "testing" +) + +func TestNacos_GetApp(t *testing.T) { + serviceName := "nacos.naming.serviceName" + n := &nacos{ + address: []string{ + "39.108.94.48:8848", + }, + params: map[string]string{ + "username": "test", + "password": "test", + }, + services: discovery.NewServices(), + context: nil, + cancelFunc: nil, + } + app, _ := n.GetApp(serviceName) + for _, node := range app.Nodes(){ + fmt.Println(node.Id()) + } +} diff --git a/discovery-consul/config.go b/discovery-consul/config.go new file mode 100644 index 00000000..2775b16c --- /dev/null +++ b/discovery-consul/config.go @@ -0,0 +1,13 @@ +package discovery_consul + +type Config struct { + Name string `json:"name"` + Driver string `json:"driver"` + Labels map[string]string `json:"labels"` + Config AccessConfig `json:"config"` +} + +type AccessConfig struct { + Address []string `json:"address"` + Params map[string]string `json:"params"` +} diff --git a/discovery-consul/consul.go b/discovery-consul/consul.go new file mode 100644 index 00000000..0b489476 --- /dev/null +++ b/discovery-consul/consul.go @@ -0,0 +1,143 @@ +package discovery_consul + +import ( + "context" + "fmt" + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/log" + "time" + + "github.com/eolinker/goku-eosc/discovery" +) + +type consul struct { + id string + name string + address []string + params map[string]string + labels map[string]string + services discovery.IServices + context context.Context + cancelFunc context.CancelFunc +} + +// Start 开始服务发现 +func (c *consul) Start() error { + ctx, cancelFunc := context.WithCancel(context.Background()) + c.context = ctx + c.cancelFunc = cancelFunc + + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + EXIT: + for { + select { + case <-ctx.Done(): + break EXIT + case <-ticker.C: + { + keys := c.services.AppKeys() + for _, serviceName := range keys { + nodeSet, err := c.getNodes(serviceName) + if err != nil { + log.Error(err) + continue + } + + nodes := make([]discovery.INode, 0, len(nodeSet)) + for _, node := range nodeSet { + nodes = append(nodes, node) + } + c.services.Update(serviceName, nodes) + } + } + + } + + } + + }() + return nil +} + +// Reset 重置服务发现配置 +func (c *consul) Reset(config interface{}, workers map[eosc.RequireId]interface{}) error { + workerConfig, ok := config.(*Config) + if !ok { + return fmt.Errorf("need %s,now %s:%w", eosc.TypeNameOf((*Config)(nil)), eosc.TypeNameOf(config), eosc.ErrorStructType) + } + + c.address = workerConfig.Config.Address + c.params = workerConfig.Config.Params + c.labels = workerConfig.Labels + + return nil +} + +// Stop 停止服务发现 +func (c *consul) Stop() error { + c.cancelFunc() + return nil +} + +func (c *consul) Remove(id string) error { + return c.services.Remove(id) +} + +// GetApp 获取服务发现应用 +func (c *consul) GetApp(serviceName string) (discovery.IApp, error) { + nodes, err := c.getNodes(serviceName) + if err != nil { + return nil, err + } + + app, err := c.Create(serviceName, c.labels, nodes) + if err != nil { + return nil, err + } + c.services.Set(serviceName, app.Id(), app) + return app, nil +} + +// Create 创建服务发现应用 +func (c *consul) Create(serviceName string, attrs map[string]string, nodes map[string]discovery.INode) (discovery.IApp, error) { + return discovery.NewApp(nil, c, attrs, nodes), nil +} + +// Id 返回 worker id +func (n *consul) Id() string { + return n.id +} + +func (n *consul) CheckSkill(skill string) bool { + return discovery.CheckSkill(skill) +} + +//getNodes 通过接入地址获取节点信息 +func (c *consul) getNodes(service string) (map[string]discovery.INode, error) { + //TODO Labels怎么处理 + + nodeSet := make(map[string]discovery.INode) + + for _, addr := range c.address { + if !validAddr(addr) { + log.Errorf("address:%s is invalid", addr) + continue + } + client, err := getConsulClient(addr, c.params) + if err != nil { + log.Error(err) + continue + } + + clientNodes := getNodesFromClient(client, service) + for _, node := range clientNodes { + if _, has := nodeSet[node.Id()]; !has { + nodeSet[node.Id()] = node + } + } + } + + return nodeSet, nil +} diff --git a/discovery-consul/consul_test.go b/discovery-consul/consul_test.go new file mode 100644 index 00000000..6763382b --- /dev/null +++ b/discovery-consul/consul_test.go @@ -0,0 +1,29 @@ +package discovery_consul + +import ( + "github.com/eolinker/eosc/log" + "github.com/eolinker/goku-eosc/discovery" + "testing" +) + +func TestConsulGetNodes(t *testing.T) { + //创建consul + + newConsul := &consul{ + id: "newConsul", + address: []string{"39.108.94.48:8500", "39.108.94.48:8501"}, + params: map[string]string{"token": "a92316d8-5c99-4fa0-b4cd-30b9e66718aa"}, //token在39.108.94.48下的/opt/consul/server_config/node_3/conf/acl.hcl文件里 + labels: map[string]string{"scheme": "http"}, + services: discovery.NewServices(), + context: nil, + cancelFunc: nil, + } + + newConsul.Start() + + APP, _ := newConsul.GetApp("consul") + + log.Infof("%s", APP) + + select {} +} diff --git a/discovery-consul/driver.go b/discovery-consul/driver.go new file mode 100644 index 00000000..42b1884c --- /dev/null +++ b/discovery-consul/driver.go @@ -0,0 +1,48 @@ +package discovery_consul + +import ( + "fmt" + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/discovery" + "reflect" +) + +const ( + driverName = "consul" +) + +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type + params map[string]string +} + +func NewDriver() *driver { + return &driver{configType: reflect.TypeOf(new(Config))} +} + +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]interface{}) (eosc.IWorker, error) { + workerConfig, ok := v.(*Config) + if !ok { + return nil, fmt.Errorf("need %s,now %s:%w", eosc.TypeNameOf((*Config)(nil)), eosc.TypeNameOf(v), eosc.ErrorStructType) + } + return &consul{ + id: id, + name: name, + address: workerConfig.Config.Address, + params: workerConfig.Config.Params, + labels: workerConfig.Labels, + services: discovery.NewServices(), + context: nil, + cancelFunc: nil, + }, nil +} diff --git a/discovery-consul/factory.go b/discovery-consul/factory.go new file mode 100644 index 00000000..87579376 --- /dev/null +++ b/discovery-consul/factory.go @@ -0,0 +1,44 @@ +package discovery_consul + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +func Register() { + eosc.DefaultProfessionDriverRegister.RegisterProfessionDriver("eolinker:goku:discovery_consul", NewFactory()) +} + +type factory struct { + profession string + name string + label string + desc string + params map[string]string +} + +func NewFactory() *factory { + return &factory{} +} + +func (f *factory) ExtendInfo() eosc.ExtendInfo { + return eosc.ExtendInfo{ + ID: "eolinker:goku:discovery_consul", + Group: "eolinker", + Project: "goku", + Name: "consul", + } +} + +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]string) (eosc.IProfessionDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + params: params, + }, nil +} diff --git a/discovery-consul/utils.go b/discovery-consul/utils.go new file mode 100644 index 00000000..293772e0 --- /dev/null +++ b/discovery-consul/utils.go @@ -0,0 +1,85 @@ +package discovery_consul + +import ( + "github.com/eolinker/eosc/log" + "github.com/eolinker/goku-eosc/discovery" + "github.com/hashicorp/consul/api" + "regexp" + "strconv" + "strings" +) + +// getConsulClient 创建并返回consul客户端 +func getConsulClient(addr string, param map[string]string) (*api.Client, error) { + defaultConfig := api.DefaultConfig() + //配置信息写入进defaultConfig里 + defaultConfig.Address = addr + if _, has := param["token"]; has { + defaultConfig.Token = param["token"] + } + + client, err := api.NewClient(defaultConfig) + if err != nil { + return nil, err + } + + return client, nil +} + +// getNodesFromClient 从连接的客户端返回健康的节点信息 +func getNodesFromClient(client *api.Client, service string) []discovery.INode { + queryOptions := &api.QueryOptions{} + serviceEntryArr, _, err := client.Health().Service(service, "", true, queryOptions) + //log.Info(serviceEntryArr) + //catalogService, _, err := client.Catalog().Service(service, "", queryOptions) + if err != nil { + return nil + } + + nodes := make([]discovery.INode, 0, len(serviceEntryArr)) + for _, serviceEntry := range serviceEntryArr { + nodeAddr := serviceEntry.Node.Address + addrSlide := append(strings.Split(nodeAddr, ":")) + ip := addrSlide[0] + var port int + if len(addrSlide) > 1 { + port, err = strconv.Atoi(addrSlide[1]) + if err != nil { + log.Error(err) + continue + } + } + + newNode := discovery.NewNode(serviceEntry.Service.Meta, serviceEntry.Node.ID, ip, port) + nodes = append(nodes, newNode) + } + + return nodes +} + +// validAddr 判断地址是否合法 +func validAddr(addr string) bool { + c := strings.Split(addr, ":") + if len(c) < 2 { + return false + } + ip := c[0] + if !validIP(ip) { + return false + } + _, err := strconv.Atoi(c[1]) + if err != nil { + return false + } + + return true +} + +//validIP 判断ip是否合法 +func validIP(ip string) bool { + match, err := regexp.MatchString(`^(?:(?:1[0-9][0-9]\.)|(?:2[0-4][0-9]\.)|(?:25[0-5]\.)|(?:[1-9][0-9]\.)|(?:[0-9]\.)){3}(?:(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5])|(?:[1-9][0-9])|(?:[0-9]))$`, ip) + if err != nil { + return false + } + return match +} diff --git a/discovery-static/config.go b/discovery-static/config.go new file mode 100644 index 00000000..fe1066c2 --- /dev/null +++ b/discovery-static/config.go @@ -0,0 +1,23 @@ +package discovery_static + +type Config struct { + Name string `json:"name"` + Driver string `json:"driver"` + Labels map[string]string `json:"labels"` + Health *HealthConfig `json:"health"` + HealthOn bool `json:"health_on"` +} + +type AccessConfig struct { + Address []string `json:"address"` + Params map[string]string `json:"params"` +} + +type HealthConfig struct { + Protocol string `json:"protocol"` + Method string `json:"method"` + Url string `json:"url"` + SuccessCode int `json:"success_code"` + Period int `json:"period"` + Timeout int `json:"timeout"` +} diff --git a/discovery-static/driver.go b/discovery-static/driver.go new file mode 100644 index 00000000..0607c869 --- /dev/null +++ b/discovery-static/driver.go @@ -0,0 +1,44 @@ +package discovery_static + +import ( + "reflect" + "sync" + + "github.com/eolinker/goku-eosc/discovery" + + "github.com/eolinker/eosc" +) + +const ( + driverName = "static" +) + +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type + params map[string]string +} + +func NewDriver() *driver { + return &driver{configType: reflect.TypeOf(new(Config))} +} + +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]interface{}) (eosc.IWorker, error) { + s := &static{ + id: id, + name: name, + locker: sync.RWMutex{}, + apps: make(map[string]discovery.IApp), + } + s.Reset(v, workers) + return s, nil +} diff --git a/discovery-static/factory.go b/discovery-static/factory.go new file mode 100644 index 00000000..643c1345 --- /dev/null +++ b/discovery-static/factory.go @@ -0,0 +1,44 @@ +package discovery_static + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +func Register() { + eosc.DefaultProfessionDriverRegister.RegisterProfessionDriver("eolinker:goku:discovery_static", NewFactory()) +} + +type factory struct { + profession string + name string + label string + desc string + params map[string]string +} + +func NewFactory() *factory { + return &factory{} +} + +func (f *factory) ExtendInfo() eosc.ExtendInfo { + return eosc.ExtendInfo{ + ID: "eolinker:goku:discovery_static", + Group: "eolinker", + Project: "goku", + Name: "consul", + } +} + +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]string) (eosc.IProfessionDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + params: params, + }, nil +} diff --git a/discovery-static/static.go b/discovery-static/static.go new file mode 100644 index 00000000..34287bb5 --- /dev/null +++ b/discovery-static/static.go @@ -0,0 +1,212 @@ +package discovery_static + +import ( + "context" + "fmt" + "regexp" + "strconv" + "strings" + "sync" + "time" + "unicode" + + health_check_http "github.com/eolinker/goku-eosc/health-check-http" + + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/discovery" +) + +const name = "static" + +type static struct { + id string + name string + labels map[string]string + apps map[string]discovery.IApp + locker sync.RWMutex + healthOn bool + checker *health_check_http.HttpCheck + context context.Context + cancelFunc context.CancelFunc +} + +func (s *static) Id() string { + return s.id +} + +func (s *static) Start() error { + s.context, s.cancelFunc = context.WithCancel(context.Background()) + + return nil +} + +func (s *static) Reset(conf interface{}, workers map[eosc.RequireId]interface{}) error { + cfg, ok := conf.(*Config) + if !ok { + return fmt.Errorf("need %s,now %s:%w", eosc.TypeNameOf((*Config)(nil)), eosc.TypeNameOf(conf), eosc.ErrorStructType) + } + s.locker.Lock() + s.labels = cfg.Labels + s.locker.Unlock() + if cfg.Health == nil { + s.healthOn = false + } else { + s.healthOn = cfg.HealthOn + } + if s.healthOn { + if s.checker == nil { + s.checker = health_check_http.NewHttpCheck( + health_check_http.Config{ + Protocol: cfg.Health.Protocol, + Method: cfg.Health.Method, + Url: cfg.Health.Url, + SuccessCode: cfg.Health.SuccessCode, + Period: time.Duration(cfg.Health.Period) * time.Second, + Timeout: time.Duration(cfg.Health.Timeout) * time.Millisecond, + }) + } else { + s.checker.Reset( + health_check_http.Config{ + Protocol: cfg.Health.Protocol, + Method: cfg.Health.Method, + Url: cfg.Health.Url, + SuccessCode: cfg.Health.SuccessCode, + Period: time.Duration(cfg.Health.Period) * time.Second, + Timeout: time.Duration(cfg.Health.Timeout) * time.Millisecond, + }, + ) + } + } else { + if s.checker != nil { + s.checker.Stop() + s.checker = nil + } + } + + return nil +} + +func (s *static) Stop() error { + for _, a := range s.apps { + a.Close() + } + return nil +} + +func (s *static) CheckSkill(skill string) bool { + return discovery.CheckSkill(skill) +} + +func (s *static) GetApp(config string) (discovery.IApp, error) { + app, err := s.decode(config) + if err != nil { + return nil, err + } + s.locker.Lock() + s.apps[app.Id()] = app + s.locker.Unlock() + return app, nil +} + +func (s *static) Remove(id string) error { + s.locker.Lock() + delete(s.apps, id) + s.locker.Unlock() + return nil +} + +type Node struct { + labels map[string]string + ip string + port int +} + +func (s *static) decode(config string) (discovery.IApp, error) { + words := fields(config) + + nodes := make(map[string]discovery.INode) + + index := 0 + var node *Node + attrs := make(discovery.Attrs) + for _, word := range words { + + if word == ";" { + index = 0 + node = nil + continue + } + l := len(word) + value := word + if word[l-1] == ';' { + value = word[:l-1] + } + switch index { + case 0: + { + // 域名+端口 + node = new(Node) + vs := strings.Split(value, ":") + // 先判断是否是IP端口模式 + if !validIP(vs[0]) { + // 若不是IP端口模式,则计入全局的属性 + args := strings.Split(vs[0], "=") + if len(args) > 1 { + node.labels[args[0]] = args[1] + } + break + } + if len(vs) > 2 { + return nil, fmt.Errorf("decode ip:port failt for[%s]", value) + } + node.ip = vs[0] + if len(vs) == 2 { + port, _ := strconv.Atoi(vs[1]) + node.port = port + } + + } + default: + { + // label集合 + args := strings.Split(value, "=") + if len(args) > 1 { + node.labels[args[0]] = args[1] + } + } + } + + if word[l-1] == ';' { + n := discovery.NewNode(node.labels, fmt.Sprintf("%s:%d", node.ip, node.port), node.ip, node.port) + nodes[n.Id()] = n + index = 0 + node = nil + } else { + index++ + } + } + agent := (discovery.IHealthChecker)(nil) + if s.checker != nil { + agent, _ = s.checker.Agent() + } + + app := discovery.NewApp(agent, s, attrs, nodes) + return app, nil +} + +func fields(str string) []string { + + words := strings.FieldsFunc(strings.Join(strings.Split(str, ";"), " ; "), func(r rune) bool { + return unicode.IsSpace(r) + }) + return words +} + +//validIP 判断ip是否合法 +func validIP(ip string) bool { + match, err := regexp.MatchString(`^(?:(?:1[0-9][0-9]\.)|(?:2[0-4][0-9]\.)|(?:25[0-5]\.)|(?:[1-9][0-9]\.)|(?:[0-9]\.)){3}(?:(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5])|(?:[1-9][0-9])|(?:[0-9]))$`, ip) + if err != nil { + return false + } + return match +} diff --git a/discovery/app.go b/discovery/app.go new file mode 100644 index 00000000..ef758581 --- /dev/null +++ b/discovery/app.go @@ -0,0 +1,89 @@ +package discovery + +import ( + "sync" + + "github.com/go-basic/uuid" +) + +type app struct { + id string + nodes map[string]INode + healthChecker IHealthChecker + attrs Attrs + locker sync.RWMutex + container IAppContainer +} + +func (s *app) Reset(nodes []INode) { + tmp := make(map[string]INode) + + for _, node := range nodes { + + if n, has := s.nodes[node.Id()]; has { + n.Leave() + } + tmp[node.Id()] = node + + } + s.locker.Lock() + s.nodes = tmp + s.locker.Unlock() +} + +func (s *app) GetAttrs() Attrs { + return s.attrs +} + +func (s *app) GetAttrByName(name string) (string, bool) { + attr, ok := s.attrs[name] + return attr, ok +} + +func NewApp(checker IHealthChecker, container IAppContainer, attrs Attrs, nodes map[string]INode) IApp { + return &app{ + attrs: attrs, + nodes: nodes, + locker: sync.RWMutex{}, + healthChecker: checker, + id: uuid.New(), + container: container, + } +} + +func (s *app) Id() string { + return s.id +} + +//Nodes 将运行中的节点列表返回 +func (s *app) Nodes() []INode { + s.locker.RLock() + defer s.locker.RUnlock() + nodes := make([]INode, 0, len(s.nodes)) + for _, node := range s.nodes { + if node.Status() != Running { + continue + } + nodes = append(nodes, node) + } + return nodes +} + +//NodeError 定时检查节点,当节点失败时,则返回错误 +func (s *app) NodeError(id string) error { + s.locker.Lock() + defer s.locker.Unlock() + if n, ok := s.nodes[id]; ok { + n.Down() + if s.healthChecker != nil { + err := s.healthChecker.AddToCheck(n) + return err + } + } + return nil +} + +func (s *app) Close() error { + s.container.Remove(s.id) + return s.healthChecker.Stop() +} diff --git a/discovery/checker.go b/discovery/checker.go new file mode 100644 index 00000000..5df27285 --- /dev/null +++ b/discovery/checker.go @@ -0,0 +1,14 @@ +package discovery + +//IHealthCheckerFactory 健康检查工厂类接口 +type IHealthCheckerFactory interface { + IHealthChecker + Agent() (IHealthChecker, error) + Reset(conf interface{}) error +} + +//IHealthChecker 健康检查接口 +type IHealthChecker interface { + AddToCheck(node INode) error + Stop() error +} diff --git a/discovery/discovery.go b/discovery/discovery.go new file mode 100644 index 00000000..79cd7268 --- /dev/null +++ b/discovery/discovery.go @@ -0,0 +1,55 @@ +package discovery + +func CheckSkill(skill string) bool { + return skill == "github.com/eolinker/goku-eosc/discovery.discovery.IDiscovery" +} + +//IDiscovery 服务发现接口 +type IDiscovery interface { + GetApp(config string) (IApp, error) +} + +//IApp app接口 +type IApp interface { + IAttributes + Id() string + Nodes() []INode + Reset([]INode) + NodeError(id string) error + Close() error +} + +//IAppContainer app容器接口 +type IAppContainer interface { + Remove(id string) error +} + +//INode 节点接口 +type INode interface { + IAttributes + Id() string + Ip() string + Port() int + Addr() string + Status() NodeStatus + Up() + Down() + Leave() +} + +//Attrs 属性集合 +type Attrs map[string]string + +//IAttributes 属性接口 +type IAttributes interface { + GetAttrs() Attrs + GetAttrByName(name string) (string, bool) +} + +type NodeStatus int + +const ( + Running NodeStatus = 1 + Down NodeStatus = 2 + Leave NodeStatus = 3 +) diff --git a/discovery/node.go b/discovery/node.go new file mode 100644 index 00000000..02607961 --- /dev/null +++ b/discovery/node.go @@ -0,0 +1,65 @@ +package discovery + +import ( + "fmt" +) + +func NewNode(labels map[string]string, id string, ip string, port int) *Node { + return &Node{labels: labels, id: id, ip: ip, port: port, status: Running} +} + +type Node struct { + labels Attrs + id string + ip string + port int + status NodeStatus +} + +func (n *Node) GetAttrs() Attrs { + return n.labels +} + +func (n *Node) GetAttrByName(name string) (string, bool) { + v, ok := n.labels[name] + return v, ok +} + +func (n *Node) Ip() string { + return n.ip +} + +func (n *Node) Port() int { + return n.port +} + +func (n *Node) Id() string { + return n.id +} + +func (n *Node) Status() NodeStatus { + return n.status +} + +func (n *Node) Labels() map[string]string { + return n.labels +} + +func (n *Node) Addr() string { + if n.port == 0 { + return n.ip + } + return fmt.Sprintf("%s:%d", n.ip, n.port) +} + +func (n *Node) Up() { + n.status = Running +} + +func (n *Node) Down() { + n.status = Down +} + +func (n *Node) Leave() { + n.status = Leave +} diff --git a/discovery/services.go b/discovery/services.go new file mode 100644 index 00000000..4ee68580 --- /dev/null +++ b/discovery/services.go @@ -0,0 +1,67 @@ +package discovery + +import "github.com/eolinker/eosc/internal" + +type Services struct { + apps internal.IUntyped + appNameOfId internal.IUntyped +} + +func NewServices() *Services { + return &Services{apps: internal.NewUntyped(), appNameOfId: internal.NewUntyped()} +} + +func (n *Services) get(namespace string) (internal.IUntyped, bool) { + v, ok := n.apps.Get(namespace) + if !ok { + return nil, ok + } + apps, ok := v.(internal.IUntyped) + return apps, ok +} + +func (s *Services) Set(serviceName string, id string, app IApp) error { + s.appNameOfId.Set(id, serviceName) + if apps, ok := s.get(serviceName); ok { + apps.Set(id, app) + return nil + } + apps := internal.NewUntyped() + apps.Set(id, app) + s.apps.Set(serviceName, apps) + return nil +} + +func (s *Services) Remove(id string) error { + v, has := s.appNameOfId.Del(id) + if has { + apps, ok := s.get(v.(string)) + if ok { + apps.Del(id) + } + } + return nil +} + +func (s *Services) Update(serviceName string, nodes []INode) error { + if apps, ok := s.get(serviceName); ok { + for _, r := range apps.List() { + v, ok := r.(IApp) + if ok { + v.Reset(nodes) + } + } + } + return nil +} + +func (s *Services) AppKeys() []string { + return s.apps.Keys() +} + +type IServices interface { + Set(serviceName string, id string, app IApp) error + Remove(id string) error + Update(serviceName string, nodes []INode) error + AppKeys() []string +} diff --git a/example/config.go b/example/config.go new file mode 100644 index 00000000..5e5984aa --- /dev/null +++ b/example/config.go @@ -0,0 +1,9 @@ +package example + +import "github.com/eolinker/eosc" + +type Config struct { + Name string `json:"name"` + Label string `json:"label"` + Target eosc.RequireId `json:"target"` +} diff --git a/example/driver.go b/example/driver.go new file mode 100644 index 00000000..97419ad6 --- /dev/null +++ b/example/driver.go @@ -0,0 +1,26 @@ +package example + +import ( + "errors" + "github.com/eolinker/eosc" + "reflect" +) + +type Driver struct { + configType reflect.Type + params map[string]string +} + +func (h *Driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]interface{}) (eosc.IWorker, error) { + config, ok := v.(*Config) + if !ok { + return nil, errors.New("error") + } + + return NewExample(config, workers), nil +} +func (h *Driver) ConfigType() reflect.Type { + + return reflect.TypeOf(new(Config)) + +} diff --git a/example/employee.go b/example/employee.go new file mode 100644 index 00000000..a8ffa2f5 --- /dev/null +++ b/example/employee.go @@ -0,0 +1,48 @@ +package example + +import ( + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/service" + "net/http" +) + +var ( + _ eosc.IWorker = (*Example)(nil) +) + +type Example struct { + skill map[string]bool + name string + target service.IService +} + +func (e *Example) Id() string { + panic("implement me") +} + +func (e *Example) Start() error { + panic("implement me") +} + +func (e *Example) Reset(conf interface{}, workers map[eosc.RequireId]interface{}) error { + panic("implement me") +} + +func (e *Example) Stop() error { + panic("implement me") +} + +func (e *Example) CheckSkill(skill string) bool { + panic("implement me") +} + +func (e *Example) ServeHTTP(w http.ResponseWriter, r *http.Request) { + +} + +func NewExample(c *Config, workers map[eosc.RequireId]interface{}) *Example { + + return &Example{ + name: c.Name, + } +} diff --git a/example/register.go b/example/register.go new file mode 100644 index 00000000..c2d27060 --- /dev/null +++ b/example/register.go @@ -0,0 +1,37 @@ +package example + +import ( + "github.com/eolinker/eosc" + "reflect" +) + +func Register() { + eosc.DefaultProfessionDriverRegister.RegisterProfessionDriver("eolinker:goku:example", NewRouterDriverFactory()) +} + +type RouterDriverFactory struct { +} + +func NewRouterDriverFactory() *RouterDriverFactory { + return &RouterDriverFactory{} +} + +func (r *RouterDriverFactory) ExtendInfo() eosc.ExtendInfo { + return eosc.ExtendInfo{ + ID: "eolinker:goku:example", + Group: "eolinker", + Project: "goku", + Name: "example", + } +} + +func (r *RouterDriverFactory) Create(profession, name, label, desc string, params map[string]string) (eosc.IProfessionDriver, error) { + if params == nil { + params = make(map[string]string) + } + return &Driver{ + + configType: reflect.TypeOf(new(Config)), + params: params, + }, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..4d931360 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/eolinker/goku-eosc + +go 1.15 + +require ( + github.com/eolinker/eosc v0.0.0-20210719112509-35868a3fa3ed + github.com/ghodss/yaml v1.0.0 + github.com/go-basic/uuid v1.0.0 + github.com/hashicorp/consul/api v1.9.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..4b2dffa3 --- /dev/null +++ b/go.sum @@ -0,0 +1,110 @@ +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/eolinker/eosc v0.0.0-20210719112509-35868a3fa3ed h1:ohyziPArYFDUmaMkjfmy7h7v1vZKxmhERBwb1Vup39U= +github.com/eolinker/eosc v0.0.0-20210719112509-35868a3fa3ed/go.mod h1:h9RyDaBnWKeg6fxQu8faG8TQq/sdG+ipaePTTVTEqqA= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-basic/uuid v1.0.0 h1:Faqtetcr8uwOzR2qp8RSpkahQiv4+BnJhrpuXPOo63M= +github.com/go-basic/uuid v1.0.0/go.mod h1:yVtVnsXcmaLc9F4Zw7hTV7R0+vtuQw00mdXi+F6tqco= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/hashicorp/consul v1.10.1 h1:hVHyC7kdfZWS9c2QURt5hr87xqrOKT99zRXbxmo3e/8= +github.com/hashicorp/consul/api v1.9.1 h1:SngrdG2L62qqLsUz85qcPhFZ78rPf8tcD5qjMgs6MME= +github.com/hashicorp/consul/api v1.9.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/health-check-http/agent.go b/health-check-http/agent.go new file mode 100644 index 00000000..0d2dce91 --- /dev/null +++ b/health-check-http/agent.go @@ -0,0 +1,25 @@ +package health_check_http + +import "github.com/eolinker/goku-eosc/discovery" + +type Agent struct { + agentId string + *HttpCheck +} + +func NewAgent(agentId string) *Agent { + return &Agent{agentId: agentId} +} + +func (a *Agent) AddToCheck(node discovery.INode) error { + a.addToCheck(&checkNode{ + node: node, + agentId: a.agentId, + }) + return nil +} + +func (a *Agent) Stop() error { + a.stop(a.agentId) + return nil +} diff --git a/health-check-http/check.go b/health-check-http/check.go new file mode 100644 index 00000000..5062915f --- /dev/null +++ b/health-check-http/check.go @@ -0,0 +1,77 @@ +package health_check_http + +// +//type checker struct { +// ctx context.Context +// cancelFunc context.CancelFunc +// ch chan *checkNode +// config *Config +// id string +//} +// +//func (c *checker) doCheckLoop() { +// nodes := make(map[string]map[string]discovery.INode) +// for { +// select { +// case <-c.ctx.Done(): +// { +// return +// } +// case n, ok := <-c.ch: +// { +// +// } +// } +// } +//} +// +//func (c *checker) AddToCheck(node discovery.INode) error { +// n := &checkNode{ +// node: node, +// from: c.id, +// } +// c.ch <- n +// return nil +//} +// +//func (c *checker) Stop() error { +// c.cancelFunc() +// return nil +//} +// +//type checkNode struct { +// node discovery.INode +// from string +//} +// +//func NewHealthCheck(conf interface{}) (discovery.IHealthChecker, error) { +// ctx, cancel := context.WithCancel(context.Background()) +// ch := make(chan *checkNode, 10) +// c := &checker{ +// ctx: ctx, +// cancelFunc: cancel, +// ch: ch, +// } +// return c, nil +//} +// +//func (c *checker) check() error { +// for _, checkNode := range c.nodes { +// uri := fmt.Sprintf("%s://%s/%s", c.config.Protocol, strings.TrimSuffix(checkNode.checkNode.Addr(), "/"), strings.TrimPrefix(c.config.Url, "/")) +// c.client.Timeout = c.config.Timeout +// request, err := http.NewRequest(c.config.Method, uri, nil) +// if err != nil { +// return err +// } +// resp, err := c.client.Do(request) +// if err != nil { +// return err +// } +// defer resp.Body.Close() +// if c.config.SuccessCode != resp.StatusCode { +// return errors.New("error status code") +// } +// +// } +// return nil +//} diff --git a/health-check-http/checker-demo.go b/health-check-http/checker-demo.go new file mode 100644 index 00000000..7c35835c --- /dev/null +++ b/health-check-http/checker-demo.go @@ -0,0 +1,108 @@ +package health_check_http + +//var supportMethods = []string{ +// "POST", "GET", "PUT", "DELETE", "OPTIONS", "HEAD", "OPTIONS", +//} +// +//type checkerFactory struct { +// c chan *checkNode +// ctx context.Context +// cancelFunc context.CancelFunc +//} +// +//type checkNode struct { +// checkNode discovery.INode +// from string +// IsRemove bool +//} +// +//func NewCheckerFactory() *checkerFactory { +// +// ctx, cancelFunc := context.WithCancel(context.Background()) +// ch := make(chan *checkNode, 10) +// +// c := checkerFactory{ +// c: ch, +// ctx: ctx, +// cancelFunc: cancelFunc, +// } +// go c.doCheckLoop() +// return &c +//} +//func (c *checkerFactory) doCheckLoop() { +// for { +// select { +// case <-c.ctx.Done(): +// { +// return +// } +// case checkNode, ok := <-c.c: +// { +// +// } +// +// } +// } +//} +//func (c *checkerFactory) Create(config interface{}) (discovery.IHealthChecker, error) { +// cfg, ok := config.(*Config) +// if !ok { +// return nil, errors.New("fail to create health checker") +// } +// validMethod := false +// for _, method := range supportMethods { +// if method == cfg.Method { +// validMethod = true +// break +// } +// } +// if !validMethod { +// return nil, errors.New("error request method") +// } +// return &checker{config: cfg, client: &http.Client{}}, nil +//} +// +//type checker struct { +// id string +// config *Config +// client *http.Client +// nodes map[string]*checkNode +// +// ch chan *checkNode +//} +// +//func (c *checker) AddToCheck(checkNode discovery.INode) error { +// n := &checkNode{ +// checkNode: checkNode, +// from: c.id, +// } +// c.ch <- n +// c.nodes[checkNode.Id()] = n +// return nil +//} +// +//func (c *checker) Stop() error { +// +// return nil +//} +// +//func (c *checker) check() error { +// for _, checkNode := range c.nodes { +// uri := fmt.Sprintf("%s://%s/%s", c.config.Protocol, strings.TrimSuffix(checkNode.checkNode.Addr(), "/"), strings.TrimPrefix(c.config.Url, "/")) +// c.client.Timeout = c.config.Timeout +// request, err := http.NewRequest(c.config.Method, uri, nil) +// if err != nil { +// return err +// } +// resp, err := c.client.Do(request) +// if err != nil { +// return err +// } +// defer resp.Body.Close() +// if c.config.SuccessCode != resp.StatusCode { +// return errors.New("error status code") +// } +// +// } +// return nil +//} diff --git a/health-check-http/config.go b/health-check-http/config.go new file mode 100644 index 00000000..2fa8aed2 --- /dev/null +++ b/health-check-http/config.go @@ -0,0 +1,12 @@ +package health_check_http + +import "time" + +type Config struct { + Protocol string + Method string + Url string + SuccessCode int + Period time.Duration + Timeout time.Duration +} diff --git a/health-check-http/http-check.go b/health-check-http/http-check.go new file mode 100644 index 00000000..4230bf88 --- /dev/null +++ b/health-check-http/http-check.go @@ -0,0 +1,142 @@ +package health_check_http + +import ( + "context" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/eolinker/eosc/log" + + "github.com/eolinker/goku-eosc/discovery" + "github.com/go-basic/uuid" +) + +func NewHttpCheck(config Config) *HttpCheck { + ctx, cancel := context.WithCancel(context.Background()) + + checker := &HttpCheck{ + config: &config, + ctx: ctx, + cancel: cancel, + ch: make(chan *checkNode, 10), + client: &http.Client{}, + locker: sync.RWMutex{}, + } + go checker.doCheckLoop() + return checker +} + +type HttpCheck struct { + config *Config + ctx context.Context + cancel context.CancelFunc + ch chan *checkNode + delCh chan string + client *http.Client + locker sync.RWMutex +} + +func (h *HttpCheck) doCheckLoop() { + ticker := time.NewTicker(h.config.Period) + nodes := map[string]map[string]*checkNode{} + defer ticker.Stop() + for { + select { + case <-h.ctx.Done(): + return + case <-ticker.C: + { + nodes = h.check(nodes) + } + case node, ok := <-h.ch: + { + if ok { + if _, ok := nodes[node.agentId]; !ok { + nodes[node.agentId] = make(map[string]*checkNode) + } + nodes[node.agentId][node.node.Id()] = node + } + } + case id, ok := <-h.delCh: + { + if ok { + delete(nodes, id) + } + } + } + } +} + +func (h *HttpCheck) Agent() (discovery.IHealthChecker, error) { + return NewAgent(uuid.New()), nil +} + +func (h *HttpCheck) Reset(conf Config) error { + h.config = &conf + return nil +} + +func (h *HttpCheck) AddToCheck(node discovery.INode) error { + h.addToCheck(&checkNode{ + node: node, + agentId: "", + }) + return nil +} + +func (h *HttpCheck) addToCheck(node *checkNode) error { + h.ch <- node + return nil +} + +func (h *HttpCheck) Stop() error { + h.cancel() + return nil +} + +func (h *HttpCheck) stop(id string) { + h.delCh <- id +} + +func (h *HttpCheck) check(nodes map[string]map[string]*checkNode) map[string]map[string]*checkNode { + newNodes := make(map[string][]*checkNode) + for _, ns := range nodes { + for _, n := range ns { + if n.node.Status() == discovery.Down { + newNodes[n.node.Addr()] = append(newNodes[n.node.Addr()], n) + } + } + } + for addr, ns := range newNodes { + uri := fmt.Sprintf("%s://%s/%s", h.config.Protocol, strings.TrimSuffix(addr, "/"), strings.TrimPrefix(h.config.Url, "/")) + h.client.Timeout = h.config.Timeout + request, err := http.NewRequest(h.config.Method, uri, nil) + if err != nil { + log.Error(err) + continue + } + resp, err := h.client.Do(request) + if err != nil { + log.Error(err) + continue + } + resp.Body.Close() + if h.config.SuccessCode != resp.StatusCode { + log.Error(err) + continue + } + for _, n := range ns { + n.node.Up() + delete(nodes[n.agentId], n.node.Id()) + } + } + return nodes +} + +type checkNode struct { + node discovery.INode + agentId string +} diff --git a/http-proxy/http-proxy-request/request.go b/http-proxy/http-proxy-request/request.go new file mode 100644 index 00000000..347ec230 --- /dev/null +++ b/http-proxy/http-proxy-request/request.go @@ -0,0 +1,239 @@ +package http_proxy_request + +import ( + "bytes" + "crypto/tls" + "errors" + "io" + "net/http" + "net/url" + + http_context "github.com/eolinker/eosc/node/http-context" + + // "fmt" + "time" +) + +//Version 版本号 +var Version = "2.0" + +var ( + transport = &http.Transport{TLSClientConfig: &tls.Config{ + InsecureSkipVerify: false, + }} + httpClient = &http.Client{ + Transport: transport, + } +) + +//SetCert 设置证书配置 +func SetCert(skip int, clientCerts []tls.Certificate) { + tlsConfig := &tls.Config{InsecureSkipVerify: skip == 1, Certificates: clientCerts} + transport.TLSClientConfig = tlsConfig +} + +//Request request +type Request struct { + client *http.Client + method string + url string + headers map[string][]string + body []byte + + queryParams map[string][]string + + timeout time.Duration + httpRequest *http.Request +} + +func (r *Request) SetQueryParams(queryParams url.Values) { + r.queryParams = queryParams +} + +func (r *Request) SetHeaders(headers http.Header) { + r.headers = headers +} + +func (r *Request) Body() []byte { + return r.body +} + +func (r *Request) HttpRequest() *http.Request { + return r.httpRequest +} + +//NewRequest 创建新请求 +func NewRequest(method string, URL *url.URL) (*Request, error) { + if method != "GET" && method != "POST" && method != "PUT" && method != "DELETE" && + method != "HEAD" && method != "OPTIONS" && method != "PATCH" { + return nil, errors.New("Unsupported Request method") + } + return newRequest(method, URL) +} + +//URLPath urlPath +func URLPath(url string, query url.Values) string { + if len(query) < 1 { + return url + } + return url + "?" + query.Encode() +} + +func newRequest(method string, URL *url.URL) (*Request, error) { + var urlPath string + queryParams := make(map[string][]string) + for key, values := range URL.Query() { + queryParams[key] = values + } + urlPath = URL.Scheme + "://" + URL.Host + URL.Path + + r := &Request{ + + client: httpClient, + method: method, + url: urlPath, + headers: make(map[string][]string), + queryParams: queryParams, + } + return r, nil +} + +//SetHeader 设置请求头 +func (r *Request) SetHeader(key string, values ...string) { + if len(values) > 0 { + r.headers[key] = values[:] + } else { + delete(r.headers, key) + } +} + +//Headers 获取请求头 +func (r *Request) Headers() map[string][]string { + headers := make(map[string][]string) + for key, values := range r.headers { + headers[key] = values[:] + } + return headers +} + +//SetQueryParam 设置Query参数 +func (r *Request) SetQueryParam(key string, values ...string) { + if len(values) > 0 { + r.queryParams[key] = values[:] + } else { + delete(r.queryParams, key) + } +} + +//SetTimeout 设置请求超时时间 +func (r *Request) SetTimeout(timeout time.Duration) { + r.timeout = timeout +} + +//send 发送请求 +func (r *Request) Send(ctx *http_context.Context) (*http.Response, error) { + req := r.HttpRequest() + req.Header.Set("Accept-Encoding", "gzip") + req.Header = parseHeaders(r.headers) + + r.client.Timeout = r.timeout + + httpResponse, err := r.client.Do(req) + + return httpResponse, err +} + +//QueryParams 获取query参数 +func (r *Request) QueryParams() map[string][]string { + params := make(map[string][]string) + for key, values := range r.queryParams { + params[key] = values[:] + } + return params +} + +//URLPath 获取完整的URL路径 +func (r *Request) URLPath() string { + if len(r.queryParams) > 0 { + return r.url + "?" + parseParams(r.queryParams).Encode() + } + return r.url +} + +//SetURL 设置URL +func (r *Request) SetURL(url string) { + r.url = url +} + +//SetRawBody 设置源数据 +func (r *Request) SetRawBody(body []byte) { + r.body = body +} + +// 解析请求头 +func parseHeaders(headers map[string][]string) http.Header { + h := http.Header{} + for key, values := range headers { + for _, value := range values { + h.Add(key, value) + } + } + + _, hasAccept := h["Accept"] + if !hasAccept { + h.Add("Accept", "*/*") + } + _, hasAgent := h["User-Agent"] + if !hasAgent { + h.Add("User-Agent", "goku-requests/"+Version) + } + return h +} + +// 解析请求体 +func (r *Request) ParseBody() error { + if r.httpRequest == nil { + var body io.Reader = nil + if len(r.body) > 0 { + body = bytes.NewBuffer(r.body) + } + request, err := http.NewRequest(r.method, r.URLPath(), body) + if err != nil { + return err + } + r.httpRequest = request + } + return nil +} + +// 解析参数 +func parseParams(params map[string][]string) url.Values { + v := url.Values{} + for key, values := range params { + for _, value := range values { + v.Add(key, value) + } + } + return v +} + +// 解析URL +func parseURL(urlPath string) (URL *url.URL, err error) { + URL, err = url.Parse(urlPath) + if err != nil { + return nil, err + } + + if URL.Scheme != "http" && URL.Scheme != "https" { + urlPath = "http://" + urlPath + URL, err = url.Parse(urlPath) + if err != nil { + return nil, err + } + + if URL.Scheme != "http" && URL.Scheme != "https" { + return nil, errors.New("[package requests] only HTTP and HTTPS are accepted") + } + } + return +} diff --git a/http-proxy/r.go b/http-proxy/r.go new file mode 100644 index 00000000..2b0792ad --- /dev/null +++ b/http-proxy/r.go @@ -0,0 +1,57 @@ +package http_proxy + +import ( + "fmt" + "net/http" + "net/url" + "time" + + http_proxy_request "github.com/eolinker/goku-eosc/http-proxy/http-proxy-request" + http_context "github.com/eolinker/eosc/node/http-context" +) + +//DoRequest 构造请求 +func DoRequest(ctx *http_context.Context, uri string, timeout time.Duration) (*http.Response, error) { + if uri == "" { + return nil, fmt.Errorf("invaild url") + } + + u, err := url.ParseRequestURI(uri) + if err != nil { + return nil, err + } + + req, err := http_proxy_request.NewRequest(ctx.ProxyRequest.Method, u) + if err != nil { + + return nil, err + } + + queryDest := u.Query() + if ctx.ProxyRequest.Queries() != nil { + for k, vs := range ctx.ProxyRequest.Queries() { + for _, v := range vs { + queryDest.Add(k, v) + } + } + } + + req.SetHeaders(ctx.ProxyRequest.Headers()) + + req.SetQueryParams(queryDest) + body, _ := ctx.ProxyRequest.RawBody() + req.SetRawBody(body) + if timeout != 0 { + req.SetTimeout(timeout * time.Millisecond) + } + err = req.ParseBody() + if err != nil { + return nil, err + } + response, err := req.Send(ctx) + if err != nil { + return nil, err + } + + return response, err +} diff --git a/http-router/config.go b/http-router/config.go new file mode 100644 index 00000000..5ca32443 --- /dev/null +++ b/http-router/config.go @@ -0,0 +1,30 @@ +package http_router + +import "net/http" + +type Config struct { + name string + port int + Rules []RouterRule + host []string + service http.Handler +} + +type RouterRule struct { + location string + header map[string]string + query map[string]string +} + +type RouterWork struct { + Service http.Handler + Config Config +} + +//func (r *RouterWork) Start() error { +// routerManager.Add(r.Config, r.Service) +//} +// +//func (r *RouterWork) Stop() error { +// routerManager.Del(r.Config, r.Service) +//} diff --git a/http-router/driver.go b/http-router/driver.go new file mode 100644 index 00000000..4265be48 --- /dev/null +++ b/http-router/driver.go @@ -0,0 +1,32 @@ +package http_router + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +type HttpRouterDriver struct { + info eosc.DriverInfo + configType reflect.Type +} + +func (h *HttpRouterDriver) Create(id, name string, v interface{}, workers map[string]interface{}) (eosc.IWorker, error) { + panic("implement me") +} + +func NewHttpRouter(profession, name, label, desc string, params map[string]string) *HttpRouterDriver { + return &HttpRouterDriver{ + info: eosc.DriverInfo{ + Name: name, + Label: label, + Desc: desc, + Profession: profession, + Params: params, + }, + } +} + +func (h *HttpRouterDriver) ConfigType() reflect.Type { + return h.configType +} diff --git a/http-router/register.go b/http-router/register.go new file mode 100644 index 00000000..01424ce6 --- /dev/null +++ b/http-router/register.go @@ -0,0 +1,24 @@ +package http_router + +import "github.com/eolinker/eosc" + +func Register() { + eosc.DefaultProfessionDriverRegister.RegisterProfessionDriver("eolinker:goku:http_router",NewRouterDriverFactory()) +} + +type RouterDriverFactory struct { + +} + +func (r *RouterDriverFactory) ExtendInfo() eosc.ExtendInfo { + panic("implement me") +} + +func (r *RouterDriverFactory) Create(profession string, name string, label string, desc string, params map[string]string) (eosc.IProfessionDriver, error) { + panic("implement me") +} + +func NewRouterDriverFactory() *RouterDriverFactory { + return &RouterDriverFactory{} +} + diff --git a/http-router/router.go b/http-router/router.go new file mode 100644 index 00000000..cf098086 --- /dev/null +++ b/http-router/router.go @@ -0,0 +1,34 @@ +package http_router + +import ( + "github.com/eolinker/eosc" +) + +type Router struct { +} + +func (r *Router) Marshal() ([]byte, error) { + panic("implement me") +} + +func (r *Router) Worker() (eosc.IWorker, error) { + panic("implement me") +} + +func (r *Router) CheckSkill(skill string) bool { + panic("implement me") +} + +func (r *Router) Info() eosc.WorkerInfo { + return eosc.WorkerInfo{ + Id: "", + Name: "", + Driver: "", + Create: "", + Update: "", + } +} + +func NewRouter(c *Config) *Router { + return &Router{} +} diff --git a/listener-manager/listener.go b/listener-manager/listener.go new file mode 100644 index 00000000..96181541 --- /dev/null +++ b/listener-manager/listener.go @@ -0,0 +1,67 @@ +package listener_manager + +import ( + "errors" + "fmt" + "net" + "sync" +) + +const ( + TCP = "tcp" + UDP = "udp" +) + +var defaultListener = newListener() + +type listener struct { + tcpListeners map[int]net.Listener + udpListeners map[int]net.Listener + locker sync.RWMutex +} + +func newListener() *listener { + return &listener{tcpListeners: make(map[int]net.Listener), udpListeners: make(map[int]net.Listener), locker: sync.RWMutex{}} +} + +func (l *listener) getTCPListener(port int) (net.Listener, error) { + l.locker.RLock() + defer l.locker.RUnlock() + if v, ok := l.tcpListeners[port]; ok { + return v, nil + } + return nil, errors.New("no listener in port") +} + +func (l *listener) setTCPListener(port int, listen net.Listener) { + l.locker.Lock() + defer l.locker.Unlock() + l.tcpListeners[port] = listen +} + +func (l *listener) deleteTCPListener(port int) { + l.locker.Lock() + defer l.locker.Unlock() + delete(l.tcpListeners, port) +} + +func NewTCPListener(ip string, port int) (net.Listener, error) { + addr := fmt.Sprintf("%s:%d", ip, port) + l, err := net.Listen(TCP, addr) + if err != nil { + return nil, err + } + return l, nil +} + +func GetTCPListener(port int) (net.Listener, error) { + return defaultListener.getTCPListener(port) +} + +func SetTCPListener(port int, listen net.Listener) { + defaultListener.setTCPListener(port, listen) +} + +func DeleteTCPListener(port int) { + defaultListener.deleteTCPListener(port) +} diff --git a/main.go b/main.go new file mode 100644 index 00000000..fe7f767d --- /dev/null +++ b/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/router-http/Header.go b/router-http/Header.go new file mode 100644 index 00000000..ff132a98 --- /dev/null +++ b/router-http/Header.go @@ -0,0 +1,74 @@ +package router_http + +import ( + "encoding/json" + "net/http" + "sort" + "strings" +) + +type HeaderSort []string +type HeaderChecker string + +func (l HeaderSort) Len() int { + return len(l) +} + +func (l HeaderSort) Less(i, j int) bool { + // 若l[i]或l[j]中有个是 * ,且只会有一个*结点, * 排到最后 + if l[i] == "*" { + return false + } + if l[j] == "*"{ + return true + } + + mapI := make(map[string]string) + mapJ := make(map[string]string) + + _ = json.Unmarshal([]byte(l[i]), &mapI) + _ = json.Unmarshal([]byte(l[j]), &mapJ) + + // 需要满足key数量多的优先 + if len(mapI) == len(mapJ) { + // key数量相同则按字母排序从小到大排序,先匹配完的优先 + length := len(mapI) + + KeyArrI := make([]string, 0, length) + KeyArrJ := make([]string, 0, length) + + for key := range mapI { + KeyArrI = append(KeyArrI, strings.ToLower(key)) + } + for key := range mapJ { + KeyArrJ = append(KeyArrJ, strings.ToLower(key)) + } + + sort.Strings(KeyArrI) + sort.Strings(KeyArrJ) + + return KeyArrI[length - 1] < KeyArrI[length - 1] + + } + return len(mapI) > len(mapJ) +} + +func (l HeaderSort) Swap(i, j int) { + l[i],l[j] = l[j],l[i] +} + +func(hc HeaderChecker) check(request *http.Request) bool{ + if hc == "*"{ + return true + } + headerMap := make(map[string]string) + json.Unmarshal([]byte(hc), &headerMap) + + for key, value := range headerMap { + if request.Header.Get(strings.ToLower(key)) != value{ + return false + } + } + + return true +} \ No newline at end of file diff --git a/router-http/Host.go b/router-http/Host.go new file mode 100644 index 00000000..d3fd72d2 --- /dev/null +++ b/router-http/Host.go @@ -0,0 +1,79 @@ +package router_http + +import "strings" + +const ( + // 完整域名匹配 + hostPerfectMatchHostIndex = iota + // 子域名匹配 + hostLongestMatchHostIndex + // 任意匹配 + hostMatchAnyHostIndex +) + +type HostSort []string + +func (l HostSort) Len() int { + return len(l) +} + +func (l HostSort) Less(i, j int) bool { + s1,l1 := getHost(l[i]) + s2,l2:= getHost(l[j]) + if s1 == s2 { // 优先级 + if len(l1) == len(l2){ + // 长度相同,按字符排 + return l1 < l2 + } + // 按长度降序 + return l1 > l2 + } + return s1 < s2 +} + +func (l HostSort) Swap(i, j int) { + l[i],l[j] = l[j],l[i] +} + +func createHost(location string) Checker_One { + t,s:= getHost(location) + switch t { + case hostPerfectMatchHostIndex: + return hostPerfectMatchType(s) + case hostLongestMatchHostIndex: + return hostSubHostMatchType(s) + default: + return hostMatchAnyType(s) + } +} + +func getHost(s string) (int,string) { + if s == "*"{ + return hostMatchAnyHostIndex, s + } + + if index := strings.LastIndex(s,"*"); index != -1{ + return hostLongestMatchHostIndex, s[index + 1:] + }else{ + return hostPerfectMatchHostIndex, s + } +} + +type hostPerfectMatchType string + +func (l hostPerfectMatchType) check(v string) bool { + return string(l) == v +} + +type hostSubHostMatchType string + +func (l hostSubHostMatchType) check(v string) bool { + return strings.HasSuffix(v, string(l)) +} + +type hostMatchAnyType string + +func (l hostMatchAnyType) check(v string) bool { + return true +} + diff --git a/router-http/Location.go b/router-http/Location.go new file mode 100644 index 00000000..52c91c5d --- /dev/null +++ b/router-http/Location.go @@ -0,0 +1,135 @@ +package router_http + +import ( + "regexp" + "strings" +) + +const ( + // 正则表达式(区分大小写) + locationRegularMatchCase = "~" + // 正则表达式(不区分大小写) + locationRegularNotMatchCase = "~*" + // 完全匹配 + locationPerfectMatch = "=" + // 最长匹配前缀 + locationLongestMatch = "" + // 匹配任意 + locationMatchAny = "*" +) +const ( + // 完全匹配 + locationPerfectMatchIndex = iota + // 最长匹配前缀 + locationLongestMatchIndex + // 正则表达式(区分大小写) + locationRegularMatchCaseIndex + // 正则表达式(不区分大小写) + locationRegularNotMatchCaseIndex + // 匹配任意 + locationMatchAnyIndex +) + +type LocationSort []string + +func (l LocationSort) Len() int { + return len(l) +} + +func (l LocationSort) Less(i, j int) bool { + s1,l1 := getLocation(l[i]) + s2,l2:= getLocation(l[j]) + if s1 == s2 { // 优先级 + if len(l1) == len(l2){ + // 长度相同,按字符排 + return l1 < l2 + } + // 按长度降序 + return l1 > l2 + } + return s1 < s2 +} + +func (l LocationSort) Swap(i, j int) { + l[i],l[j] = l[j],l[i] +} + +func getLocation(s string) (int,string) { + + index := strings.IndexAny(s, "/") + if index != -1 { + switch s[:index] { + case locationPerfectMatch: + { + return locationPerfectMatchIndex, s[index:] + } + case locationLongestMatch: + { + return locationLongestMatchIndex, s[index:] + } + case locationRegularMatchCase: + { + return locationRegularMatchCaseIndex, s[index:] + } + case locationRegularNotMatchCase: + { + return locationRegularNotMatchCaseIndex, s[index:] + } + case locationMatchAny: + { + return locationMatchAnyIndex, "/" + } + default: + return locationLongestMatchIndex, "/"+s + } + } + + return locationLongestMatchIndex, "/"+s + +} + + + +func createLocation(location string) Checker_One { + t, s := getLocation(location) + switch t { + case locationPerfectMatchIndex: + return locationPerfectMatchType(s) + case locationLongestMatchIndex: + return locationLongestMatchType(s) + case locationRegularMatchCaseIndex: + compile :=regexp.MustCompile(s) + return (*LocationRegularMatchType)(compile) + case locationRegularNotMatchCaseIndex: + compile := regexp.MustCompile("(?i)" + strings.Replace(s, " ", "[ \\._-]", -1)) + return (*LocationRegularMatchType)(compile) + case locationMatchAnyIndex: + return locationMatchAnyType(s) + } + return locationLongestMatchType(s) +} + +type locationPerfectMatchType string + +func (l locationPerfectMatchType) check(v string) bool { + return string(l) == v +} + +type locationLongestMatchType string + +func (l locationLongestMatchType) check(v string) bool { + return strings.HasPrefix(v, string(l)) +} + +type LocationRegularMatchType regexp.Regexp + +func (l *LocationRegularMatchType) check(v string) bool { + return (*regexp.Regexp)(l).MatchString(v) +} + + +type locationMatchAnyType string + +func (l locationMatchAnyType) check(v string) bool { + return true +} diff --git a/router-http/Query.go b/router-http/Query.go new file mode 100644 index 00000000..b94eaaad --- /dev/null +++ b/router-http/Query.go @@ -0,0 +1,74 @@ +package router_http + +import ( + "encoding/json" + "net/http" + "sort" + "strings" +) + +type QuerySort []string +type QueryChecker string + +func (l QuerySort) Len() int { + return len(l) +} + +func (l QuerySort) Less(i, j int) bool { + // 若l[i]或l[j]中有个是 * ,且只会有一个*结点, * 排到最后 + if l[i] == "*" { + return false + } + if l[j] == "*"{ + return true + } + + mapI := make(map[string]string) + mapJ := make(map[string]string) + + _ = json.Unmarshal([]byte(l[i]), &mapI) + _ = json.Unmarshal([]byte(l[j]), &mapJ) + + // 需要满足key数量多的优先 + if len(mapI) == len(mapJ) { + // key数量相同则按字母排序从小到大排序,先匹配完的优先 + length := len(mapI) + + KeyArrI := make([]string, 0, length) + KeyArrJ := make([]string, 0, length) + + for key := range mapI { + KeyArrI = append(KeyArrI, strings.ToLower(key)) + } + for key := range mapJ { + KeyArrJ = append(KeyArrJ, strings.ToLower(key)) + } + + sort.Strings(KeyArrI) + sort.Strings(KeyArrJ) + + return KeyArrI[length - 1] < KeyArrI[length - 1] + + } + return len(mapI) > len(mapJ) +} + +func (l QuerySort) Swap(i, j int) { + l[i],l[j] = l[j],l[i] +} + +func(hc QueryChecker) check(request *http.Request) bool{ + if hc == "*"{ + return true + } + queryMap := make(map[string]string) + json.Unmarshal([]byte(hc), &queryMap) + + for key, value := range queryMap { + if request.URL.Query().Get(key) != value{ + return false + } + } + + return true +} \ No newline at end of file diff --git a/router-http/build.go b/router-http/build.go new file mode 100644 index 00000000..75933b1f --- /dev/null +++ b/router-http/build.go @@ -0,0 +1,165 @@ +package router_http + +import ( + "fmt" + "github.com/eolinker/goku-eosc/router" + "sort" +) + +// 路由树路径上经过的检测指标, 检测优先级 host>location>header>query +var RouterPathType = []string{ + "host", + "location", + "header", + "query", +} + +type Tree map[string]interface{} + +func (t Tree) Append(pathValue []string, target string) error { + if len(pathValue) < 1 { + return fmt.Errorf("no path exist") + } + pv := pathValue[0] + if len(pathValue) == 1 { + // 若target冲突 则返回错误 一条路由不能对应多个target + if _, has := t[pv]; has { + return fmt.Errorf("router config conflict") + } + t[pv] = target + } else { + next, has := t[pv] + if !has { + nextM := make(Tree) + err := nextM.Append(pathValue[1:], target) + if err != nil { + return err + } + t[pv] = nextM + } else { + nextM := next.(Tree) + err := nextM.Append(pathValue[1:], target) + if err != nil { + return err + } + } + } + return nil +} + +func createRouter(tree Tree, nodesType []string) router.IRouterHandler { + if len(nodesType) == 0 { + return nil + } + + nodeType := nodesType[0] + nextNodesType := nodesType[1:] + + switch nodeType { + case targetLocation: + pl := &_Plan_One{ + reader: CreateReader(nodeType), + checkers: nil, + nexts: nil, + } + sorts := make(LocationSort, 0, len(tree)) + for k := range tree { + sorts = append(sorts, k) + } + sort.Sort(sorts) + if len(nextNodesType) == 0 { + for _, s := range sorts { + v := tree[s] + pl.checkers = append(pl.checkers, createLocation(s)) + pl.nexts = append(pl.nexts, endPoint(v.(string))) + } + } else { + for _, s := range sorts { + v := tree[s] + pl.checkers = append(pl.checkers, createLocation(s)) + pl.nexts = append(pl.nexts, createRouter(v.(Tree), nextNodesType)) + } + } + + return pl + + case targetHost: + pl := &_Plan_One{ + reader: CreateReader(nodeType), + checkers: nil, + nexts: nil, + } + sorts := make(HostSort, 0, len(tree)) + for k := range tree { + sorts = append(sorts, k) + } + sort.Sort(sorts) + if len(nextNodesType) == 0 { + for _, s := range sorts { + v := tree[s] + pl.checkers = append(pl.checkers, createHost(s)) + pl.nexts = append(pl.nexts, endPoint(v.(string))) + } + } else { + for _, s := range sorts { + v := tree[s] + pl.checkers = append(pl.checkers, createHost(s)) + pl.nexts = append(pl.nexts, createRouter(v.(Tree), nextNodesType)) + } + } + + return pl + case targetHeader: + pl := &_Plan_Multi{ + checkers: nil, + nexts: nil, + } + sorts := make(HeaderSort, 0, len(tree)) + for k := range tree { + sorts = append(sorts, k) + } + sort.Sort(sorts) + if len(nextNodesType) == 0 { + for _, s := range sorts { + v := tree[s] + pl.checkers = append(pl.checkers, HeaderChecker(s)) + pl.nexts = append(pl.nexts, endPoint(v.(string))) + } + } else { + for _, s := range sorts { + v := tree[s] + pl.checkers = append(pl.checkers, HeaderChecker(s)) + pl.nexts = append(pl.nexts, createRouter(v.(Tree), nextNodesType)) + } + } + + return pl + default: //targetQuery + pl := &_Plan_Multi{ + checkers: nil, + nexts: nil, + } + sorts := make(QuerySort, 0, len(tree)) + for k := range tree { + sorts = append(sorts, k) + } + sort.Sort(sorts) + if len(nextNodesType) == 0 { + for _, s := range sorts { + + v := tree[s] + pl.checkers = append(pl.checkers, QueryChecker(s)) + pl.nexts = append(pl.nexts, endPoint(v.(string))) + } + } else { + for _, s := range sorts { + + v := tree[s] + pl.checkers = append(pl.checkers, QueryChecker(s)) + pl.nexts = append(pl.nexts, createRouter(v.(Tree), nextNodesType)) + } + } + + return pl + } +} diff --git a/router-http/demo_test.go b/router-http/demo_test.go new file mode 100644 index 00000000..55cf103c --- /dev/null +++ b/router-http/demo_test.go @@ -0,0 +1,94 @@ +package router_http + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "sync" + "testing" + + listener_manager "github.com/eolinker/goku-eosc/listener-manager" +) + +type RouterManagerDemo struct { + servers map[int]*RouterServerDemo + locker sync.RWMutex +} + +func NewRouterManagerDemo() *RouterManagerDemo { + return &RouterManagerDemo{locker: sync.RWMutex{}, servers: make(map[int]*RouterServerDemo)} +} + +func (r *RouterManagerDemo) Get(port int) (*RouterServerDemo, error) { + r.locker.RLock() + defer r.locker.RUnlock() + if v, ok := r.servers[port]; ok { + return v, nil + } + return nil, errors.New("") +} + +func (r *RouterManagerDemo) Set(port int, server *RouterServerDemo) { + r.locker.Lock() + defer r.locker.Unlock() + r.servers[port] = server +} + +type RouterServerDemo struct { + port int + server *http.Server + hosts []string +} + +func (r *RouterServerDemo) AppendHost(host ...string) { + r.hosts = append(r.hosts, host...) +} + +func (r *RouterServerDemo) Shutdown(ctx context.Context) error { + r.server.Shutdown(ctx) + return nil +} +func NewRouterServerDemo(port int, hosts []string) (*RouterServerDemo, error) { + ln, err := listener_manager.GetTCPListener(port) + if err == nil { + return nil, errors.New("the port is already occupied") + } + ln, err = listener_manager.NewTCPListener("0.0.0.0", port) + if err != nil { + return nil, err + } + listener_manager.SetTCPListener(port, ln) + r := &RouterServerDemo{port: port, hosts: hosts, server: &http.Server{}} + r.server.Handler = r + go r.server.Serve(ln) + + return r, nil +} + +func (r *RouterServerDemo) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + result := map[string]interface{}{ + "port": r.port, + "host": r.hosts, + } + b, _ := json.Marshal(result) + writer.Write(b) +} + +func TestRouter(t *testing.T) { + rm := NewRouterManagerDemo() + r, err := rm.Get(8080) + if err != nil { + r, err = NewRouterServerDemo(8080, []string{"www.baidu.com", "www.eolinker.com"}) + if err != nil { + return + } + } + + rm.Set(8080, r) + newR, err := rm.Get(8080) + if err == nil { + newR.AppendHost("www.goku.com", "www.apibee.com") + } + select {} +} diff --git a/router-http/endpoint.go b/router-http/endpoint.go new file mode 100644 index 00000000..58b34c23 --- /dev/null +++ b/router-http/endpoint.go @@ -0,0 +1,9 @@ +package router_http + +import "net/http" + +type endPoint string + +func (e endPoint) Match(request *http.Request) (string, bool) { + return string(e),true +} \ No newline at end of file diff --git a/router-http/plan-multi.go b/router-http/plan-multi.go new file mode 100644 index 00000000..6bb43463 --- /dev/null +++ b/router-http/plan-multi.go @@ -0,0 +1,26 @@ +package router_http + +import ( + "github.com/eolinker/goku-eosc/router" + "net/http" +) + +type Checker_Multi interface { + check(request *http.Request) bool +} + +// 适合从request检测多个值的类型,如header、query,读取request中需要检测的值在check中执行 +type _Plan_Multi struct { + checkers []Checker_Multi + nexts []router.IRouterHandler +} + +func (p *_Plan_Multi) Match(request *http.Request) (string, bool) { + + for i, c := range p.checkers { + if c.check(request) { + return p.nexts[i].Match(request) + } + } + return "", false +} diff --git a/router-http/plan-one.go b/router-http/plan-one.go new file mode 100644 index 00000000..7d9c80d8 --- /dev/null +++ b/router-http/plan-one.go @@ -0,0 +1,33 @@ +package router_http + +import ( + "github.com/eolinker/goku-eosc/router" + "net/http" +) + +type Checker_One interface { + check(v string) bool +} + +// 适合从request检测单个值的类型,如host、location +type _Plan_One struct { + reader Reader + checkers []Checker_One + nexts []router.IRouterHandler +} + +func (p *_Plan_One) Match(request *http.Request) (string, bool) { + v := p.reader.read(request) + + for i, c := range p.checkers { + if c.check(v) { + res, has := p.nexts[i].Match(request) + // 防止错失后面可能匹配成功的路径 + if !has { + continue + } + return res, has + } + } + return "", false +} diff --git a/router-http/reader-host.go b/router-http/reader-host.go new file mode 100644 index 00000000..171cce0d --- /dev/null +++ b/router-http/reader-host.go @@ -0,0 +1,13 @@ +package router_http + +import ( + "net/http" + "strings" +) + +type HostReader int + +func (h HostReader) read(request *http.Request) string { + hosts := strings.Split(request.Host, ":") + return hosts[0] +} diff --git a/router-http/reader-location.go b/router-http/reader-location.go new file mode 100644 index 00000000..24d23c55 --- /dev/null +++ b/router-http/reader-location.go @@ -0,0 +1,9 @@ +package router_http + +import "net/http" + +type LocationReader int + +func (l LocationReader) read(request *http.Request) string { + return request.URL.Path +} diff --git a/router-http/reader.go b/router-http/reader.go new file mode 100644 index 00000000..d1932e74 --- /dev/null +++ b/router-http/reader.go @@ -0,0 +1,28 @@ +package router_http + +import ( + "net/http" +) + +const ( + targetLocation = "location" + targetHost = "host" + targetHeader = "header" + targetQuery = "query" +) + +type Reader interface { + read(request *http.Request) string +} + +func CreateReader(targetType string) Reader { + + switch targetType { + case targetLocation: + return LocationReader(0) + case targetHost: + return HostReader(0) + } + + return nil +} \ No newline at end of file diff --git a/router-http/router-http-benchMark_test.go b/router-http/router-http-benchMark_test.go new file mode 100644 index 00000000..b9b8a71d --- /dev/null +++ b/router-http/router-http-benchMark_test.go @@ -0,0 +1,45 @@ +package router_http + +import ( + "flag" + "log" + "net/http" + "testing" +) + +func BenchmarkRouterMatch(b *testing.B) { + flag.Parse() + employeeArr := loadYamlEmployee() + if employeeArr == nil { + log.Fatalln("空employee切片") + } + RM, err := newRouterHttpManager(employeeArr) + if err != nil { + log.Fatalln(err) + } + + RM.StartAllServer() + + insertBenchMarkTests() + + client := &http.Client{} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + //for _, test := range benchMarkTests { + client.Do(benchMarkTests[0].request) + //} + } + +} + +var benchMarkTests = []testRequestStruct{} + +func insertBenchMarkTests() { + + request, _ := http.NewRequest("GET", "http://127.0.0.1:80/abc?token=123", nil) + request.Header.Set("user", "abc") + request.Header.Set("token", "abc") + benchMarkTests = append(benchMarkTests, testRequestStruct{80, "test10", request, "serviceE_rule4"}) + +} diff --git a/router-http/router-http.go b/router-http/router-http.go new file mode 100644 index 00000000..e7c67cee --- /dev/null +++ b/router-http/router-http.go @@ -0,0 +1,261 @@ +package router_http + +import ( + "encoding/json" + "fmt" + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/router" + "net/url" + "strconv" + "sync" +) + +const ( + routerProfession = "router" + driverName = "http" +) + +var ( + employeeError = fmt.Errorf("router HTTP employees not found") +) + +func Register() { + router.RegisterFactory(driverName, newRouterFactory()) +} + +type routerHttpFactory struct { +} + +func (r routerHttpFactory) Create(employeeArr []eosc.IEmployee) (router.IRouterManager, error) { + return newRouterHttpManager(employeeArr) +} + +func newRouterFactory() *routerHttpFactory { + return &routerHttpFactory{} +} + +type routerManager struct { + servers map[int]router.IRouter + locker sync.RWMutex +} + +// Create 创建路由管理器 +func newRouterHttpManager(employeeArr []eosc.IEmployee) (router.IRouterManager, error) { + if len(employeeArr) == 0 { + return nil, employeeError + } + + configs, err := loadRouterHttpEmployee(employeeArr) + if err != nil { + return nil, err + } + + RM := &routerManager{ + servers: make(map[int]router.IRouter), + locker: sync.RWMutex{}, + } + + for port, config := range configs { + p, _ := strconv.Atoi(port) + rt := &routerTree{ + listenPort: p, + serverState: ServerDown, + employees: make(map[string]*httpEmployee), + targets: make(map[string]*TargetConfig), + locker: sync.RWMutex{}, + } + + rt.tree, err = buildTree(config) + if err != nil { + return nil, err + } + rt.employees = config + rt.targets = buildTargetsConfig(config) + + RM.servers[p] = rt + } + + return RM, nil +} + +func (RM *routerManager) Set(port int, newEmployee eosc.IEmployee) error { + RM.locker.Lock() + + _, has := RM.servers[port] + if !has { + rT := &routerTree{ + listenPort: port, + serverState: ServerDown, + employees: make(map[string]*httpEmployee), + targets: make(map[string]*TargetConfig), + locker: sync.RWMutex{}, + } + RM.servers[port] = rT + } + RM.locker.Unlock() + + return RM.servers[port].Set(newEmployee) +} + +func (RM *routerManager) Delete(port int, id string) error { + rT, has := RM.servers[port] + if !has { + return fmt.Errorf("the port corresponding to the router tree does not exist") + } + err := rT.Delete(id) + if err == NoEmployeeError { + RM.servers[port] = nil + return nil + } + return err +} + +func (RM *routerManager) StartAllServer() { + for _, server := range RM.servers { + server.Serve() + } +} + +func (RM *routerManager) ShutDownAllServer() { + for _, server := range RM.servers { + server.ShutDown() + } +} + +func (RM *routerManager) StartServer(port int) error { + rT, has := RM.servers[port] + if !has { + return fmt.Errorf("the port corresponding to the router tree does not exist") + } + + err := rT.Serve() + return err +} + +func (RM *routerManager) ShutDownServer(port int) error { + rT, has := RM.servers[port] + if !has { + return fmt.Errorf("the port corresponding to the router tree does not exist") + } + + err := rT.ShutDown() + return err +} + +func loadRouterHttpEmployee(employeeArr []eosc.IEmployee) (map[string]map[string]*httpEmployee, error) { + conf := make(map[string]map[string]*httpEmployee, 0) + + for _, employee := range employeeArr { + if employee.Driver() != driverName { + continue + } + cData := employee.Config() + var c router.Config + err := json.Unmarshal([]byte(cData), &c) + if err != nil { + return nil, fmt.Errorf("unmarshal routerEmployee Fail [err]: %s employee data: %s ", err, cData) + } + + hE := &httpEmployee{ + employeeConfig: cData, + config: &c, + } + + id := c.ID + if id == "" { + id = c.Name + } + if _, has := conf[c.Listen]; !has { + conf[c.Listen] = map[string]*httpEmployee{id: hE} + continue + } + conf[c.Listen][id] = hE + } + + if len(conf) == 0 { + return nil, fmt.Errorf("router HTTP employees not found") + } + + return conf, nil +} + +// 构建路由树,并排序匹配的顺序 +func buildTree(routerConfigs map[string]*httpEmployee) (router.IRouterHandler, error) { + tree := make(Tree) + + pathConfigSet := toPathConfig(routerConfigs) + for _, pc := range pathConfigSet { + err := tree.Append(pc.pathValue, pc.target) + if err != nil { + return nil, err + } + } + + return createRouter(tree, RouterPathType), nil +} + +func toPathConfig(rcs map[string]*httpEmployee) []*pathConfig { + pathConfigSet := make([]*pathConfig, 0, len(rcs)) + + for _, rc := range rcs { + config := rc.config + hosts := config.Host + if config.Host == nil { + hosts = []string{"*"} + } + for _, host := range hosts { + for _, rule := range config.Rules { + location := rule.Location + if location == "" { + location = "*/" + } + + header := "*" + if rule.Header != nil { + headerStr, _ := json.Marshal(rule.Header) + header = string(headerStr) + } + + query := "*" + if rule.Query != nil { + queryStr, _ := json.Marshal(rule.Query) + query = string(queryStr) + } + + pathConfigSet = append(pathConfigSet, &pathConfig{ + pathValue: []string{host, location, header, query}, + target: rule.Target, + }) + } + } + } + return pathConfigSet +} + +func buildTargetsConfig(httpEmployees map[string]*httpEmployee) map[string]*TargetConfig { + targetConfigSet := make(map[string]*TargetConfig) + for _, hE := range httpEmployees { + config := hE.config + + hosts := config.Host + if config.Host == nil { + hosts = []string{""} + } + for _, host := range hosts { + for _, rule := range config.Rules { + location := rule.Location + header := rule.Header + + query := url.Values{} + if rule.Query != nil { + for k, v := range rule.Query { + query.Add(k, v) + } + } + + targetConfigSet[rule.Target] = &TargetConfig{location, host, header, query} + } + } + } + return targetConfigSet +} diff --git a/router-http/router-http_test.go b/router-http/router-http_test.go new file mode 100644 index 00000000..ebb80601 --- /dev/null +++ b/router-http/router-http_test.go @@ -0,0 +1,164 @@ +package router_http + +import ( + "encoding/json" + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/router" + "github.com/ghodss/yaml" + "io/ioutil" + "log" + "net/http" + "testing" +) + +type testRequestStruct struct { + port int + testName string + request *http.Request + want string +} + +var tests = []testRequestStruct{} + +func insertTests() { + + request, _ := http.NewRequest("GET", "http://www.eolinker.com/ab?token=123", nil) + request.Header.Set("app", "goku") + request.Header.Set("user", "abc") + request.Header.Set("version", "1.0") + tests = append(tests, testRequestStruct{7777, "test1", request, "serviceA_rule1"}) + + request, _ = http.NewRequest("GET", "http://www.apishop.net/ab?token=123", nil) + request.Header.Set("app", "gokux") + request.Header.Set("version", "1.0") + tests = append(tests, testRequestStruct{7777, "test2", request, "serviceD_rule1"}) + + request, _ = http.NewRequest("GET", "http://www.apibee.com/ab?token=123", nil) + request.Header.Set("user", "abc") + tests = append(tests, testRequestStruct{7777, "test3", request, "serviceA_rule3"}) + + request, _ = http.NewRequest("GET", "http://www.apishop.net/abc?token=123", nil) + request.Header.Set("user", "abc") + tests = append(tests, testRequestStruct{7777, "test4", request, "serviceA_rule4"}) + + request, _ = http.NewRequest("GET", "http://www.apishop.net/cxz?token=123", nil) + request.Header.Set("user", "abc") + request.Header.Set("token", "abc") + tests = append(tests, testRequestStruct{7777, "test5", request, "serviceA_rule2"}) + + request, _ = http.NewRequest("GET", "http://www.eolinker.com/abcd?token=123", nil) + tests = append(tests, testRequestStruct{7777, "test6", request, "serviceB_rule1"}) + + request, _ = http.NewRequest("GET", "http://www.eolinker.com/cxz?token=123", nil) + tests = append(tests, testRequestStruct{7777, "test7", request, "serviceB_rule2"}) + + request, _ = http.NewRequest("GET", "http://www.apibee.com/ab?token=123&token2=321", nil) + request.Header.Set("user", "abc") + request.Header.Set("token", "abc") + tests = append(tests, testRequestStruct{7777, "test8", request, "serviceC_rule3"}) + + request, _ = http.NewRequest("GET", "http://www.apibee.com/ab?token=123", nil) + request.Header.Set("user", "abc") + request.Header.Set("token", "abc") + tests = append(tests, testRequestStruct{7777, "test9", request, "serviceC_rule2"}) + + request, _ = http.NewRequest("GET", "http://www.adasdavera.com/abc?token=123", nil) + request.Header.Set("user", "abc") + request.Header.Set("token", "abc") + tests = append(tests, testRequestStruct{80, "test10", request, "serviceE_rule4"}) + +} + +func TestRM(t *testing.T) { + employeeArr := loadYamlEmployee() + if employeeArr == nil { + log.Fatalln("空employee切片") + } + RM, err := newRouterHttpManager(employeeArr) + if err != nil { + log.Fatalln(err) + } + + //测试路由树正确性 + //insertTests() + //NRM := RM.(*routerManager) + //for _, test := range tests { + // t.Run(test.testName, func(t *testing.T) { + // Tree := NRM.servers[test.port].(*routerTree) + // target, _ := Tree.tree.Match(test.request) + // if target != test.want { + // log.Println(test.testName, " target == ", target) + // t.Fail() + // } + // }) + //} + + RM.StartAllServer() + //测试删除实例 + //RM.Delete(80, "E") + + //测试shutdown后再启动 + //RM.ShutDownServer(80) + //RM.StartServer(80) + + //测试新增实例 + //newEmploye := loadNewEmployee()[0] + //RM.Set(80, newEmploye) + + select {} + +} + +func loadYamlEmployee() []eosc.IEmployee { + data, err := ioutil.ReadFile("test_router_manager.yaml") + if err != nil { + return nil + } + + convertData, err := yaml.YAMLToJSON(data) + if err != nil { + return nil + } + + var cfg map[string][]router.Config + err = json.Unmarshal(convertData, &cfg) + if err != nil { + return nil + } + + employeeArr := make([]eosc.IEmployee, 0, len(cfg["router"])) + + for _, config := range cfg["router"] { + cdata, _ := json.Marshal(config) + employeeArr = append(employeeArr, router.NewEmployee(config.ID, config.Name, config.Driver, string(cdata))) + } + + return employeeArr +} + +func loadNewEmployee() []eosc.IEmployee { + data, err := ioutil.ReadFile("test_router_manager_newEmployee.yaml") + if err != nil { + return nil + } + + convertData, err := yaml.YAMLToJSON(data) + if err != nil { + return nil + } + + var cfg map[string][]router.Config + err = json.Unmarshal(convertData, &cfg) + if err != nil { + return nil + } + + employeeArr := make([]eosc.IEmployee, 0, len(cfg["router"])) + + for _, config := range cfg["router"] { + cdata, _ := json.Marshal(config) + employeeArr = append(employeeArr, router.NewEmployee(config.ID, config.Name, config.Driver, string(cdata))) + } + + return employeeArr +} diff --git a/router-http/router-tree.go b/router-http/router-tree.go new file mode 100644 index 00000000..65d0c4c3 --- /dev/null +++ b/router-http/router-tree.go @@ -0,0 +1,172 @@ +package router_http + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/listener" + "github.com/eolinker/eosc/log" + listener_manager "github.com/eolinker/goku-eosc/listener-manager" + "github.com/eolinker/goku-eosc/router" + "net/http" + "sync" +) + +const ( + ServerUp = "UP" + ServerDown = "DOWN" +) + +var NoEmployeeError = errors.New("no employee exist") + +type routerTree struct { + listenPort int + server *http.Server + serverState string + tree router.IRouterHandler + employees map[string]*httpEmployee // key为实例id + targets map[string]*TargetConfig // key为target + locker sync.RWMutex +} + +type httpEmployee struct { + employeeConfig string + config *router.Config +} + +type pathConfig struct { + pathValue []string + target string +} + +func (r *routerTree) Set(newEmployee eosc.IWorker) error { + r.locker.Lock() + defer r.locker.Unlock() + + if newEmployee.Driver() != driverName { + return fmt.Errorf("router set employee fail. the employee's driver isn't HTTP ") + } + + id := newEmployee.ID() + if id == "" { + id = newEmployee.Name() + } + + // 若更新的实例在原有配置中已存在且配置相同,则直接返回 + newEmployeeConfig := newEmployee.Config() + if oldHE, has := r.employees[id]; has && oldHE.employeeConfig == newEmployeeConfig { + return nil + } + + TempEmployees := cloneEmployees(r.employees) + var c router.Config + err := json.Unmarshal([]byte(newEmployeeConfig), &c) + if err != nil { + return fmt.Errorf("unmarshal routerEmployee Fail [err]: %s employee data: %s ", err, newEmployeeConfig) + } + TempEmployees[id] = &httpEmployee{newEmployeeConfig, &c} + + NewTree, err := buildTree(TempEmployees) + if err != nil { + return fmt.Errorf("set employee fail err: %s", err) + } + + r.tree = NewTree + r.employees = TempEmployees + r.targets = buildTargetsConfig(r.employees) + + return nil +} + +func (r *routerTree) Delete(id string) error { + r.locker.Lock() + defer r.locker.Unlock() + + if _, has := r.employees[id]; !has { + return fmt.Errorf("delete employee fail. Employee id %s not exist", id) + } + + TempEmployees := cloneEmployees(r.employees) + delete(TempEmployees, id) + if len(TempEmployees) == 0 { + // 若该端口下已没有路由实例,则关闭Server 并在路由管理器中删除本路由树 + r.ShutDown() + return NoEmployeeError + } + + NewTree, err := buildTree(TempEmployees) + if err != nil { + return fmt.Errorf("delete employee fail err: %s", err) + } + r.tree = NewTree + r.employees = TempEmployees + r.targets = buildTargetsConfig(r.employees) + + return nil +} + +//启动路由树 +func (r *routerTree) Serve() error { + r.locker.Lock() + defer r.locker.Unlock() + + if r.serverState == ServerUp { + return nil + } + + r.server = &http.Server{} + r.server.Handler = r + + ln, err := listener.ListenerTCP(r.listenPort, n) + if err != nil { + + } + go func(srv *http.Server) { + err := srv.Serve(ln) + log.Error(err) + }(r.server) + r.serverState = ServerUp + + return nil +} + +func (r *routerTree) ShutDown() error { + r.locker.Lock() + defer r.locker.Unlock() + + if r.serverState == ServerDown { + return nil + } + + listener_manager.DeleteTCPListener(r.listenPort) + r.server.Shutdown(context.Background()) + r.serverState = ServerDown + return nil +} + +func (r *routerTree) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + //TODO + + target, has := r.tree.Match(request) + if !has { + return + } + result := map[string]interface{}{ + "location": r.targets[target].Location(), + "host": r.targets[target].Host(), + "header": r.targets[target].Header(), + "query": r.targets[target].Query(), + } + data, _ := json.Marshal(result) + writer.Write(data) +} + +func cloneEmployees(HES map[string]*httpEmployee) map[string]*httpEmployee { + NewHES := make(map[string]*httpEmployee) + for id, HE := range HES { + NewHES[id] = HE + } + return NewHES +} diff --git a/router-http/target.go b/router-http/target.go new file mode 100644 index 00000000..652aa92c --- /dev/null +++ b/router-http/target.go @@ -0,0 +1,26 @@ +package router_http + +import "net/url" + +type TargetConfig struct { + location string + host string + header map[string]string + query url.Values +} + +func (t *TargetConfig) Location() string { + return t.location +} + +func (t *TargetConfig) Host() string { + return t.host +} + +func (t *TargetConfig) Header() map[string]string { + return t.header +} + +func (t *TargetConfig) Query() url.Values { + return t.query +} diff --git a/router-http/test_router_manager.yaml b/router-http/test_router_manager.yaml new file mode 100644 index 00000000..a4134271 --- /dev/null +++ b/router-http/test_router_manager.yaml @@ -0,0 +1,98 @@ +router: + - + name: A + driver: http + listen: "7777" + host: + - www.eolinker.com + - www.apibee.com + - www.apishop.net + rules: + - location: "/ab" + header: + version: "1.0" + app: goku + user: abc + target: serviceA_rule1 + - location: "*/" + header: + user: abc + token: abc + target: serviceA_rule2 + - location: "*/" + header: + user: abc + target: serviceA_rule3 + - location: "=/abc" + header: + user: abc + target: serviceA_rule4 + - name: B + driver: http + listen: "7777" + host: + - www.eolinker.com + rules: + - location: "/ab" + target: serviceB_rule1 + - location: "*/" + target: serviceB_rule2 + - name: C + driver: http + listen: "7777" + host: + - www.apibee.com + rules: + - location: "/ab" + header: + version: "1.0" + app: goku + target: serviceC_rule1 + - location: "*/" + header: + token: abc + user: abc + query: + token: "123" + target: serviceC_rule2 + - location: "*/" + header: + user: abc + token: abc + query: + token: "123" + token2: "321" + target: serviceC_rule3 + - name: D + driver: http + listen: "7777" + host: + - "*.apishop.net" + rules: + - location: "/ab" + query: + token: "123" + target: serviceD_rule1 + - name: E + driver: http + listen: "80" + rules: + - location: "/ab" + header: + version: "1.0" + app: goku + user: abc + target: serviceE_rule1 + - location: "*/" + header: + user: abc + token: abc + target: serviceE_rule2 + - location: "*/" + header: + user: abc + target: serviceE_rule3 + - location: "=/abc" + header: + user: abc + target: serviceE_rule4 \ No newline at end of file diff --git a/router-http/test_router_manager_newEmployee.yaml b/router-http/test_router_manager_newEmployee.yaml new file mode 100644 index 00000000..f638ca18 --- /dev/null +++ b/router-http/test_router_manager_newEmployee.yaml @@ -0,0 +1,9 @@ +router: + - name: E2 + driver: http + listen: "80" + rules: + - location: "=/abcd" + header: + user: abc + target: serviceE_rule5 \ No newline at end of file diff --git a/router-k/chain.go b/router-k/chain.go new file mode 100644 index 00000000..8e0030c6 --- /dev/null +++ b/router-k/chain.go @@ -0,0 +1,4 @@ +package router + +type Chain struct { +} diff --git a/router-k/checker.go b/router-k/checker.go new file mode 100644 index 00000000..3bb9a899 --- /dev/null +++ b/router-k/checker.go @@ -0,0 +1,21 @@ +package router + +import "regexp" + +type IChecker interface { + Check(v string) bool +} + +func parseChecker(r string) IChecker { + // todo parse checker + + return nil +} + +type Prefix string + +type Regexp struct { + rule string + *regexp.Regexp +} +type Indistinct string diff --git a/router-k/config.go b/router-k/config.go new file mode 100644 index 00000000..2e2b70a1 --- /dev/null +++ b/router-k/config.go @@ -0,0 +1,16 @@ +package router + +import "github.com/eolinker/goku-eosc/service" + +type Rule struct { + Location string + Header []string + Query []string +} + +type Config struct { + Id string + Hosts []string + Target service.IService + Rules []Rule +} diff --git a/router-k/manager.go b/router-k/manager.go new file mode 100644 index 00000000..9fff3659 --- /dev/null +++ b/router-k/manager.go @@ -0,0 +1,98 @@ +package router + +import ( + "context" + "encoding/binary" + "encoding/hex" + "github.com/eolinker/eosc/listener" + "net" + "net/http" + "sync" + "time" +) + +var _ iManager = (*Manager)(nil) +var ( + sign = "" +) + +func init() { + n := time.Now().UnixNano() + data := make([]byte, 8) + binary.PutVarint(data, n) + sign = hex.EncodeToString(data) +} + +type iManager interface { + Add(port int, id string, config *Config) error + Del(port int, id string) error +} + +var manager = NewManager() + +type Manager struct { + locker sync.Mutex + routers IRouters + servers map[int]*http.Server + listeners map[int]net.Listener +} + +func NewManager() *Manager { + return &Manager{ + routers: NewRouters(), + } +} + +func (m *Manager) Add(port int, id string, config *Config) error { + m.locker.Lock() + defer m.locker.Unlock() + router, isCreate, err := m.routers.Set(port, id, config) + if err != nil { + return err + } + if isCreate { + s, has := m.servers[port] + if !has { + s = &http.Server{} + s.Handler = router + l, err := listener.ListenTCP(port, sign) + if err != nil { + return err + } + go s.Serve(l) + m.servers[port] = s + m.listeners[port] = l + } + } + return nil +} + +func (m *Manager) Del(port int, id string) error { + m.locker.Lock() + defer m.locker.Unlock() + if r, has := m.routers.Del(port, id); has { + if r.Count() == 0 { + if s, has := m.servers[port]; has { + ctx := context.Background() + err := s.Shutdown(ctx) + if err != nil { + return err + } + delete(m.servers, port) + m.listeners[port].Close() + delete(m.listeners, port) + } + } + } + + return nil + +} + +func Add(port int, id string, config *Config) error { + return manager.Add(port, id, config) +} + +func Del(port int, id string) error { + return manager.Del(port, id) +} diff --git a/router-k/match.go b/router-k/match.go new file mode 100644 index 00000000..6389e691 --- /dev/null +++ b/router-k/match.go @@ -0,0 +1,20 @@ +package router + +import "net/http" + +type IMatcher interface { + Match(req *http.Request) (http.Handler, bool) +} + +type Matcher struct { + reader IReader + checker IChecker +} + +func (m *Matcher) Match(req *http.Request) (http.Handler, bool) { + v, has := m.reader.Reader(req) + if !has { + return nil, false + } + +} diff --git a/router-k/reader.go b/router-k/reader.go new file mode 100644 index 00000000..62f3eb5a --- /dev/null +++ b/router-k/reader.go @@ -0,0 +1,7 @@ +package router + +import "net/http" + +type IReader interface { + Reader(req *http.Request) (string, bool) +} diff --git a/router-k/router.go b/router-k/router.go new file mode 100644 index 00000000..1beaa50f --- /dev/null +++ b/router-k/router.go @@ -0,0 +1,84 @@ +package router + +import ( + "github.com/eolinker/eosc/internal" + "net/http" + "sync" +) + +var _ IRouter = (*Router)(nil) + +type IRouter interface { + SetRouter(id string, config *Config) error + Count() int + Del(id string) int + http.Handler +} + +type Router struct { + locker sync.Locker + data internal.IUntyped + match IMatcher +} + +func NewRouter() *Router { + return &Router{ + locker: &sync.Mutex{}, + } +} + +func (r *Router) Count() int { + return r.data.Count() +} + +func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { + h, has := r.match.Match(req) + if !has { + http.NotFound(w, req) + return + } + h.ServeHTTP(w, req) +} + +func (r *Router) SetRouter(id string, config *Config) error { + r.locker.Lock() + defer r.locker.Unlock() + data := r.data.Clone() + data.Set(id, config) + list := data.List() + cs := make([]*Config, 0, len(list)) + for _, i := range list { + cs = append(cs, i.(*Config)) + } + matcher, err := parse(cs) + if err != nil { + return err + } + r.match = matcher + r.data = data + return nil +} + +func (r *Router) Del(id string) int { + r.locker.Lock() + defer r.locker.Unlock() + + data := r.data.Clone() + data.Del(id) + if data.Count() == 0 { + r.match = nil + } else { + list := data.List() + cs := make([]*Config, 0, len(list)) + for _, i := range list { + cs = append(cs, i.(*Config)) + } + m, err := parse(cs) + if err != nil { + return r.data.Count() + } + r.match = m + } + + return r.data.Count() +} diff --git a/router-k/routers.go b/router-k/routers.go new file mode 100644 index 00000000..60396f62 --- /dev/null +++ b/router-k/routers.go @@ -0,0 +1,71 @@ +package router + +import ( + "github.com/eolinker/eosc/internal" + "strconv" +) + +var _ IRouters = (*Routers)(nil) + +type IRouters interface { + Set(port int, id string, conf *Config) (IRouter, bool, error) + Get(port int) (IRouter, bool) + Del(port int, id string) (IRouter, bool) +} +type Routers struct { + data internal.IUntyped +} + +func (rs *Routers) Set(port int, id string, conf *Config) (IRouter, bool, error) { + name := strconv.Itoa(port) + r, has := rs.data.Get(name) + + if !has { + router := NewRouter() + err := router.SetRouter(id, conf) + if err != nil { + return nil, false, err + } + rs.data.Set(id, router) + return router, true, nil + } else { + router := r.(IRouter) + err := router.SetRouter(id, conf) + if err != nil { + return nil, false, err + } + return router, false, nil + } + +} + +func NewRouters() *Routers { + return &Routers{ + data: internal.NewUntyped(), + } +} + +func (rs *Routers) Get(port int) (IRouter, bool) { + name := strconv.Itoa(port) + r, has := rs.data.Get(name) + if !has { + var router IRouter = NewRouter() + rs.data.Set(name, router) + return router, true + } + return r.(IRouter), false +} + +func (rs *Routers) Del(port int, id string) (IRouter, bool) { + name := strconv.Itoa(port) + if i, has := rs.data.Get(name); has { + r := i.(IRouter) + count := r.Del(id) + if count == 0 { + rs.data.Del(name) + } + return r, true + } + return nil, false + +} diff --git a/router-k/server.go b/router-k/server.go new file mode 100644 index 00000000..f1d1fb59 --- /dev/null +++ b/router-k/server.go @@ -0,0 +1,13 @@ +package router + +var _ iServer = (*Server)(nil) +var _ iServers = (*Servers)(nil) + +type iServer interface { +} +type Server struct { +} +type iServers interface { +} +type Servers struct { +} diff --git a/router-k/tree.go b/router-k/tree.go new file mode 100644 index 00000000..a90ad812 --- /dev/null +++ b/router-k/tree.go @@ -0,0 +1,15 @@ +package router + +import "net/http" + +type Tree struct { +} + +func (t *Tree) Match(req *http.Request) (http.Handler, bool) { + panic("implement me") +} + +func parse(cs []*Config) (IMatcher, error) { + //todo parse config to tree + return &Tree{}, nil +} diff --git a/router/config.go b/router/config.go new file mode 100644 index 00000000..f94f7934 --- /dev/null +++ b/router/config.go @@ -0,0 +1,26 @@ +package router + +const ( + group = "goku" + version = "v1.0" + label = "http路由" + desc = "http路由" + name = "http" + profession = "router" +) + +type Config struct { + ID string `json:"id"` + Name string `json:"name" yaml:"name"` + Driver string `json:"driver" yaml:"driver"` + Listen string `json:"listen" yaml:"listen"` + Host []string `json:"host" yaml:"host"` + Rules []Rule `json:"rules" yaml:"rules"` +} + +type Rule struct { + Location string `json:"location" yaml:"location"` + Header map[string]string `json:"header" yaml:"header"` + Query map[string]string `json:"query" yaml:"query"` + Target string `json:"target" target:"target"` +} diff --git a/router/employee.go b/router/employee.go new file mode 100644 index 00000000..a7f9f722 --- /dev/null +++ b/router/employee.go @@ -0,0 +1,37 @@ +package router + +func NewEmployee(id string, name string, driver string, config string) *Employee { + return &Employee{ + id: id, + name: name, + driver: driver, + config: config, + } +} + +type Employee struct { + id string + name string + driver string + config string +} + +func (e *Employee) Technique() []string { + return []string{} +} + +func (e *Employee) ID() string { + return e.id +} + +func (e *Employee) Name() string { + return e.name +} + +func (e *Employee) Driver() string { + return e.driver +} + +func (e *Employee) Config() string { + return e.config +} diff --git a/router/factory.go b/router/factory.go new file mode 100644 index 00000000..7e99a831 --- /dev/null +++ b/router/factory.go @@ -0,0 +1,59 @@ +package router + +import ( + "fmt" + + "github.com/eolinker/eosc" +) + +//Factory 路由工厂,实现IDriverFactory方法 +type Factory struct { + id string + name string + group string + profession string + version string +} + +func (f *Factory) Profession() string { + return f.profession +} + +//ID 获取工厂ID +func (f *Factory) ID() string { + return f.id +} + +//Name 获取工厂名称 +func (f *Factory) Name() string { + return f.name +} + +//Group 获取工厂分组 +func (f *Factory) Group() string { + return f.group +} + +//Version 获取版本号 +func (f *Factory) Version() string { + return f.version +} + +func Register() error { + return nil +} + +func NewFactory() *Factory { + return &Factory{ + id: fmt.Sprintf("%s:%s_%s:%s", group, profession, name, version), + name: name, + group: group, + profession: profession, + version: version, + } +} + +func (f *Factory) Create(name string) (eosc.IProfessionDriver, error) { + r := NewRouter(name) + return r, nil +} diff --git a/router/register.go b/router/register.go new file mode 100644 index 00000000..02f6d2d2 --- /dev/null +++ b/router/register.go @@ -0,0 +1,55 @@ +package router + +import "github.com/eolinker/eosc" + +var ( + defaultDriverRegister iDriverRegister = newDriverManager() +) + +type iDriverRegister interface { + RegisterDriverByKey(key string, factory IRouterHttpFactory) + GetDriverByKey(key string) (IRouterHttpFactory, bool) + Keys() []string +} + +type DriverRegister struct { + register eosc.IRegister + keys []string +} + +func newDriverManager() *DriverRegister { + return &DriverRegister{ + register: eosc.NewRegister(), + keys: make([]string, 0, 10), + } +} + +func (dm *DriverRegister) GetDriverByKey(key string) (IRouterHttpFactory, bool) { + o, has := dm.register.Get(key) + if has { + f, ok := o.(IRouterHttpFactory) + return f, ok + } + return nil, false +} + +func (dm *DriverRegister) RegisterDriverByKey(key string, factory IRouterHttpFactory) { + dm.register.Register(key, factory, true) + dm.keys = append(dm.keys, key) +} + +func (dm *DriverRegister) Keys() []string { + return dm.keys +} + +func RegisterFactory(key string, factory IRouterHttpFactory) { + defaultDriverRegister.RegisterDriverByKey(key, factory) +} + +func Get(key string) (IRouterHttpFactory, bool) { + return defaultDriverRegister.GetDriverByKey(key) +} + +func Keys() []string { + return defaultDriverRegister.Keys() +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 00000000..5dc6a187 --- /dev/null +++ b/router/router.go @@ -0,0 +1,73 @@ +package router + +import ( + "fmt" + "net/http" + "net/url" + "reflect" + + "github.com/eolinker/eosc" +) + +type IRouterHttpFactory interface { +} + +type IRouterManager interface { + Delete(port int, id string) error + StartAllServer() + ShutDownAllServer() + StartServer(port int) error + ShutDownServer(port int) error +} + +type IRouterHandler interface { + Match(request *http.Request) (string, bool) +} + +type IRouter interface { + Delete(id string) error + Serve() error + ShutDown() error +} + +type IRouterRule interface { + Location() string + Host() string + Header() map[string]string + Query() url.Values +} + +func NewRouter(name string) *Router { + return &Router{ + id: fmt.Sprintf("%s:%s_%s:%s", group, profession, name, version), + name: name, + } +} + +//Router 路由模块 +type Router struct { + id string + name string + label string +} + +func (r *Router) ConfigType() reflect.Type { + panic("implement me") +} + +func (r *Router) Create(id, name string, v interface{}, workers map[eosc.RequireId]interface{}) (eosc.IWorker, error) { + panic("implement me") +} + +func (r *Router) Name() string { + return r.name +} + +func (r *Router) Check(config string) error { + return nil + panic("implement me") +} + +func (r *Router) Render() eosc.Render { + panic("implement me") +} diff --git a/service-http/config.go b/service-http/config.go new file mode 100644 index 00000000..c726b08a --- /dev/null +++ b/service-http/config.go @@ -0,0 +1,17 @@ +package service_http + +import ( + "github.com/eolinker/eosc" +) + +type Config struct { + id string + Name string `json:"name"` + Driver string `json:"driver"` + Desc string `json:"desc"` + Timeout int64 `json:"timeout"` + Retry int `json:"retry"` + Scheme string `json:"scheme"` + RewriteUrl string `json:"rewrite_url"` + Upstream eosc.RequireId `json:"upstream" skill:"github.com/eolinker/goku-eosc/upstream.upstream.IUpstream"` +} diff --git a/service-http/driver.go b/service-http/driver.go new file mode 100644 index 00000000..ace95d17 --- /dev/null +++ b/service-http/driver.go @@ -0,0 +1,73 @@ +package service_http + +import ( + "errors" + "fmt" + "reflect" + "time" + + "github.com/eolinker/goku-eosc/upstream" + + "github.com/eolinker/eosc" +) + +const ( + driverName = "http" +) + +var ( + ErrorStructType = "error struct type: %s, need struct type: %s" +) + +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type + params map[string]string +} + +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]interface{}) (eosc.IWorker, error) { + cfg, ok := v.(*Config) + if !ok { + return nil, errors.New(fmt.Sprintf("error struct type: %s, need struct type: %s", eosc.TypeNameOf(v), d.configType)) + } + if work, has := workers[cfg.Upstream]; has { + w := &serviceWorker{ + id: id, + name: name, + driver: cfg.Driver, + desc: cfg.Desc, + timeout: time.Duration(cfg.Timeout) * time.Millisecond, + rewriteUrl: cfg.RewriteUrl, + retry: cfg.Retry, + scheme: cfg.Scheme, + upstream: work.(upstream.IUpstream), + } + return w, nil + } else { + work, has = workers[eosc.RequireId(fmt.Sprintf("%s@%s", cfg.Upstream, "upstream"))] + if has { + w := &serviceWorker{ + id: id, + name: name, + driver: cfg.Driver, + desc: cfg.Desc, + timeout: time.Duration(cfg.Timeout) * time.Millisecond, + rewriteUrl: cfg.RewriteUrl, + retry: cfg.Retry, + scheme: cfg.Scheme, + upstream: work.(upstream.IUpstream), + } + return w, nil + } + } + return nil, errors.New("fail to create serviceWorker") +} diff --git a/service-http/factory.go b/service-http/factory.go new file mode 100644 index 00000000..b9ed70d7 --- /dev/null +++ b/service-http/factory.go @@ -0,0 +1,44 @@ +package service_http + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +func Register() { + eosc.DefaultProfessionDriverRegister.RegisterProfessionDriver("eolinker:goku:service_http", NewFactory()) +} + +type factory struct { + profession string + name string + label string + desc string + params map[string]string +} + +func NewFactory() *factory { + return &factory{} +} + +func (f *factory) ExtendInfo() eosc.ExtendInfo { + return eosc.ExtendInfo{ + ID: "eolinker:goku:service_http", + Group: "eolinker", + Project: "goku", + Name: "service_http", + } +} + +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]string) (eosc.IProfessionDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + params: params, + }, nil +} diff --git a/service-http/service.go b/service-http/service.go new file mode 100644 index 00000000..0117fdcb --- /dev/null +++ b/service-http/service.go @@ -0,0 +1,119 @@ +package service_http + +import ( + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/upstream" + + http_context "github.com/eolinker/eosc/node/http-context" + + "github.com/eolinker/goku-eosc/service" +) + +type serviceWorker struct { + id string + name string + driver string + desc string + timeout time.Duration + rewriteUrl string + retry int + scheme string + proxyAddr string + upstream upstream.IUpstream +} + +func (s *serviceWorker) Id() string { + return s.id +} + +func (s *serviceWorker) Start() error { + return nil +} + +func (s *serviceWorker) Reset(conf interface{}, workers map[eosc.RequireId]interface{}) error { + data, ok := conf.(*Config) + if !ok { + return errors.New(fmt.Sprintf(ErrorStructType, eosc.TypeNameOf(conf), eosc.TypeNameOf((*Config)(nil)))) + } + if worker, has := workers[data.Upstream]; has { + s.desc = data.Desc + s.timeout = time.Duration(data.Timeout) * time.Millisecond + s.rewriteUrl = data.RewriteUrl + s.retry = data.Retry + s.scheme = data.Scheme + u, ok := worker.(upstream.IUpstream) + if ok { + s.upstream = u + return nil + } + } else { + worker, has = workers[eosc.RequireId(fmt.Sprintf("%s@%s", data.Upstream, "upstream"))] + if has { + s.desc = data.Desc + s.timeout = time.Duration(data.Timeout) * time.Millisecond + s.rewriteUrl = data.RewriteUrl + s.retry = data.Retry + s.scheme = data.Scheme + u, ok := worker.(upstream.IUpstream) + if ok { + s.upstream = u + return nil + } + return nil + } + } + return errors.New("fail to create serviceWorker") + +} + +func (s *serviceWorker) Stop() error { + return nil +} + +func (s *serviceWorker) CheckSkill(skill string) bool { + return service.CheckSkill(skill) +} + +func (s *serviceWorker) Name() string { + return s.name +} + +func (s *serviceWorker) Desc() string { + return s.desc +} + +func (s *serviceWorker) Retry() int { + return s.retry +} + +func (s *serviceWorker) Timeout() time.Duration { + return s.timeout +} + +func (s *serviceWorker) Scheme() string { + return s.scheme +} + +func (s *serviceWorker) ProxyAddr() string { + return s.proxyAddr +} + +func (s *serviceWorker) Handle(w http.ResponseWriter, r *http.Request, router service.IRouterRule) error { + // 构造context + ctx := http_context.NewContext(r, w) + // 设置目标URL + ctx.ProxyRequest.SetTargetURL(recombinePath(r.URL.Path, router.Location(), s.rewriteUrl)) + s.upstream.Send(ctx, s) + return nil +} + +func recombinePath(requestURL, location, targetURL string) string { + new := strings.Replace(requestURL, location, "", 1) + return fmt.Sprintf("%s/%s", strings.TrimSuffix(targetURL, "/"), strings.TrimPrefix(new, "/")) +} diff --git a/service/service.go b/service/service.go new file mode 100644 index 00000000..a75c7ca4 --- /dev/null +++ b/service/service.go @@ -0,0 +1,34 @@ +// github.com/eolinker/goku-eosc/service.service.IService + +package service + +import ( + "net/http" + "net/url" + "time" +) + +func CheckSkill(skill string) bool { + return skill == "github.com/eolinker/goku-eosc/service.service.IService" +} + +// IService github.com/eolinker/goku-eosc/service.service.IService +type IService interface { + Handle(w http.ResponseWriter, r *http.Request, router IRouterRule) error +} + +type IRouterRule interface { + Location() string + Host() string + Header() map[string]string + Query() url.Values +} + +type IServiceDetail interface { + Name() string + Desc() string + Retry() int + Timeout() time.Duration + Scheme() string + ProxyAddr() string +} diff --git a/service/service_test.go b/service/service_test.go new file mode 100644 index 00000000..4edd4d0e --- /dev/null +++ b/service/service_test.go @@ -0,0 +1,22 @@ +package service + +import ( + "fmt" + "reflect" + "testing" +) + +func TestService(t *testing.T) { + fmt.Println(TypeNameOf((*IService)(nil))) +} + +func TypeNameOf(v interface{}) string { + return TypeName(reflect.TypeOf(v)) +} + +func TypeName(t reflect.Type) string { + if t.Kind() == reflect.Ptr { + return TypeName(t.Elem()) + } + return fmt.Sprintf("%s.%s", t.PkgPath(), t.String()) +} diff --git a/stores/register.go b/stores/register.go new file mode 100644 index 00000000..a9b01d32 --- /dev/null +++ b/stores/register.go @@ -0,0 +1,55 @@ +package stores + +import "github.com/eolinker/eosc" + +var ( + defaultDriverRegister iDriverRegister = newDriverManager() +) + +type iDriverRegister interface { + RegisterDriverByKey(key string, factory IStoresFactory) + GetDriverByKey(key string) (IStoresFactory, bool) + Keys() []string +} + +type DriverRegister struct { + register eosc.IRegister + keys []string +} + +func newDriverManager() *DriverRegister { + return &DriverRegister{ + register: eosc.NewRegister(), + keys: make([]string, 0, 10), + } +} + +func (dm *DriverRegister) GetDriverByKey(key string) (IStoresFactory, bool) { + o, has := dm.register.Get(key) + if has { + f, ok := o.(IStoresFactory) + return f, ok + } + return nil, false +} + +func (dm *DriverRegister) RegisterDriverByKey(key string, factory IStoresFactory) { + dm.register.Register(key, factory, true) + dm.keys = append(dm.keys, key) +} + +func (dm *DriverRegister) Keys() []string { + return dm.keys +} + +func RegisterFactory(key string, factory IStoresFactory) { + defaultDriverRegister.RegisterDriverByKey(key, factory) +} + +func Get(key string) (IStoresFactory, bool) { + return defaultDriverRegister.GetDriverByKey(key) +} + +func Keys() []string { + return defaultDriverRegister.Keys() +} diff --git a/stores/stores.go b/stores/stores.go new file mode 100644 index 00000000..65708125 --- /dev/null +++ b/stores/stores.go @@ -0,0 +1,8 @@ +package stores + +import "github.com/eolinker/eosc" + +type IStoresFactory interface{ + CreateStore(config map[string]string) (eosc.IStore, error) +} + diff --git a/stores/yaml/init.go b/stores/yaml/init.go new file mode 100644 index 00000000..0ad32035 --- /dev/null +++ b/stores/yaml/init.go @@ -0,0 +1,90 @@ +package yaml + +import ( + "encoding/json" + "errors" + "io/ioutil" + + "github.com/ghodss/yaml" + + "github.com/eolinker/eosc" + "github.com/eolinker/eosc/store" +) + +const ( + mode = "yaml" + keyInclude = ":include" +) + +func Register() error { + store.RegisterStoreFactory(mode, CreateStore) + return nil +} + +//CreateStore 创建store +func CreateStore(config map[string]string) (eosc.IStore, error) { + store := NewStore() + path, ok := config["conf"] + if !ok { + return nil, errors.New("conf path is empty") + } + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + convertData, err := yaml.YAMLToJSON(data) + if err != nil { + return nil, err + } + var cfg Employees + err = json.Unmarshal(convertData, &cfg) + if err != nil { + return nil, err + } + + store.SetEmployee(cfg) + return store, nil +} + +type Employees map[string][]interface{} + +//TODO +//func Register() { +// stores.RegisterFactory(mode, newStoreYamlFactory()) +//} +// +//type storeYamlFactory struct { +//} +// +//func newStoreYamlFactory() *storeYamlFactory { +// return &storeYamlFactory{} +//} +// +////CreateStore 创建store +//func (s *storeYamlFactory) CreateStore(config map[string]string) (eosc.IStore, error) { +// store := NewStore() +// path, ok := config["conf"] +// if !ok { +// return nil, errors.New("conf path is empty") +// } +// data, err := ioutil.ReadFile(path) +// if err != nil { +// return nil, err +// } +// +// convertData, err := yaml.YAMLToJSON(data) +// if err != nil { +// return nil, err +// } +// var cfg Employees +// err = json.Unmarshal(convertData, &cfg) +// if err != nil { +// return nil, err +// } +// +// store.SetEmployee(cfg) +// return store, nil +//} +// +//type Employees map[string][]interface{} diff --git a/stores/yaml/store.go b/stores/yaml/store.go new file mode 100644 index 00000000..dbf01a93 --- /dev/null +++ b/stores/yaml/store.go @@ -0,0 +1,89 @@ +package yaml + +import ( + "encoding/json" + "errors" + "reflect" + + "github.com/eolinker/eosc/log" + + "github.com/eolinker/eosc" +) + +func NewStore() *Store { + return &Store{ + employees: make(map[string][]string), + drivers: map[string][]eosc.IStore{}, + canWrite: false, + } +} + +type Store struct { + employees map[string][]string + professions []string + drivers map[string][]eosc.IStore + canWrite bool +} + +func (s *Store) SetEmployee(employees Employees) { + for key, value := range employees { + if _, ok := s.employees[key]; !ok { + s.employees[key] = make([]string, 0, len(value)) + } + for _, v := range value { + val := reflect.ValueOf(v) + result := make(map[string]interface{}) + if val.Kind() == reflect.Map { + m := val.MapRange() + for m.Next() { + result[m.Key().Interface().(string)] = m.Value().Interface() + } + } + data, err := json.Marshal(result) + if err != nil { + log.Error(err) + continue + } + s.employees[key] = append(s.employees[key], string(data)) + } + s.professions = append(s.professions, key) + } +} + +func (s *Store) GetEmployee(profession string) ([]string, error) { + if v, ok := s.employees[profession]; ok { + return v, nil + } + return nil, errors.New("the employee does not exist") +} + +func (s *Store) Professions() []string { + return s.professions +} + +func (s *Store) Info(profession string, nameId string) (string, error) { + panic("implement me") +} + +func (s *Store) All(profession string) (string, error) { + panic("implement me") +} + +func (s *Store) Mode() string { + return mode +} + +func (s *Store) InfoByID(id string) string { + if v, ok := s.employees[id]; ok { + data, err := json.Marshal(v) + if err != nil { + return "" + } + return string(data) + } + return "" +} + +func (s *Store) CanWrite() bool { + return s.canWrite +} diff --git a/upstream-http/config.go b/upstream-http/config.go new file mode 100644 index 00000000..cb0dce43 --- /dev/null +++ b/upstream-http/config.go @@ -0,0 +1,16 @@ +package upstream_http + +import ( + "github.com/eolinker/eosc" +) + +type Config struct { + id string + Name string `json:"name"` + Driver string `json:"driver"` + Desc string `json:"desc"` + Scheme string `json:"scheme"` + Type string `json:"type"` + Config string `json:"config"` + Discovery eosc.RequireId `json:"upstream" skill:"github.com/eolinker/goku-eosc/discovery.discovery.IDiscovery"` +} diff --git a/upstream-http/driver.go b/upstream-http/driver.go new file mode 100644 index 00000000..005f250a --- /dev/null +++ b/upstream-http/driver.go @@ -0,0 +1,62 @@ +package upstream_http + +import ( + "errors" + "fmt" + "reflect" + + "github.com/eolinker/goku-eosc/discovery" + + "github.com/eolinker/eosc" +) + +const ( + driverName = "http_proxy" +) + +var ( + ErrorStructType = "error struct type: %s, need struct type: %s" +) + +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type + params map[string]string +} + +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]interface{}) (eosc.IWorker, error) { + cfg, ok := v.(*Config) + if !ok { + return nil, errors.New(fmt.Sprintf(ErrorStructType, eosc.TypeNameOf(v), d.configType)) + } + if factory, has := workers[cfg.Discovery]; has { + f, ok := factory.(discovery.IDiscovery) + if ok { + app, err := f.GetApp(cfg.Config) + if err != nil { + return nil, err + } + w := &httpUpstream{ + id: id, + name: name, + driver: cfg.Driver, + desc: cfg.Desc, + scheme: cfg.Scheme, + balanceType: cfg.Type, + app: app, + } + return w, nil + } + + } + return nil, errors.New("fail to create upstream worker") +} diff --git a/upstream-http/factory.go b/upstream-http/factory.go new file mode 100644 index 00000000..0b123bac --- /dev/null +++ b/upstream-http/factory.go @@ -0,0 +1,44 @@ +package upstream_http + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +func Register() { + eosc.DefaultProfessionDriverRegister.RegisterProfessionDriver("eolinker:goku:http_proxy", NewFactory()) +} + +type factory struct { + profession string + name string + label string + desc string + params map[string]string +} + +func NewFactory() *factory { + return &factory{} +} + +func (f *factory) ExtendInfo() eosc.ExtendInfo { + return eosc.ExtendInfo{ + ID: "eolinker:goku:upstream_http_proxy", + Group: "eolinker", + Project: "goku", + Name: "http_proxy", + } +} + +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]string) (eosc.IProfessionDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + params: params, + }, nil +} diff --git a/upstream-http/http-proxy/http-proxy-request/request.go b/upstream-http/http-proxy/http-proxy-request/request.go new file mode 100644 index 00000000..347ec230 --- /dev/null +++ b/upstream-http/http-proxy/http-proxy-request/request.go @@ -0,0 +1,239 @@ +package http_proxy_request + +import ( + "bytes" + "crypto/tls" + "errors" + "io" + "net/http" + "net/url" + + http_context "github.com/eolinker/eosc/node/http-context" + + // "fmt" + "time" +) + +//Version 版本号 +var Version = "2.0" + +var ( + transport = &http.Transport{TLSClientConfig: &tls.Config{ + InsecureSkipVerify: false, + }} + httpClient = &http.Client{ + Transport: transport, + } +) + +//SetCert 设置证书配置 +func SetCert(skip int, clientCerts []tls.Certificate) { + tlsConfig := &tls.Config{InsecureSkipVerify: skip == 1, Certificates: clientCerts} + transport.TLSClientConfig = tlsConfig +} + +//Request request +type Request struct { + client *http.Client + method string + url string + headers map[string][]string + body []byte + + queryParams map[string][]string + + timeout time.Duration + httpRequest *http.Request +} + +func (r *Request) SetQueryParams(queryParams url.Values) { + r.queryParams = queryParams +} + +func (r *Request) SetHeaders(headers http.Header) { + r.headers = headers +} + +func (r *Request) Body() []byte { + return r.body +} + +func (r *Request) HttpRequest() *http.Request { + return r.httpRequest +} + +//NewRequest 创建新请求 +func NewRequest(method string, URL *url.URL) (*Request, error) { + if method != "GET" && method != "POST" && method != "PUT" && method != "DELETE" && + method != "HEAD" && method != "OPTIONS" && method != "PATCH" { + return nil, errors.New("Unsupported Request method") + } + return newRequest(method, URL) +} + +//URLPath urlPath +func URLPath(url string, query url.Values) string { + if len(query) < 1 { + return url + } + return url + "?" + query.Encode() +} + +func newRequest(method string, URL *url.URL) (*Request, error) { + var urlPath string + queryParams := make(map[string][]string) + for key, values := range URL.Query() { + queryParams[key] = values + } + urlPath = URL.Scheme + "://" + URL.Host + URL.Path + + r := &Request{ + + client: httpClient, + method: method, + url: urlPath, + headers: make(map[string][]string), + queryParams: queryParams, + } + return r, nil +} + +//SetHeader 设置请求头 +func (r *Request) SetHeader(key string, values ...string) { + if len(values) > 0 { + r.headers[key] = values[:] + } else { + delete(r.headers, key) + } +} + +//Headers 获取请求头 +func (r *Request) Headers() map[string][]string { + headers := make(map[string][]string) + for key, values := range r.headers { + headers[key] = values[:] + } + return headers +} + +//SetQueryParam 设置Query参数 +func (r *Request) SetQueryParam(key string, values ...string) { + if len(values) > 0 { + r.queryParams[key] = values[:] + } else { + delete(r.queryParams, key) + } +} + +//SetTimeout 设置请求超时时间 +func (r *Request) SetTimeout(timeout time.Duration) { + r.timeout = timeout +} + +//send 发送请求 +func (r *Request) Send(ctx *http_context.Context) (*http.Response, error) { + req := r.HttpRequest() + req.Header.Set("Accept-Encoding", "gzip") + req.Header = parseHeaders(r.headers) + + r.client.Timeout = r.timeout + + httpResponse, err := r.client.Do(req) + + return httpResponse, err +} + +//QueryParams 获取query参数 +func (r *Request) QueryParams() map[string][]string { + params := make(map[string][]string) + for key, values := range r.queryParams { + params[key] = values[:] + } + return params +} + +//URLPath 获取完整的URL路径 +func (r *Request) URLPath() string { + if len(r.queryParams) > 0 { + return r.url + "?" + parseParams(r.queryParams).Encode() + } + return r.url +} + +//SetURL 设置URL +func (r *Request) SetURL(url string) { + r.url = url +} + +//SetRawBody 设置源数据 +func (r *Request) SetRawBody(body []byte) { + r.body = body +} + +// 解析请求头 +func parseHeaders(headers map[string][]string) http.Header { + h := http.Header{} + for key, values := range headers { + for _, value := range values { + h.Add(key, value) + } + } + + _, hasAccept := h["Accept"] + if !hasAccept { + h.Add("Accept", "*/*") + } + _, hasAgent := h["User-Agent"] + if !hasAgent { + h.Add("User-Agent", "goku-requests/"+Version) + } + return h +} + +// 解析请求体 +func (r *Request) ParseBody() error { + if r.httpRequest == nil { + var body io.Reader = nil + if len(r.body) > 0 { + body = bytes.NewBuffer(r.body) + } + request, err := http.NewRequest(r.method, r.URLPath(), body) + if err != nil { + return err + } + r.httpRequest = request + } + return nil +} + +// 解析参数 +func parseParams(params map[string][]string) url.Values { + v := url.Values{} + for key, values := range params { + for _, value := range values { + v.Add(key, value) + } + } + return v +} + +// 解析URL +func parseURL(urlPath string) (URL *url.URL, err error) { + URL, err = url.Parse(urlPath) + if err != nil { + return nil, err + } + + if URL.Scheme != "http" && URL.Scheme != "https" { + urlPath = "http://" + urlPath + URL, err = url.Parse(urlPath) + if err != nil { + return nil, err + } + + if URL.Scheme != "http" && URL.Scheme != "https" { + return nil, errors.New("[package requests] only HTTP and HTTPS are accepted") + } + } + return +} diff --git a/upstream-http/http-proxy/r.go b/upstream-http/http-proxy/r.go new file mode 100644 index 00000000..1bd22a09 --- /dev/null +++ b/upstream-http/http-proxy/r.go @@ -0,0 +1,57 @@ +package http_proxy + +import ( + "fmt" + "net/http" + "net/url" + "time" + + http_proxy_request "github.com/eolinker/goku-eosc/upstream-http/http-proxy/http-proxy-request" + http_context "github.com/eolinker/eosc/node/http-context" +) + +//DoRequest 构造请求 +func DoRequest(ctx *http_context.Context, uri string, timeout time.Duration) (*http.Response, error) { + if uri == "" { + return nil, fmt.Errorf("invaild url") + } + + u, err := url.ParseRequestURI(uri) + if err != nil { + return nil, err + } + + req, err := http_proxy_request.NewRequest(ctx.ProxyRequest.Method, u) + if err != nil { + + return nil, err + } + + queryDest := u.Query() + if ctx.ProxyRequest.Queries() != nil { + for k, vs := range ctx.ProxyRequest.Queries() { + for _, v := range vs { + queryDest.Add(k, v) + } + } + } + + req.SetHeaders(ctx.ProxyRequest.Headers()) + + req.SetQueryParams(queryDest) + body, _ := ctx.ProxyRequest.RawBody() + req.SetRawBody(body) + if timeout != 0 { + req.SetTimeout(timeout * time.Millisecond) + } + err = req.ParseBody() + if err != nil { + return nil, err + } + response, err := req.Send(ctx) + if err != nil { + return nil, err + } + + return response, err +} diff --git a/upstream-http/org.go b/upstream-http/org.go new file mode 100644 index 00000000..19b23941 --- /dev/null +++ b/upstream-http/org.go @@ -0,0 +1,62 @@ +package upstream_http + +import ( + "fmt" + "net/http" + "reflect" + + "github.com/eolinker/goku-eosc/service" + + "github.com/eolinker/goku-eosc/upstream/balance" + + http_proxy "github.com/eolinker/goku-eosc/upstream-http/http-proxy" + + http_context "github.com/eolinker/eosc/node/http-context" + + "github.com/eolinker/eosc/utils" +) + +//Http org +type httpUpstream struct { + Scheme string `json:"scheme"` + Nodes []*node `json:"nodes" yaml:"nodes"` + Type string `json:"type"` +} + +type node struct { + IP string `json:"ip" yaml:"ip"` + Port int `json:"port" yaml:"port"` + Labels map[string]string `json:"labels" yaml:"labels"` +} + +//send 请求发送,忽略重试 +func (h *httpUpstream) Send(ctx *http_context.Context, serviceDetail service.IServiceDetail, handler balance.IBalanceHandler) (*http.Response, error) { + var response *http.Response + var err error + path := utils.TrimPrefixAll(ctx.ProxyRequest.TargetURL(), "/") + node, err := handler.Next() + if err != nil { + return nil, err + } + for doTrice := serviceDetail.GetRetry() + 1; doTrice > 0; doTrice-- { + + u := fmt.Sprintf("%s://%s/%s", h.Scheme, node.Addr(), path) + response, err = http_proxy.DoRequest(ctx, u, serviceDetail.GetTimeout()) + + if err != nil { + node, err = handler.Next() + if err != nil { + return nil, err + } + continue + } else { + return response, err + } + } + + return response, err +} + +func GetType() reflect.Type { + return reflect.TypeOf((*httpUpstream)(nil)) +} diff --git a/upstream-http/upstream.go b/upstream-http/upstream.go new file mode 100644 index 00000000..2c2d88b2 --- /dev/null +++ b/upstream-http/upstream.go @@ -0,0 +1,120 @@ +package upstream_http + +import ( + "errors" + "fmt" + "net/http" + "reflect" + + "github.com/eolinker/goku-eosc/upstream" + + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/discovery" + + "github.com/eolinker/goku-eosc/service" + + "github.com/eolinker/goku-eosc/upstream/balance" + + http_proxy "github.com/eolinker/goku-eosc/http-proxy" + + http_context "github.com/eolinker/eosc/node/http-context" + + "github.com/eolinker/eosc/utils" +) + +//Http org +type httpUpstream struct { + id string + name string + driver string + desc string + scheme string + balanceType string + app discovery.IApp + balanceHandler balance.IBalanceHandler +} + +func (h *httpUpstream) Id() string { + return h.id +} + +func (h *httpUpstream) Start() error { + handler, err := balance.GetDriver(h.balanceType, h.app) + if err != nil { + return err + } + h.balanceHandler = handler + return nil +} + +func (h *httpUpstream) Reset(conf interface{}, workers map[eosc.RequireId]interface{}) error { + cfg, ok := conf.(*Config) + if !ok { + return errors.New(fmt.Sprintf(ErrorStructType, eosc.TypeNameOf(conf), eosc.TypeNameOf((*Config)(nil)))) + } + if factory, has := workers[cfg.Discovery]; has { + f, ok := factory.(discovery.IDiscovery) + if ok { + app, err := f.GetApp(cfg.Config) + if err != nil { + return err + } + h.desc = cfg.Desc + h.scheme = cfg.Scheme + h.balanceType = cfg.Type + h.app = app + handler, err := balance.GetDriver(h.balanceType, h.app) + if err != nil { + return err + } + h.balanceHandler = handler + return nil + } + } + return errors.New("fail to create upstream worker") +} + +func (h *httpUpstream) Stop() error { + h.app.Close() + return nil +} + +func (h *httpUpstream) CheckSkill(skill string) bool { + return upstream.CheckSkill(skill) +} + +//send 请求发送,忽略重试 +func (h *httpUpstream) Send(ctx *http_context.Context, serviceDetail service.IServiceDetail) (*http.Response, error) { + var response *http.Response + var err error + path := utils.TrimPrefixAll(ctx.ProxyRequest.TargetURL(), "/") + node, err := h.balanceHandler.Next() + if err != nil { + return nil, err + } + for doTrice := serviceDetail.Retry() + 1; doTrice > 0; doTrice-- { + + u := fmt.Sprintf("%s://%s/%s", h.scheme, node.Addr(), path) + response, err = http_proxy.DoRequest(ctx, u, serviceDetail.Timeout()) + + if err != nil { + if response == nil { + node.Down() + } + h.app.NodeError(node.Id()) + node, err = h.balanceHandler.Next() + if err != nil { + return nil, err + } + continue + } else { + return response, err + } + } + + return response, err +} + +func GetType() reflect.Type { + return reflect.TypeOf((*httpUpstream)(nil)) +} diff --git a/upstream-http_anonymous/config.go b/upstream-http_anonymous/config.go new file mode 100644 index 00000000..f24464be --- /dev/null +++ b/upstream-http_anonymous/config.go @@ -0,0 +1,7 @@ +package upstream_http_anonymous + +type Config struct { + id string + Name string `json:"name"` + Driver string `json:"driver"` +} diff --git a/upstream-http_anonymous/driver.go b/upstream-http_anonymous/driver.go new file mode 100644 index 00000000..6f3efd96 --- /dev/null +++ b/upstream-http_anonymous/driver.go @@ -0,0 +1,39 @@ +package upstream_http_anonymous + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +const ( + driverName = "http_proxy_anonymous" +) + +var ( + ErrorStructType = "error struct type: %s, need struct type: %s" +) + +//driver 实现github.com/eolinker/eosc.eosc.IProfessionDriver接口 +type driver struct { + profession string + name string + driver string + label string + desc string + configType reflect.Type + params map[string]string +} + +func (d *driver) ConfigType() reflect.Type { + return d.configType +} + +func (d *driver) Create(id, name string, v interface{}, workers map[eosc.RequireId]interface{}) (eosc.IWorker, error) { + w := &httpUpstream{ + id: id, + name: name, + driver: driverName, + } + return w, nil +} diff --git a/upstream-http_anonymous/factory.go b/upstream-http_anonymous/factory.go new file mode 100644 index 00000000..633e3449 --- /dev/null +++ b/upstream-http_anonymous/factory.go @@ -0,0 +1,44 @@ +package upstream_http_anonymous + +import ( + "reflect" + + "github.com/eolinker/eosc" +) + +func Register() { + eosc.DefaultProfessionDriverRegister.RegisterProfessionDriver("eolinker:goku:http_proxy", NewFactory()) +} + +type factory struct { + profession string + name string + label string + desc string + params map[string]string +} + +func NewFactory() *factory { + return &factory{} +} + +func (f *factory) ExtendInfo() eosc.ExtendInfo { + return eosc.ExtendInfo{ + ID: "eolinker:goku:upstream_http_proxy", + Group: "eolinker", + Project: "goku", + Name: "http_proxy", + } +} + +func (f *factory) Create(profession string, name string, label string, desc string, params map[string]string) (eosc.IProfessionDriver, error) { + return &driver{ + profession: profession, + name: name, + label: label, + desc: desc, + driver: driverName, + configType: reflect.TypeOf((*Config)(nil)), + params: params, + }, nil +} diff --git a/upstream-http_anonymous/org.go b/upstream-http_anonymous/org.go new file mode 100644 index 00000000..ae357d4a --- /dev/null +++ b/upstream-http_anonymous/org.go @@ -0,0 +1,68 @@ +package upstream_http_anonymous + +import ( + "fmt" + "net/http" + "reflect" + + "github.com/eolinker/goku-eosc/upstream" + + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/service" + + http_proxy "github.com/eolinker/goku-eosc/http-proxy" + + http_context "github.com/eolinker/eosc/node/http-context" + + "github.com/eolinker/eosc/utils" +) + +//Http org +type httpUpstream struct { + id string + name string + driver string +} + +func (h *httpUpstream) Id() string { + return h.id +} + +func (h *httpUpstream) Start() error { + return nil +} + +func (h *httpUpstream) Reset(conf interface{}, workers map[eosc.RequireId]interface{}) error { + return nil +} + +func (h *httpUpstream) Stop() error { + return nil +} + +func (h *httpUpstream) CheckSkill(skill string) bool { + return upstream.CheckSkill(skill) +} + +//send 请求发送,忽略重试 +func (h *httpUpstream) Send(ctx *http_context.Context, serviceDetail service.IServiceDetail) (*http.Response, error) { + var response *http.Response + var err error + path := utils.TrimPrefixAll(ctx.ProxyRequest.TargetURL(), "/") + for doTrice := serviceDetail.Retry() + 1; doTrice > 0; doTrice-- { + u := fmt.Sprintf("%s://%s/%s", serviceDetail.Scheme(), serviceDetail.ProxyAddr(), path) + response, err = http_proxy.DoRequest(ctx, u, serviceDetail.Timeout()) + + if err != nil { + continue + } else { + return response, err + } + } + + return response, err +} + +func GetType() reflect.Type { + return reflect.TypeOf((*httpUpstream)(nil)) +} diff --git a/upstream/balance/balance.go b/upstream/balance/balance.go new file mode 100644 index 00000000..eb77f0f8 --- /dev/null +++ b/upstream/balance/balance.go @@ -0,0 +1,84 @@ +package balance + +import ( + "errors" + + "github.com/eolinker/eosc" + "github.com/eolinker/goku-eosc/discovery" +) + +var ( + defaultDriverRegister iDriverRegister = newDriverManager() +) + +type IBalanceFactory interface { + Create(app discovery.IApp) (IBalanceHandler, error) +} + +type IBalanceHandler interface { + Next() (discovery.INode, error) +} + +type iDriverRegister interface { + RegisterDriverByKey(key string, factory IBalanceFactory) + GetDriverByKey(key string) (IBalanceFactory, bool) + Keys() []string +} + +type DriverRegister struct { + register eosc.IRegister + keys []string +} + +func newDriverManager() *DriverRegister { + return &DriverRegister{ + register: eosc.NewRegister(), + keys: make([]string, 0, 10), + } +} + +func (dm *DriverRegister) GetDriverByKey(key string) (IBalanceFactory, bool) { + o, has := dm.register.Get(key) + if has { + f, ok := o.(IBalanceFactory) + return f, ok + } + return nil, false +} + +func (dm *DriverRegister) RegisterDriverByKey(key string, factory IBalanceFactory) { + dm.register.Register(key, factory, true) + dm.keys = append(dm.keys, key) +} + +func (dm *DriverRegister) Keys() []string { + return dm.keys +} + +func Register(key string, factory IBalanceFactory) { + defaultDriverRegister.RegisterDriverByKey(key, factory) +} + +func Get(key string) (IBalanceFactory, bool) { + return defaultDriverRegister.GetDriverByKey(key) +} + +func Keys() []string { + return defaultDriverRegister.Keys() +} + +func GetDriver(name string, app discovery.IApp) (IBalanceHandler, error) { + factory, ok := Get(name) + if !ok { + for _, key := range Keys() { + factory, ok = Get(key) + if ok { + break + } + } + if factory == nil { + return nil, errors.New("no valid balance handler") + } + } + return factory.Create(app) +} diff --git a/upstream/round-robin/round_robin.go b/upstream/round-robin/round_robin.go new file mode 100644 index 00000000..ce84f711 --- /dev/null +++ b/upstream/round-robin/round_robin.go @@ -0,0 +1,107 @@ +package round_robin + +import ( + "errors" + "strconv" + + "github.com/eolinker/goku-eosc/upstream/balance" + + "github.com/eolinker/goku-eosc/discovery" +) + +const ( + name = "round-robin" +) + +func Register() { + balance.Register(name, newRoundRobinFactory()) +} + +func newRoundRobinFactory() *roundRobinFactory { + return &roundRobinFactory{} +} + +type roundRobinFactory struct { +} + +func (r *roundRobinFactory) Create(app discovery.IApp) (balance.IBalanceHandler, error) { + rr := newRoundRobin(app.Nodes()) + return rr, nil +} + +type node struct { + weight int + discovery.INode +} + +type roundRobin struct { + // nodes 节点列表 + nodes []node + // 节点数量 + size int + // index 当前索引 + index int + // gcdWeight 权重最大公约数 + gcdWeight int + // maxWeight 权重最大值 + maxWeight int +} + +func (r *roundRobin) Next() (discovery.INode, error) { + cw := 0 + for { + r.index = (r.index + 1) % r.size + if r.index == 0 { + cw = cw - r.gcdWeight + if cw <= 0 { + cw = r.maxWeight + if cw == 0 { + return nil, errors.New("") + } + } + } + if r.nodes[r.index].weight >= cw { + return r.nodes[r.index], nil + } + } +} + +func newRoundRobin(nodes []discovery.INode) *roundRobin { + size := len(nodes) + r := &roundRobin{ + nodes: make([]node, 0, size), + size: size, + } + for i, n := range r.nodes { + weight, _ := r.nodes[r.index].GetAttrByName("weight") + w, _ := strconv.Atoi(weight) + if w == 0 { + w = 1 + } + nd := node{w, n} + r.nodes = append(r.nodes, nd) + if i == 0 { + r.maxWeight = w + r.gcdWeight = w + continue + } + r.gcdWeight = gcd(w, r.gcdWeight) + r.maxWeight = max(w, r.maxWeight) + } + return r +} + +func gcd(a, b int) int { + c := a % b + if c == 0 { + return b + } + return gcd(b, c) +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/upstream/upstream.go b/upstream/upstream.go new file mode 100644 index 00000000..a4a0c7d7 --- /dev/null +++ b/upstream/upstream.go @@ -0,0 +1,19 @@ +package upstream + +import ( + "net/http" + + "github.com/eolinker/goku-eosc/service" + http_context "github.com/eolinker/eosc/node/http-context" +) + +func CheckSkill(skill string) bool { + return skill == "github.com/eolinker/goku-eosc/upstream.upstream.IUpstream" +} + +type IUpstream interface { + Send(ctx *http_context.Context, serviceDetail service.IServiceDetail) (*http.Response, error) +} + +type Factory struct { +} diff --git a/upstream/upstream_test.go b/upstream/upstream_test.go new file mode 100644 index 00000000..f5f439fa --- /dev/null +++ b/upstream/upstream_test.go @@ -0,0 +1,14 @@ +package upstream + +import ( + "fmt" + "testing" + + "github.com/eolinker/goku-eosc/discovery" + + "github.com/eolinker/eosc" +) + +func TestUpstream(t *testing.T) { + fmt.Println(eosc.TypeNameOf((*discovery.IDiscovery)(nil))) +}