This commit is contained in:
黄孟柱
2021-07-20 10:46:32 +08:00
parent 04e21cea88
commit a10d4b53a0
106 changed files with 6486 additions and 0 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@
# Dependency directories (remove the comment below to include it)
# vendor/
/.idea/

14
discover-eureka/config.go Normal file
View 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
View 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
View 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
}

View 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"`
}

View 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())
}
}

View 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
View 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
View 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
View 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
View 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
}

View 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
}

View 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())
}
}

View 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
View 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
}

View 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 {}
}

View 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
}

View 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
View 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
}

View 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"`
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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=

View 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
}

View 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
//}

View 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
//}

View 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
}

View 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
}

View 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
View 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
View 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
View 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
View 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
View 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{}
}

View 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)
}

5
main.go Normal file
View File

@@ -0,0 +1,5 @@
package main
func main() {
}

74
router-http/Header.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View 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]
}

View 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
View 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
}

View 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
View 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
}

View 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
View 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
View 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
}

View 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

View 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
View File

@@ -0,0 +1,4 @@
package router
type Chain struct {
}

21
router-k/checker.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View 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
}

View 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
View 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
View 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))
}

View File

@@ -0,0 +1,7 @@
package upstream_http_anonymous
type Config struct {
id string
Name string `json:"name"`
Driver string `json:"driver"`
}

View 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