init
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
/.idea/
|
||||
14
discover-eureka/config.go
Normal file
14
discover-eureka/config.go
Normal file
@@ -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
|
||||
}
|
||||
46
discover-eureka/driver.go
Normal file
46
discover-eureka/driver.go
Normal file
@@ -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
|
||||
|
||||
}
|
||||
|
||||
176
discover-eureka/eureka.go
Normal file
176
discover-eureka/eureka.go
Normal file
@@ -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
|
||||
}
|
||||
80
discover-eureka/eureka_request.go
Normal file
80
discover-eureka/eureka_request.go
Normal file
@@ -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"`
|
||||
}
|
||||
34
discover-eureka/eureka_test.go
Normal file
34
discover-eureka/eureka_test.go
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
43
discover-eureka/factory.go
Normal file
43
discover-eureka/factory.go
Normal file
@@ -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
|
||||
}
|
||||
15
discover-nacos/config.go
Normal file
15
discover-nacos/config.go
Normal file
@@ -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
|
||||
}
|
||||
|
||||
47
discover-nacos/driver.go
Normal file
47
discover-nacos/driver.go
Normal file
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
43
discover-nacos/factory.go
Normal file
43
discover-nacos/factory.go
Normal file
@@ -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
|
||||
}
|
||||
177
discover-nacos/nacos.go
Normal file
177
discover-nacos/nacos.go
Normal file
@@ -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
|
||||
}
|
||||
70
discover-nacos/nacos_request.go
Normal file
70
discover-nacos/nacos_request.go
Normal file
@@ -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
|
||||
}
|
||||
|
||||
27
discover-nacos/nacos_test.go
Normal file
27
discover-nacos/nacos_test.go
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
13
discovery-consul/config.go
Normal file
13
discovery-consul/config.go
Normal file
@@ -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"`
|
||||
}
|
||||
143
discovery-consul/consul.go
Normal file
143
discovery-consul/consul.go
Normal file
@@ -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
|
||||
}
|
||||
29
discovery-consul/consul_test.go
Normal file
29
discovery-consul/consul_test.go
Normal file
@@ -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 {}
|
||||
}
|
||||
48
discovery-consul/driver.go
Normal file
48
discovery-consul/driver.go
Normal file
@@ -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
|
||||
}
|
||||
44
discovery-consul/factory.go
Normal file
44
discovery-consul/factory.go
Normal file
@@ -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
|
||||
}
|
||||
85
discovery-consul/utils.go
Normal file
85
discovery-consul/utils.go
Normal file
@@ -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
|
||||
}
|
||||
23
discovery-static/config.go
Normal file
23
discovery-static/config.go
Normal file
@@ -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"`
|
||||
}
|
||||
44
discovery-static/driver.go
Normal file
44
discovery-static/driver.go
Normal file
@@ -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
|
||||
}
|
||||
44
discovery-static/factory.go
Normal file
44
discovery-static/factory.go
Normal file
@@ -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
|
||||
}
|
||||
212
discovery-static/static.go
Normal file
212
discovery-static/static.go
Normal file
@@ -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
|
||||
}
|
||||
89
discovery/app.go
Normal file
89
discovery/app.go
Normal file
@@ -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()
|
||||
}
|
||||
14
discovery/checker.go
Normal file
14
discovery/checker.go
Normal file
@@ -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
|
||||
}
|
||||
55
discovery/discovery.go
Normal file
55
discovery/discovery.go
Normal file
@@ -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
|
||||
)
|
||||
65
discovery/node.go
Normal file
65
discovery/node.go
Normal file
@@ -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
|
||||
}
|
||||
67
discovery/services.go
Normal file
67
discovery/services.go
Normal file
@@ -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
|
||||
}
|
||||
9
example/config.go
Normal file
9
example/config.go
Normal file
@@ -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"`
|
||||
}
|
||||
26
example/driver.go
Normal file
26
example/driver.go
Normal file
@@ -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))
|
||||
|
||||
}
|
||||
48
example/employee.go
Normal file
48
example/employee.go
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
37
example/register.go
Normal file
37
example/register.go
Normal file
@@ -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
|
||||
}
|
||||
10
go.mod
Normal file
10
go.mod
Normal file
@@ -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
|
||||
)
|
||||
110
go.sum
Normal file
110
go.sum
Normal file
@@ -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=
|
||||
25
health-check-http/agent.go
Normal file
25
health-check-http/agent.go
Normal file
@@ -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
|
||||
}
|
||||
77
health-check-http/check.go
Normal file
77
health-check-http/check.go
Normal file
@@ -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
|
||||
//}
|
||||
108
health-check-http/checker-demo.go
Normal file
108
health-check-http/checker-demo.go
Normal file
@@ -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
|
||||
//}
|
||||
12
health-check-http/config.go
Normal file
12
health-check-http/config.go
Normal file
@@ -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
|
||||
}
|
||||
142
health-check-http/http-check.go
Normal file
142
health-check-http/http-check.go
Normal file
@@ -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
|
||||
}
|
||||
239
http-proxy/http-proxy-request/request.go
Normal file
239
http-proxy/http-proxy-request/request.go
Normal file
@@ -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
|
||||
}
|
||||
57
http-proxy/r.go
Normal file
57
http-proxy/r.go
Normal file
@@ -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
|
||||
}
|
||||
30
http-router/config.go
Normal file
30
http-router/config.go
Normal file
@@ -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)
|
||||
//}
|
||||
32
http-router/driver.go
Normal file
32
http-router/driver.go
Normal file
@@ -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
|
||||
}
|
||||
24
http-router/register.go
Normal file
24
http-router/register.go
Normal file
@@ -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{}
|
||||
}
|
||||
|
||||
34
http-router/router.go
Normal file
34
http-router/router.go
Normal file
@@ -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{}
|
||||
}
|
||||
67
listener-manager/listener.go
Normal file
67
listener-manager/listener.go
Normal file
@@ -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)
|
||||
}
|
||||
74
router-http/Header.go
Normal file
74
router-http/Header.go
Normal file
@@ -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
|
||||
}
|
||||
79
router-http/Host.go
Normal file
79
router-http/Host.go
Normal file
@@ -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
|
||||
}
|
||||
|
||||
135
router-http/Location.go
Normal file
135
router-http/Location.go
Normal file
@@ -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
|
||||
}
|
||||
74
router-http/Query.go
Normal file
74
router-http/Query.go
Normal file
@@ -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
|
||||
}
|
||||
165
router-http/build.go
Normal file
165
router-http/build.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
94
router-http/demo_test.go
Normal file
94
router-http/demo_test.go
Normal file
@@ -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 {}
|
||||
}
|
||||
9
router-http/endpoint.go
Normal file
9
router-http/endpoint.go
Normal file
@@ -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
|
||||
}
|
||||
26
router-http/plan-multi.go
Normal file
26
router-http/plan-multi.go
Normal file
@@ -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
|
||||
}
|
||||
33
router-http/plan-one.go
Normal file
33
router-http/plan-one.go
Normal file
@@ -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
|
||||
}
|
||||
13
router-http/reader-host.go
Normal file
13
router-http/reader-host.go
Normal file
@@ -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]
|
||||
}
|
||||
9
router-http/reader-location.go
Normal file
9
router-http/reader-location.go
Normal file
@@ -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
|
||||
}
|
||||
28
router-http/reader.go
Normal file
28
router-http/reader.go
Normal file
@@ -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
|
||||
}
|
||||
45
router-http/router-http-benchMark_test.go
Normal file
45
router-http/router-http-benchMark_test.go
Normal file
@@ -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"})
|
||||
|
||||
}
|
||||
261
router-http/router-http.go
Normal file
261
router-http/router-http.go
Normal file
@@ -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
|
||||
}
|
||||
164
router-http/router-http_test.go
Normal file
164
router-http/router-http_test.go
Normal file
@@ -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
|
||||
}
|
||||
172
router-http/router-tree.go
Normal file
172
router-http/router-tree.go
Normal file
@@ -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
|
||||
}
|
||||
26
router-http/target.go
Normal file
26
router-http/target.go
Normal file
@@ -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
|
||||
}
|
||||
98
router-http/test_router_manager.yaml
Normal file
98
router-http/test_router_manager.yaml
Normal file
@@ -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
|
||||
9
router-http/test_router_manager_newEmployee.yaml
Normal file
9
router-http/test_router_manager_newEmployee.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
router:
|
||||
- name: E2
|
||||
driver: http
|
||||
listen: "80"
|
||||
rules:
|
||||
- location: "=/abcd"
|
||||
header:
|
||||
user: abc
|
||||
target: serviceE_rule5
|
||||
4
router-k/chain.go
Normal file
4
router-k/chain.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package router
|
||||
|
||||
type Chain struct {
|
||||
}
|
||||
21
router-k/checker.go
Normal file
21
router-k/checker.go
Normal file
@@ -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
|
||||
16
router-k/config.go
Normal file
16
router-k/config.go
Normal file
@@ -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
|
||||
}
|
||||
98
router-k/manager.go
Normal file
98
router-k/manager.go
Normal file
@@ -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)
|
||||
}
|
||||
20
router-k/match.go
Normal file
20
router-k/match.go
Normal file
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
7
router-k/reader.go
Normal file
7
router-k/reader.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package router
|
||||
|
||||
import "net/http"
|
||||
|
||||
type IReader interface {
|
||||
Reader(req *http.Request) (string, bool)
|
||||
}
|
||||
84
router-k/router.go
Normal file
84
router-k/router.go
Normal file
@@ -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()
|
||||
}
|
||||
71
router-k/routers.go
Normal file
71
router-k/routers.go
Normal file
@@ -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
|
||||
|
||||
}
|
||||
13
router-k/server.go
Normal file
13
router-k/server.go
Normal file
@@ -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 {
|
||||
}
|
||||
15
router-k/tree.go
Normal file
15
router-k/tree.go
Normal file
@@ -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
|
||||
}
|
||||
26
router/config.go
Normal file
26
router/config.go
Normal file
@@ -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"`
|
||||
}
|
||||
37
router/employee.go
Normal file
37
router/employee.go
Normal file
@@ -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
|
||||
}
|
||||
59
router/factory.go
Normal file
59
router/factory.go
Normal file
@@ -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
|
||||
}
|
||||
55
router/register.go
Normal file
55
router/register.go
Normal file
@@ -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()
|
||||
}
|
||||
73
router/router.go
Normal file
73
router/router.go
Normal file
@@ -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")
|
||||
}
|
||||
17
service-http/config.go
Normal file
17
service-http/config.go
Normal file
@@ -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"`
|
||||
}
|
||||
73
service-http/driver.go
Normal file
73
service-http/driver.go
Normal file
@@ -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")
|
||||
}
|
||||
44
service-http/factory.go
Normal file
44
service-http/factory.go
Normal file
@@ -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
|
||||
}
|
||||
119
service-http/service.go
Normal file
119
service-http/service.go
Normal file
@@ -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, "/"))
|
||||
}
|
||||
34
service/service.go
Normal file
34
service/service.go
Normal file
@@ -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
|
||||
}
|
||||
22
service/service_test.go
Normal file
22
service/service_test.go
Normal file
@@ -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())
|
||||
}
|
||||
55
stores/register.go
Normal file
55
stores/register.go
Normal file
@@ -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()
|
||||
}
|
||||
8
stores/stores.go
Normal file
8
stores/stores.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package stores
|
||||
|
||||
import "github.com/eolinker/eosc"
|
||||
|
||||
type IStoresFactory interface{
|
||||
CreateStore(config map[string]string) (eosc.IStore, error)
|
||||
}
|
||||
|
||||
90
stores/yaml/init.go
Normal file
90
stores/yaml/init.go
Normal file
@@ -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{}
|
||||
89
stores/yaml/store.go
Normal file
89
stores/yaml/store.go
Normal file
@@ -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
|
||||
}
|
||||
16
upstream-http/config.go
Normal file
16
upstream-http/config.go
Normal file
@@ -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"`
|
||||
}
|
||||
62
upstream-http/driver.go
Normal file
62
upstream-http/driver.go
Normal file
@@ -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")
|
||||
}
|
||||
44
upstream-http/factory.go
Normal file
44
upstream-http/factory.go
Normal file
@@ -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
|
||||
}
|
||||
239
upstream-http/http-proxy/http-proxy-request/request.go
Normal file
239
upstream-http/http-proxy/http-proxy-request/request.go
Normal file
@@ -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
|
||||
}
|
||||
57
upstream-http/http-proxy/r.go
Normal file
57
upstream-http/http-proxy/r.go
Normal file
@@ -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
|
||||
}
|
||||
62
upstream-http/org.go
Normal file
62
upstream-http/org.go
Normal file
@@ -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))
|
||||
}
|
||||
120
upstream-http/upstream.go
Normal file
120
upstream-http/upstream.go
Normal file
@@ -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))
|
||||
}
|
||||
7
upstream-http_anonymous/config.go
Normal file
7
upstream-http_anonymous/config.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package upstream_http_anonymous
|
||||
|
||||
type Config struct {
|
||||
id string
|
||||
Name string `json:"name"`
|
||||
Driver string `json:"driver"`
|
||||
}
|
||||
39
upstream-http_anonymous/driver.go
Normal file
39
upstream-http_anonymous/driver.go
Normal file
@@ -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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user