带阿里标识的版本

This commit is contained in:
robin
2026-02-28 18:55:33 +08:00
parent 150799f41d
commit 5d0b7c7e91
477 changed files with 10813 additions and 4044 deletions

View File

@@ -39,6 +39,7 @@ type HTTPDNSAccessLogListFilter struct {
ClusterId int64
NodeId int64
AppId string
AppIds []string
Domain string
Status string
Keyword string
@@ -215,6 +216,20 @@ func (s *HTTPDNSAccessLogsStore) buildConditions(f HTTPDNSAccessLogListFilter) [
}
if appID := strings.TrimSpace(f.AppId); appID != "" {
conditions = append(conditions, "app_id = '"+escapeString(appID)+"'")
} else if len(f.AppIds) > 0 {
validAppIds := make([]string, 0, len(f.AppIds))
for _, appID := range f.AppIds {
appID = strings.TrimSpace(appID)
if len(appID) == 0 {
continue
}
validAppIds = append(validAppIds, "'"+escapeString(appID)+"'")
}
if len(validAppIds) == 0 {
conditions = append(conditions, "1 = 0")
} else {
conditions = append(conditions, "app_id IN ("+strings.Join(validAppIds, ",")+")")
}
}
if domain := strings.TrimSpace(f.Domain); domain != "" {
conditions = append(conditions, "domain = '"+escapeString(domain)+"'")

View File

@@ -4,6 +4,7 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"strings"
)
type HTTPDNSAccessLogDAO dbs.DAO
@@ -52,6 +53,10 @@ func (this *HTTPDNSAccessLogDAO) CreateLog(tx *dbs.Tx, log *HTTPDNSAccessLog) er
}
func (this *HTTPDNSAccessLogDAO) BuildListQuery(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, domain string, status string, keyword string) *dbs.Query {
return this.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, nil, domain, status, keyword)
}
func (this *HTTPDNSAccessLogDAO) BuildListQueryWithAppIds(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, appIds []string, domain string, status string, keyword string) *dbs.Query {
query := this.Query(tx).DescPk()
if len(day) > 0 {
query = query.Attr("day", day)
@@ -62,6 +67,21 @@ func (this *HTTPDNSAccessLogDAO) BuildListQuery(tx *dbs.Tx, day string, clusterI
if nodeId > 0 {
query = query.Attr("nodeId", nodeId)
}
if len(appIds) > 0 {
validAppIds := make([]string, 0, len(appIds))
for _, value := range appIds {
value = strings.TrimSpace(value)
if len(value) == 0 {
continue
}
validAppIds = append(validAppIds, value)
}
if len(validAppIds) == 0 {
query = query.Where("1 = 0")
} else {
query = query.Attr("appId", validAppIds)
}
}
if len(appId) > 0 {
query = query.Attr("appId", appId)
}
@@ -78,11 +98,24 @@ func (this *HTTPDNSAccessLogDAO) BuildListQuery(tx *dbs.Tx, day string, clusterI
}
func (this *HTTPDNSAccessLogDAO) CountLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, domain string, status string, keyword string) (int64, error) {
return this.BuildListQuery(tx, day, clusterId, nodeId, appId, domain, status, keyword).Count()
return this.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, nil, domain, status, keyword).Count()
}
func (this *HTTPDNSAccessLogDAO) ListLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, domain string, status string, keyword string, offset int64, size int64) (result []*HTTPDNSAccessLog, err error) {
_, err = this.BuildListQuery(tx, day, clusterId, nodeId, appId, domain, status, keyword).
_, err = this.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, nil, domain, status, keyword).
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
func (this *HTTPDNSAccessLogDAO) CountLogsWithAppIds(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, appIds []string, domain string, status string, keyword string) (int64, error) {
return this.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, appIds, domain, status, keyword).Count()
}
func (this *HTTPDNSAccessLogDAO) ListLogsWithAppIds(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, appIds []string, domain string, status string, keyword string, offset int64, size int64) (result []*HTTPDNSAccessLog, err error) {
_, err = this.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, appIds, domain, status, keyword).
Offset(offset).
Limit(size).
Slice(&result).

View File

@@ -85,6 +85,18 @@ func (this *HTTPDNSAppDAO) FindEnabledApp(tx *dbs.Tx, appDbId int64) (*HTTPDNSAp
return one.(*HTTPDNSApp), nil
}
func (this *HTTPDNSAppDAO) FindEnabledAppWithUser(tx *dbs.Tx, appDbId int64, userId int64) (*HTTPDNSApp, error) {
one, err := this.Query(tx).
Pk(appDbId).
State(HTTPDNSAppStateEnabled).
Attr("userId", userId).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSApp), nil
}
func (this *HTTPDNSAppDAO) FindEnabledAppWithAppId(tx *dbs.Tx, appId string) (*HTTPDNSApp, error) {
one, err := this.Query(tx).
State(HTTPDNSAppStateEnabled).
@@ -96,6 +108,31 @@ func (this *HTTPDNSAppDAO) FindEnabledAppWithAppId(tx *dbs.Tx, appId string) (*H
return one.(*HTTPDNSApp), nil
}
func (this *HTTPDNSAppDAO) FindEnabledAppWithAppIdAndUser(tx *dbs.Tx, appId string, userId int64) (*HTTPDNSApp, error) {
one, err := this.Query(tx).
State(HTTPDNSAppStateEnabled).
Attr("appId", appId).
Attr("userId", userId).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSApp), nil
}
func (this *HTTPDNSAppDAO) FindLatestEnabledAppWithNameAndUser(tx *dbs.Tx, name string, userId int64) (*HTTPDNSApp, error) {
one, err := this.Query(tx).
State(HTTPDNSAppStateEnabled).
Attr("name", name).
Attr("userId", userId).
DescPk().
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSApp), nil
}
func (this *HTTPDNSAppDAO) ListEnabledApps(tx *dbs.Tx, offset int64, size int64, keyword string) (result []*HTTPDNSApp, err error) {
query := this.Query(tx).
State(HTTPDNSAppStateEnabled).
@@ -110,6 +147,21 @@ func (this *HTTPDNSAppDAO) ListEnabledApps(tx *dbs.Tx, offset int64, size int64,
return
}
func (this *HTTPDNSAppDAO) ListEnabledAppsWithUser(tx *dbs.Tx, userId int64, offset int64, size int64, keyword string) (result []*HTTPDNSApp, err error) {
query := this.Query(tx).
State(HTTPDNSAppStateEnabled).
Attr("userId", userId).
AscPk()
if len(keyword) > 0 {
query = query.Where("(name LIKE :kw OR appId LIKE :kw)").Param("kw", "%"+keyword+"%")
}
if size > 0 {
query = query.Offset(offset).Limit(size)
}
_, err = query.Slice(&result).FindAll()
return
}
func (this *HTTPDNSAppDAO) CountEnabledApps(tx *dbs.Tx, keyword string) (int64, error) {
query := this.Query(tx).State(HTTPDNSAppStateEnabled)
if len(keyword) > 0 {
@@ -118,6 +170,14 @@ func (this *HTTPDNSAppDAO) CountEnabledApps(tx *dbs.Tx, keyword string) (int64,
return query.Count()
}
func (this *HTTPDNSAppDAO) CountEnabledAppsWithUser(tx *dbs.Tx, userId int64, keyword string) (int64, error) {
query := this.Query(tx).State(HTTPDNSAppStateEnabled).Attr("userId", userId)
if len(keyword) > 0 {
query = query.Where("(name LIKE :kw OR appId LIKE :kw)").Param("kw", "%"+keyword+"%")
}
return query.Count()
}
func (this *HTTPDNSAppDAO) FindAllEnabledApps(tx *dbs.Tx) (result []*HTTPDNSApp, err error) {
_, err = this.Query(tx).
State(HTTPDNSAppStateEnabled).
@@ -126,3 +186,28 @@ func (this *HTTPDNSAppDAO) FindAllEnabledApps(tx *dbs.Tx) (result []*HTTPDNSApp,
FindAll()
return
}
func (this *HTTPDNSAppDAO) FindAllEnabledAppsWithUser(tx *dbs.Tx, userId int64) (result []*HTTPDNSApp, err error) {
_, err = this.Query(tx).
State(HTTPDNSAppStateEnabled).
Attr("userId", userId).
AscPk().
Slice(&result).
FindAll()
return
}
func (this *HTTPDNSAppDAO) ListEnabledAppIdsWithUser(tx *dbs.Tx, userId int64) (result []string, err error) {
apps, err := this.FindAllEnabledAppsWithUser(tx, userId)
if err != nil {
return nil, err
}
result = make([]string, 0, len(apps))
for _, app := range apps {
if app == nil || len(app.AppId) == 0 {
continue
}
result = append(result, app.AppId)
}
return
}

View File

@@ -38,6 +38,27 @@ func init() {
func (this *HTTPDNSAppSecretDAO) InitAppSecret(tx *dbs.Tx, appDbId int64, signEnabled bool) (string, uint64, error) {
signSecret := "ss_" + rands.HexString(12)
now := uint64(time.Now().Unix())
// 兼容历史数据:如果已存在(可能是停用状态)则直接恢复并更新,避免 UNIQUE(appId) 冲突
old, err := this.Query(tx).
Attr("appId", appDbId).
Find()
if err != nil {
return "", 0, err
}
if old != nil {
oldSecret := old.(*HTTPDNSAppSecret)
_, err = this.Query(tx).
Pk(oldSecret.Id).
Set("signEnabled", signEnabled).
Set("signSecret", signSecret).
Set("signUpdatedAt", now).
Set("updatedAt", now).
Set("state", HTTPDNSAppSecretStateEnabled).
Update()
return signSecret, now, err
}
var op = NewHTTPDNSAppSecretOperator()
op.AppId = appDbId
op.SignEnabled = signEnabled
@@ -45,7 +66,7 @@ func (this *HTTPDNSAppSecretDAO) InitAppSecret(tx *dbs.Tx, appDbId int64, signEn
op.SignUpdatedAt = now
op.UpdatedAt = now
op.State = HTTPDNSAppSecretStateEnabled
err := this.Save(tx, op)
err = this.Save(tx, op)
return signSecret, now, err
}

View File

@@ -152,6 +152,22 @@ func (this *HTTPDNSClusterDAO) FindAllEnabledClusters(tx *dbs.Tx) (result []*HTT
return
}
func (this *HTTPDNSClusterDAO) FindDefaultPrimaryClusterId(tx *dbs.Tx) (int64, error) {
col, err := this.Query(tx).
State(HTTPDNSClusterStateEnabled).
Attr("isDefault", true).
Result("id").
AscPk().
FindCol(nil)
if err != nil {
return 0, err
}
if col == nil {
return 0, nil
}
return types.Int64(col), nil
}
func (this *HTTPDNSClusterDAO) UpdateDefaultCluster(tx *dbs.Tx, clusterId int64) error {
err := this.Query(tx).
State(HTTPDNSClusterStateEnabled).

View File

@@ -104,11 +104,16 @@ func (i *HTTPDNSNodeInstaller) Install(dir string, params interface{}, installSt
_, _, _ = i.client.Exec("chown " + i.client.User() + " " + filepath.Dir(configFile))
}
listenAddr := strings.TrimSpace(nodeParams.HTTPDNSListenAddr)
if len(listenAddr) == 0 {
listenAddr = ":443"
}
configData := []byte(`rpc.endpoints: [ ${endpoints} ]
nodeId: "${nodeId}"
secret: "${nodeSecret}"
https.listenAddr: ":443"
https.listenAddr: "${listenAddr}"
https.cert: "${certFile}"
https.key: "${keyFile}"`)
certFileClean := strings.ReplaceAll(certFile, "\\", "/")
@@ -117,6 +122,7 @@ https.key: "${keyFile}"`)
configData = bytes.ReplaceAll(configData, []byte("${endpoints}"), []byte(nodeParams.QuoteEndpoints()))
configData = bytes.ReplaceAll(configData, []byte("${nodeId}"), []byte(nodeParams.NodeId))
configData = bytes.ReplaceAll(configData, []byte("${nodeSecret}"), []byte(nodeParams.Secret))
configData = bytes.ReplaceAll(configData, []byte("${listenAddr}"), []byte(listenAddr))
configData = bytes.ReplaceAll(configData, []byte("${certFile}"), []byte(certFileClean))
configData = bytes.ReplaceAll(configData, []byte("${keyFile}"), []byte(keyFileClean))

View File

@@ -11,6 +11,7 @@ type NodeParams struct {
Secret string
TLSCertData []byte
TLSKeyData []byte
HTTPDNSListenAddr string
IsUpgrading bool // 是否为升级
}

View File

@@ -1,15 +1,19 @@
package installers
package installers
import (
"encoding/json"
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
@@ -136,6 +140,11 @@ func (q *HTTPDNSNodeQueue) InstallNode(nodeId int64, installStatus *models.NodeI
installStatus.ErrorCode = "EMPTY_TLS_CERT"
return err
}
httpdnsListenAddr, err := q.resolveClusterTLSListenAddr(cluster)
if err != nil {
installStatus.ErrorCode = "INVALID_TLS_LISTEN"
return err
}
params := &NodeParams{
Endpoints: apiEndpoints,
@@ -143,6 +152,7 @@ func (q *HTTPDNSNodeQueue) InstallNode(nodeId int64, installStatus *models.NodeI
Secret: node.Secret,
TLSCertData: tlsCertData,
TLSKeyData: tlsKeyData,
HTTPDNSListenAddr: httpdnsListenAddr,
IsUpgrading: isUpgrading,
}
@@ -246,6 +256,37 @@ func (q *HTTPDNSNodeQueue) resolveClusterTLSCertPair(cluster *models.HTTPDNSClus
return nil, nil, errors.New("cluster tls certificate is not configured")
}
func (q *HTTPDNSNodeQueue) resolveClusterTLSListenAddr(cluster *models.HTTPDNSCluster) (string, error) {
const defaultListenAddr = ":443"
if cluster == nil || len(cluster.TLSPolicy) == 0 {
return defaultListenAddr, nil
}
tlsConfig, err := serverconfigs.NewTLSProtocolConfigFromJSON(cluster.TLSPolicy)
if err != nil {
return "", fmt.Errorf("decode cluster tls listen failed: %w", err)
}
for _, listen := range tlsConfig.Listen {
if listen == nil {
continue
}
if err := listen.Init(); err != nil {
return "", fmt.Errorf("invalid cluster tls listen address '%s': %w", listen.PortRange, err)
}
if listen.MinPort <= 0 {
continue
}
host := strings.TrimSpace(listen.Host)
return net.JoinHostPort(host, strconv.Itoa(listen.MinPort)), nil
}
return defaultListenAddr, nil
}
func (q *HTTPDNSNodeQueue) parseSSHInfo(node *models.HTTPDNSNode) (string, int, int64, error) {
if node == nil {
return "", 0, 0, errors.New("node should not be nil")

View File

@@ -4,6 +4,7 @@ import (
"context"
"log"
"strconv"
"strings"
"time"
"github.com/TeaOSLab/EdgeAPI/internal/clickhouse"
@@ -132,16 +133,43 @@ func (s *HTTPDNSAccessLogService) CreateHTTPDNSAccessLogs(ctx context.Context, r
}
func (s *HTTPDNSAccessLogService) ListHTTPDNSAccessLogs(ctx context.Context, req *pb.ListHTTPDNSAccessLogsRequest) (*pb.ListHTTPDNSAccessLogsResponse, error) {
_, _, err := s.ValidateAdminAndUser(ctx, true)
_, userId, err := s.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
allowedAppIds := []string(nil)
if userId > 0 {
if len(strings.TrimSpace(req.GetAppId())) > 0 {
app, err := ensureAppAccessByAppId(s.NullTx(), req.GetAppId(), userId)
if err != nil {
return nil, err
}
if app == nil {
return &pb.ListHTTPDNSAccessLogsResponse{
Logs: []*pb.HTTPDNSAccessLog{},
Total: 0,
}, nil
}
} else {
allowedAppIds, err = models.SharedHTTPDNSAppDAO.ListEnabledAppIdsWithUser(s.NullTx(), userId)
if err != nil {
return nil, err
}
if len(allowedAppIds) == 0 {
return &pb.ListHTTPDNSAccessLogsResponse{
Logs: []*pb.HTTPDNSAccessLog{},
Total: 0,
}, nil
}
}
}
store := clickhouse.NewHTTPDNSAccessLogsStore()
canReadFromClickHouse := s.shouldReadHTTPDNSAccessLogsFromClickHouse() && store.Client().IsConfigured()
canReadFromMySQL := s.shouldReadHTTPDNSAccessLogsFromMySQL()
if canReadFromClickHouse {
resp, listErr := s.listFromClickHouse(ctx, store, req)
resp, listErr := s.listFromClickHouse(ctx, store, req, allowedAppIds)
if listErr == nil {
return resp, nil
}
@@ -158,11 +186,11 @@ func (s *HTTPDNSAccessLogService) ListHTTPDNSAccessLogs(ctx context.Context, req
}, nil
}
total, err := models.SharedHTTPDNSAccessLogDAO.CountLogs(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), req.GetDomain(), req.GetStatus(), req.GetKeyword())
total, err := models.SharedHTTPDNSAccessLogDAO.CountLogsWithAppIds(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), allowedAppIds, req.GetDomain(), req.GetStatus(), req.GetKeyword())
if err != nil {
return nil, err
}
logs, err := models.SharedHTTPDNSAccessLogDAO.ListLogs(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), req.GetDomain(), req.GetStatus(), req.GetKeyword(), req.GetOffset(), req.GetSize())
logs, err := models.SharedHTTPDNSAccessLogDAO.ListLogsWithAppIds(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), allowedAppIds, req.GetDomain(), req.GetStatus(), req.GetKeyword(), req.GetOffset(), req.GetSize())
if err != nil {
return nil, err
}
@@ -212,12 +240,13 @@ func (s *HTTPDNSAccessLogService) ListHTTPDNSAccessLogs(ctx context.Context, req
}, nil
}
func (s *HTTPDNSAccessLogService) listFromClickHouse(ctx context.Context, store *clickhouse.HTTPDNSAccessLogsStore, req *pb.ListHTTPDNSAccessLogsRequest) (*pb.ListHTTPDNSAccessLogsResponse, error) {
func (s *HTTPDNSAccessLogService) listFromClickHouse(ctx context.Context, store *clickhouse.HTTPDNSAccessLogsStore, req *pb.ListHTTPDNSAccessLogsRequest, allowedAppIds []string) (*pb.ListHTTPDNSAccessLogsResponse, error) {
filter := clickhouse.HTTPDNSAccessLogListFilter{
Day: req.GetDay(),
ClusterId: req.GetClusterId(),
NodeId: req.GetNodeId(),
AppId: req.GetAppId(),
AppIds: allowedAppIds,
Domain: req.GetDomain(),
Status: req.GetStatus(),
Keyword: req.GetKeyword(),

View File

@@ -5,8 +5,11 @@ import (
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"strings"
"time"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
)
@@ -18,19 +21,44 @@ type HTTPDNSAppService struct {
}
func (this *HTTPDNSAppService) CreateHTTPDNSApp(ctx context.Context, req *pb.CreateHTTPDNSAppRequest) (*pb.CreateHTTPDNSAppResponse, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
if len(req.Name) == 0 || len(req.AppId) == 0 {
if userId > 0 {
req.UserId = userId
}
appName := strings.TrimSpace(req.Name)
appId := strings.TrimSpace(req.AppId)
if len(appName) == 0 || len(appId) == 0 {
return nil, errors.New("required 'name' and 'appId'")
}
if req.PrimaryClusterId <= 0 {
return nil, errors.New("required 'primaryClusterId'")
}
var appDbId int64
now := time.Now().Unix()
err = this.RunTx(func(tx *dbs.Tx) error {
exists, err := models.SharedHTTPDNSAppDAO.FindEnabledAppWithAppId(tx, strings.TrimSpace(req.AppId))
// 用户端防重复提交:短时间内同用户同应用名仅创建一次。
if req.UserId > 0 {
latest, err := models.SharedHTTPDNSAppDAO.FindLatestEnabledAppWithNameAndUser(tx, appName, req.UserId)
if err != nil {
return err
}
if latest != nil && int64(latest.CreatedAt) >= now-5 {
appDbId = int64(latest.Id)
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(tx, appDbId)
if err != nil {
return err
}
if secret == nil {
_, _, err = models.SharedHTTPDNSAppSecretDAO.InitAppSecret(tx, appDbId, req.SignEnabled)
if err != nil {
return err
}
}
return nil
}
}
exists, err := models.SharedHTTPDNSAppDAO.FindEnabledAppWithAppId(tx, appId)
if err != nil {
return err
}
@@ -38,7 +66,25 @@ func (this *HTTPDNSAppService) CreateHTTPDNSApp(ctx context.Context, req *pb.Cre
return errors.New("appId already exists")
}
appDbId, err = models.SharedHTTPDNSAppDAO.CreateApp(tx, req.Name, strings.TrimSpace(req.AppId), req.PrimaryClusterId, req.BackupClusterId, req.IsOn, req.UserId)
primaryClusterId := req.PrimaryClusterId
backupClusterId := req.BackupClusterId
if primaryClusterId <= 0 || backupClusterId <= 0 {
defaultPrimaryClusterId, defaultBackupClusterId, err := readHTTPDNSDefaultClusterIds(tx)
if err != nil {
return err
}
if primaryClusterId <= 0 {
primaryClusterId = defaultPrimaryClusterId
}
if backupClusterId <= 0 {
backupClusterId = defaultBackupClusterId
}
}
if primaryClusterId > 0 && backupClusterId == primaryClusterId {
backupClusterId = 0
}
appDbId, err = models.SharedHTTPDNSAppDAO.CreateApp(tx, appName, appId, primaryClusterId, backupClusterId, req.IsOn, req.UserId)
if err != nil {
return err
}
@@ -54,13 +100,53 @@ func (this *HTTPDNSAppService) CreateHTTPDNSApp(ctx context.Context, req *pb.Cre
return &pb.CreateHTTPDNSAppResponse{AppDbId: appDbId}, nil
}
func readHTTPDNSDefaultClusterIds(tx *dbs.Tx) (primaryClusterId int64, backupClusterId int64, err error) {
primaryClusterId, err = models.SharedHTTPDNSClusterDAO.FindDefaultPrimaryClusterId(tx)
if err != nil {
return 0, 0, err
}
backupClusterId = 0
backupValueJSON, err := models.SharedSysSettingDAO.ReadSetting(tx, systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId)
if err != nil {
return 0, 0, err
}
if len(backupValueJSON) > 0 {
backupClusterId = types.Int64(string(backupValueJSON))
}
if backupClusterId > 0 {
backupCluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(tx, backupClusterId)
if err != nil {
return 0, 0, err
}
if backupCluster == nil || !backupCluster.IsOn {
backupClusterId = 0
}
}
if primaryClusterId > 0 {
primaryCluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(tx, primaryClusterId)
if err != nil {
return 0, 0, err
}
if primaryCluster == nil || !primaryCluster.IsOn {
primaryClusterId = 0
}
}
if primaryClusterId > 0 && backupClusterId == primaryClusterId {
backupClusterId = 0
}
return primaryClusterId, backupClusterId, nil
}
func (this *HTTPDNSAppService) UpdateHTTPDNSApp(ctx context.Context, req *pb.UpdateHTTPDNSAppRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
oldApp, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, req.AppDbId)
oldApp, err := ensureAppAccess(tx, req.AppDbId, userId)
if err != nil {
return err
}
@@ -68,7 +154,20 @@ func (this *HTTPDNSAppService) UpdateHTTPDNSApp(ctx context.Context, req *pb.Upd
return errors.New("app not found")
}
err = models.SharedHTTPDNSAppDAO.UpdateApp(tx, req.AppDbId, req.Name, req.PrimaryClusterId, req.BackupClusterId, req.IsOn, req.UserId)
targetUserId := req.UserId
if targetUserId <= 0 {
targetUserId = oldApp.UserId
}
if userId > 0 {
targetUserId = userId
}
primaryClusterId := req.PrimaryClusterId
backupClusterId := req.BackupClusterId
if primaryClusterId > 0 && backupClusterId == primaryClusterId {
backupClusterId = 0
}
err = models.SharedHTTPDNSAppDAO.UpdateApp(tx, req.AppDbId, req.Name, primaryClusterId, backupClusterId, req.IsOn, targetUserId)
if err != nil {
return err
}
@@ -86,12 +185,12 @@ func (this *HTTPDNSAppService) UpdateHTTPDNSApp(ctx context.Context, req *pb.Upd
}
func (this *HTTPDNSAppService) DeleteHTTPDNSApp(ctx context.Context, req *pb.DeleteHTTPDNSAppRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, req.AppDbId)
app, err := ensureAppAccess(tx, req.AppDbId, userId)
if err != nil {
return err
}
@@ -146,14 +245,17 @@ func (this *HTTPDNSAppService) DeleteHTTPDNSApp(ctx context.Context, req *pb.Del
}
func (this *HTTPDNSAppService) FindHTTPDNSApp(ctx context.Context, req *pb.FindHTTPDNSAppRequest) (*pb.FindHTTPDNSAppResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, true)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(this.NullTx(), req.AppDbId)
app, err := ensureAppAccess(this.NullTx(), req.AppDbId, userId)
if err != nil {
return nil, err
}
if app == nil {
return &pb.FindHTTPDNSAppResponse{}, nil
}
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(this.NullTx(), req.AppDbId)
if err != nil {
return nil, err
@@ -162,11 +264,16 @@ func (this *HTTPDNSAppService) FindHTTPDNSApp(ctx context.Context, req *pb.FindH
}
func (this *HTTPDNSAppService) ListHTTPDNSApps(ctx context.Context, req *pb.ListHTTPDNSAppsRequest) (*pb.ListHTTPDNSAppsResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, true)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
apps, err := models.SharedHTTPDNSAppDAO.ListEnabledApps(this.NullTx(), req.Offset, req.Size, req.Keyword)
var apps []*models.HTTPDNSApp
if userId > 0 {
apps, err = models.SharedHTTPDNSAppDAO.ListEnabledAppsWithUser(this.NullTx(), userId, req.Offset, req.Size, req.Keyword)
} else {
apps, err = models.SharedHTTPDNSAppDAO.ListEnabledApps(this.NullTx(), req.Offset, req.Size, req.Keyword)
}
if err != nil {
return nil, err
}
@@ -182,13 +289,19 @@ func (this *HTTPDNSAppService) ListHTTPDNSApps(ctx context.Context, req *pb.List
}
func (this *HTTPDNSAppService) FindAllHTTPDNSApps(ctx context.Context, req *pb.FindAllHTTPDNSAppsRequest) (*pb.FindAllHTTPDNSAppsResponse, error) {
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
_, userId, validateErr := this.ValidateAdminAndUser(ctx, true)
if validateErr != nil {
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
return nil, validateErr
}
}
apps, err := models.SharedHTTPDNSAppDAO.FindAllEnabledApps(this.NullTx())
var apps []*models.HTTPDNSApp
var err error
if validateErr == nil && userId > 0 {
apps, err = models.SharedHTTPDNSAppDAO.FindAllEnabledAppsWithUser(this.NullTx(), userId)
} else {
apps, err = models.SharedHTTPDNSAppDAO.FindAllEnabledApps(this.NullTx())
}
if err != nil {
return nil, err
}
@@ -204,12 +317,20 @@ func (this *HTTPDNSAppService) FindAllHTTPDNSApps(ctx context.Context, req *pb.F
}
func (this *HTTPDNSAppService) UpdateHTTPDNSAppSignEnabled(ctx context.Context, req *pb.UpdateHTTPDNSAppSignEnabledRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
err := models.SharedHTTPDNSAppSecretDAO.UpdateSignEnabled(tx, req.AppDbId, req.SignEnabled)
app, err := ensureAppAccess(tx, req.AppDbId, userId)
if err != nil {
return err
}
if app == nil {
return errors.New("app not found")
}
err = models.SharedHTTPDNSAppSecretDAO.UpdateSignEnabled(tx, req.AppDbId, req.SignEnabled)
if err != nil {
return err
}
@@ -222,14 +343,21 @@ func (this *HTTPDNSAppService) UpdateHTTPDNSAppSignEnabled(ctx context.Context,
}
func (this *HTTPDNSAppService) ResetHTTPDNSAppSignSecret(ctx context.Context, req *pb.ResetHTTPDNSAppSignSecretRequest) (*pb.ResetHTTPDNSAppSignSecretResponse, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
var signSecret string
var updatedAt int64
err = this.RunTx(func(tx *dbs.Tx) error {
var err error
app, err := ensureAppAccess(tx, req.AppDbId, userId)
if err != nil {
return err
}
if app == nil {
return errors.New("app not found")
}
signSecret, updatedAt, err = models.SharedHTTPDNSAppSecretDAO.ResetSignSecret(tx, req.AppDbId)
if err != nil {
return err

View File

@@ -16,7 +16,7 @@ type HTTPDNSDomainService struct {
}
func (this *HTTPDNSDomainService) CreateHTTPDNSDomain(ctx context.Context, req *pb.CreateHTTPDNSDomainRequest) (*pb.CreateHTTPDNSDomainResponse, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
@@ -25,6 +25,14 @@ func (this *HTTPDNSDomainService) CreateHTTPDNSDomain(ctx context.Context, req *
}
var domainId int64
err = this.RunTx(func(tx *dbs.Tx) error {
app, err := ensureAppAccess(tx, req.AppDbId, userId)
if err != nil {
return err
}
if app == nil {
return errors.New("app not found")
}
domainId, err = models.SharedHTTPDNSDomainDAO.CreateDomain(tx, req.AppDbId, req.Domain, req.IsOn)
if err != nil {
return err
@@ -38,12 +46,12 @@ func (this *HTTPDNSDomainService) CreateHTTPDNSDomain(ctx context.Context, req *
}
func (this *HTTPDNSDomainService) DeleteHTTPDNSDomain(ctx context.Context, req *pb.DeleteHTTPDNSDomainRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
domain, err := models.SharedHTTPDNSDomainDAO.FindEnabledDomain(tx, req.DomainId)
domain, app, err := ensureDomainAccess(tx, req.DomainId, userId)
if err != nil {
return err
}
@@ -55,7 +63,7 @@ func (this *HTTPDNSDomainService) DeleteHTTPDNSDomain(ctx context.Context, req *
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(domain.AppId), models.HTTPDNSNodeTaskTypeDomainChanged)
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(app.Id), models.HTTPDNSNodeTaskTypeDomainChanged)
})
if err != nil {
return nil, err
@@ -64,12 +72,12 @@ func (this *HTTPDNSDomainService) DeleteHTTPDNSDomain(ctx context.Context, req *
}
func (this *HTTPDNSDomainService) UpdateHTTPDNSDomainStatus(ctx context.Context, req *pb.UpdateHTTPDNSDomainStatusRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
domain, err := models.SharedHTTPDNSDomainDAO.FindEnabledDomain(tx, req.DomainId)
domain, app, err := ensureDomainAccess(tx, req.DomainId, userId)
if err != nil {
return err
}
@@ -81,7 +89,7 @@ func (this *HTTPDNSDomainService) UpdateHTTPDNSDomainStatus(ctx context.Context,
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(domain.AppId), models.HTTPDNSNodeTaskTypeDomainChanged)
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(app.Id), models.HTTPDNSNodeTaskTypeDomainChanged)
})
if err != nil {
return nil, err
@@ -90,11 +98,19 @@ func (this *HTTPDNSDomainService) UpdateHTTPDNSDomainStatus(ctx context.Context,
}
func (this *HTTPDNSDomainService) ListHTTPDNSDomainsWithAppId(ctx context.Context, req *pb.ListHTTPDNSDomainsWithAppIdRequest) (*pb.ListHTTPDNSDomainsWithAppIdResponse, error) {
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
_, userId, validateErr := this.ValidateAdminAndUser(ctx, true)
if validateErr != nil {
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
return nil, validateErr
}
} else if userId > 0 {
app, err := ensureAppAccess(this.NullTx(), req.AppDbId, userId)
if err != nil {
return nil, err
}
if app == nil {
return &pb.ListHTTPDNSDomainsWithAppIdResponse{}, nil
}
}
domains, err := models.SharedHTTPDNSDomainDAO.ListEnabledDomainsWithAppId(this.NullTx(), req.AppDbId, req.Keyword)
if err != nil {

View File

@@ -16,7 +16,7 @@ type HTTPDNSRuleService struct {
}
func (this *HTTPDNSRuleService) CreateHTTPDNSCustomRule(ctx context.Context, req *pb.CreateHTTPDNSCustomRuleRequest) (*pb.CreateHTTPDNSCustomRuleResponse, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
@@ -25,8 +25,16 @@ func (this *HTTPDNSRuleService) CreateHTTPDNSCustomRule(ctx context.Context, req
}
var ruleId int64
err = this.RunTx(func(tx *dbs.Tx) error {
domain, app, err := ensureDomainAccess(tx, req.Rule.DomainId, userId)
if err != nil {
return err
}
if domain == nil || app == nil {
return errors.New("domain not found")
}
rule := &models.HTTPDNSCustomRule{
AppId: uint32(req.Rule.AppId),
AppId: domain.AppId,
DomainId: uint32(req.Rule.DomainId),
RuleName: req.Rule.RuleName,
LineScope: req.Rule.LineScope,
@@ -49,7 +57,7 @@ func (this *HTTPDNSRuleService) CreateHTTPDNSCustomRule(ctx context.Context, req
return err
}
}
return notifyHTTPDNSAppTasksByAppDbId(tx, req.Rule.AppId, models.HTTPDNSNodeTaskTypeRuleChanged)
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(app.Id), models.HTTPDNSNodeTaskTypeRuleChanged)
})
if err != nil {
return nil, err
@@ -58,7 +66,7 @@ func (this *HTTPDNSRuleService) CreateHTTPDNSCustomRule(ctx context.Context, req
}
func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRule(ctx context.Context, req *pb.UpdateHTTPDNSCustomRuleRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
@@ -66,7 +74,7 @@ func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRule(ctx context.Context, req
return nil, errors.New("invalid 'rule.id'")
}
err = this.RunTx(func(tx *dbs.Tx) error {
oldRule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, req.Rule.Id)
oldRule, app, err := ensureRuleAccess(tx, req.Rule.Id, userId)
if err != nil {
return err
}
@@ -101,15 +109,12 @@ func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRule(ctx context.Context, req
return err
}
}
err = notifyHTTPDNSAppTasksByAppDbId(tx, int64(oldRule.AppId), models.HTTPDNSNodeTaskTypeRuleChanged)
err = notifyHTTPDNSAppTasksByAppDbId(tx, int64(app.Id), models.HTTPDNSNodeTaskTypeRuleChanged)
if err != nil {
return err
}
targetAppDbId := req.Rule.AppId
if targetAppDbId <= 0 {
targetAppDbId = int64(oldRule.AppId)
}
targetAppDbId := int64(app.Id)
return notifyHTTPDNSAppTasksByAppDbId(tx, targetAppDbId, models.HTTPDNSNodeTaskTypeRuleChanged)
})
if err != nil {
@@ -119,12 +124,12 @@ func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRule(ctx context.Context, req
}
func (this *HTTPDNSRuleService) DeleteHTTPDNSCustomRule(ctx context.Context, req *pb.DeleteHTTPDNSCustomRuleRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
rule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, req.RuleId)
rule, app, err := ensureRuleAccess(tx, req.RuleId, userId)
if err != nil {
return err
}
@@ -136,7 +141,7 @@ func (this *HTTPDNSRuleService) DeleteHTTPDNSCustomRule(ctx context.Context, req
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(rule.AppId), models.HTTPDNSNodeTaskTypeRuleChanged)
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(app.Id), models.HTTPDNSNodeTaskTypeRuleChanged)
})
if err != nil {
return nil, err
@@ -145,12 +150,12 @@ func (this *HTTPDNSRuleService) DeleteHTTPDNSCustomRule(ctx context.Context, req
}
func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRuleStatus(ctx context.Context, req *pb.UpdateHTTPDNSCustomRuleStatusRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
rule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, req.RuleId)
rule, app, err := ensureRuleAccess(tx, req.RuleId, userId)
if err != nil {
return err
}
@@ -162,7 +167,7 @@ func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRuleStatus(ctx context.Contex
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(rule.AppId), models.HTTPDNSNodeTaskTypeRuleChanged)
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(app.Id), models.HTTPDNSNodeTaskTypeRuleChanged)
})
if err != nil {
return nil, err
@@ -171,11 +176,19 @@ func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRuleStatus(ctx context.Contex
}
func (this *HTTPDNSRuleService) ListHTTPDNSCustomRulesWithDomainId(ctx context.Context, req *pb.ListHTTPDNSCustomRulesWithDomainIdRequest) (*pb.ListHTTPDNSCustomRulesWithDomainIdResponse, error) {
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
_, userId, validateErr := this.ValidateAdminAndUser(ctx, true)
if validateErr != nil {
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
return nil, validateErr
}
} else if userId > 0 {
domain, _, err := ensureDomainAccess(this.NullTx(), req.DomainId, userId)
if err != nil {
return nil, err
}
if domain == nil {
return &pb.ListHTTPDNSCustomRulesWithDomainIdResponse{}, nil
}
}
rules, err := models.SharedHTTPDNSCustomRuleDAO.ListEnabledRulesWithDomainId(this.NullTx(), req.DomainId)
if err != nil {

View File

@@ -60,7 +60,7 @@ type nodeClientInfo struct {
}
func (this *HTTPDNSSandboxService) TestHTTPDNSResolve(ctx context.Context, req *pb.TestHTTPDNSResolveRequest) (*pb.TestHTTPDNSResolveResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, true)
_, userId, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
@@ -73,6 +73,9 @@ func (this *HTTPDNSSandboxService) TestHTTPDNSResolve(ctx context.Context, req *
if err != nil {
return nil, err
}
if userId > 0 && app != nil && app.UserId != userId {
return nil, errors.New("access denied")
}
if app == nil || !app.IsOn {
return &pb.TestHTTPDNSResolveResponse{
Code: "APP_NOT_FOUND_OR_DISABLED",

View File

@@ -0,0 +1,81 @@
package httpdns
import (
"errors"
"strings"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/iwind/TeaGo/dbs"
)
func ensureAppAccess(tx *dbs.Tx, appDbId int64, userId int64) (*models.HTTPDNSApp, error) {
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, appDbId)
if err != nil {
return nil, err
}
if app == nil {
return nil, nil
}
if userId > 0 && app.UserId != userId {
return nil, errors.New("access denied")
}
return app, nil
}
func ensureAppAccessByAppId(tx *dbs.Tx, appId string, userId int64) (*models.HTTPDNSApp, error) {
appId = strings.TrimSpace(appId)
if len(appId) == 0 {
return nil, nil
}
app, err := models.SharedHTTPDNSAppDAO.FindEnabledAppWithAppId(tx, appId)
if err != nil {
return nil, err
}
if app == nil {
return nil, nil
}
if userId > 0 && app.UserId != userId {
return nil, errors.New("access denied")
}
return app, nil
}
func ensureDomainAccess(tx *dbs.Tx, domainId int64, userId int64) (*models.HTTPDNSDomain, *models.HTTPDNSApp, error) {
domain, err := models.SharedHTTPDNSDomainDAO.FindEnabledDomain(tx, domainId)
if err != nil {
return nil, nil, err
}
if domain == nil {
return nil, nil, nil
}
app, err := ensureAppAccess(tx, int64(domain.AppId), userId)
if err != nil {
return nil, nil, err
}
if app == nil {
return nil, nil, nil
}
return domain, app, nil
}
func ensureRuleAccess(tx *dbs.Tx, ruleId int64, userId int64) (*models.HTTPDNSCustomRule, *models.HTTPDNSApp, error) {
rule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, ruleId)
if err != nil {
return nil, nil, err
}
if rule == nil {
return nil, nil, nil
}
app, err := ensureAppAccess(tx, int64(rule.AppId), userId)
if err != nil {
return nil, nil, err
}
if app == nil {
return nil, nil, nil
}
return rule, app, nil
}

View File

@@ -42,10 +42,10 @@ func EnsureClickHouseTables() error {
firewall_rule_group_id UInt64 DEFAULT 0,
firewall_rule_set_id UInt64 DEFAULT 0,
firewall_rule_id UInt64 DEFAULT 0,
request_headers String CODEC(ZSTD(3)) DEFAULT '',
request_body String CODEC(ZSTD(3)) DEFAULT '',
response_headers String CODEC(ZSTD(3)) DEFAULT '',
response_body String CODEC(ZSTD(3)) DEFAULT '',
request_headers String DEFAULT '' CODEC(ZSTD(3)),
request_body String DEFAULT '' CODEC(ZSTD(3)),
response_headers String DEFAULT '' CODEC(ZSTD(3)),
response_body String DEFAULT '' CODEC(ZSTD(3)),
INDEX idx_trace_id trace_id TYPE bloom_filter(0.01) GRANULARITY 4,
INDEX idx_ip ip TYPE bloom_filter(0.01) GRANULARITY 4,
INDEX idx_host host TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
@@ -74,7 +74,7 @@ SETTINGS index_granularity = 8192`,
is_recursive UInt8,
error String CODEC(ZSTD(1)),
ns_route_codes Array(String),
content_json String CODEC(ZSTD(3)) DEFAULT '',
content_json String DEFAULT '' CODEC(ZSTD(3)),
INDEX idx_request_id request_id TYPE bloom_filter(0.01) GRANULARITY 4,
INDEX idx_remote_addr remote_addr TYPE bloom_filter(0.01) GRANULARITY 4,
INDEX idx_question_name question_name TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,

View File

@@ -6,8 +6,10 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
type AppSettingsAction struct {
@@ -52,11 +54,57 @@ func (this *AppSettingsAction) RunGet(params struct {
},
}
clusterResp, err := this.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(this.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
if err != nil {
this.ErrorPage(err)
return
}
clusters := make([]maps.Map, 0, len(clusterResp.GetClusters()))
clusterDomainMap := map[int64]string{}
clusterNameMap := map[int64]string{}
defaultPrimaryClusterId := int64(0)
for _, cluster := range clusterResp.GetClusters() {
clusterId := cluster.GetId()
clusterName := cluster.GetName()
clusters = append(clusters, maps.Map{
"id": clusterId,
"name": clusterName,
"serviceDomain": cluster.GetServiceDomain(),
"isDefault": cluster.GetIsDefault(),
})
clusterDomainMap[clusterId] = cluster.GetServiceDomain()
clusterNameMap[clusterId] = clusterName
if defaultPrimaryClusterId <= 0 && cluster.GetIsDefault() {
defaultPrimaryClusterId = clusterId
}
}
defaultBackupClusterId := int64(0)
defaultBackupResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
})
if err != nil {
this.ErrorPage(err)
return
}
if defaultBackupResp != nil && len(defaultBackupResp.GetValueJSON()) > 0 {
defaultBackupClusterId = types.Int64(string(defaultBackupResp.GetValueJSON()))
}
primaryClusterId := app.GetInt64("primaryClusterId")
backupClusterId := app.GetInt64("backupClusterId")
settings := maps.Map{
"appId": app.GetString("appId"),
"appStatus": app.GetBool("isOn"),
"primaryClusterId": app.GetInt64("primaryClusterId"),
"backupClusterId": app.GetInt64("backupClusterId"),
"primaryClusterId": primaryClusterId,
"backupClusterId": backupClusterId,
"defaultPrimaryClusterId": defaultPrimaryClusterId,
"defaultPrimaryClusterName": clusterNameMap[defaultPrimaryClusterId],
"defaultBackupClusterId": defaultBackupClusterId,
"defaultBackupClusterName": clusterNameMap[defaultBackupClusterId],
"primaryServiceDomain": clusterDomainMap[primaryClusterId],
"backupServiceDomain": clusterDomainMap[backupClusterId],
"signEnabled": app.GetBool("signEnabled"),
"signSecretPlain": app.GetString("signSecretPlain"),
"signSecretMasked": app.GetString("signSecretMasked"),
@@ -64,6 +112,7 @@ func (this *AppSettingsAction) RunGet(params struct {
}
this.Data["app"] = app
this.Data["settings"] = settings
this.Data["clusters"] = clusters
this.Show()
}
@@ -71,11 +120,17 @@ func (this *AppSettingsAction) RunPost(params struct {
AppId int64
AppStatus bool
PrimaryClusterId int64
BackupClusterId int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
if params.PrimaryClusterId > 0 && params.BackupClusterId > 0 && params.PrimaryClusterId == params.BackupClusterId {
this.FailField("backupClusterId", "备用集群不能与主集群相同")
return
}
appResp, err := this.RPC().HTTPDNSAppRPC().FindHTTPDNSApp(this.AdminContext(), &pb.FindHTTPDNSAppRequest{
AppDbId: params.AppId,
@@ -92,8 +147,8 @@ func (this *AppSettingsAction) RunPost(params struct {
_, err = this.RPC().HTTPDNSAppRPC().UpdateHTTPDNSApp(this.AdminContext(), &pb.UpdateHTTPDNSAppRequest{
AppDbId: params.AppId,
Name: appResp.GetApp().GetName(),
PrimaryClusterId: appResp.GetApp().GetPrimaryClusterId(),
BackupClusterId: appResp.GetApp().GetBackupClusterId(),
PrimaryClusterId: params.PrimaryClusterId,
BackupClusterId: params.BackupClusterId,
IsOn: params.AppStatus,
UserId: appResp.GetApp().GetUserId(),
})

View File

@@ -55,6 +55,24 @@ func (this *CreateAction) RunGet(params struct{}) {
}
this.Data["defaultBackupClusterId"] = defaultBackupClusterId
usersResp, err := this.RPC().UserRPC().ListEnabledUsers(this.AdminContext(), &pb.ListEnabledUsersRequest{
Offset: 0,
Size: 10_000,
})
if err != nil {
this.ErrorPage(err)
return
}
users := make([]maps.Map, 0, len(usersResp.GetUsers()))
for _, user := range usersResp.GetUsers() {
users = append(users, maps.Map{
"id": user.GetId(),
"fullname": user.GetFullname(),
"username": user.GetUsername(),
})
}
this.Data["users"] = users
this.Show()
}

View File

@@ -18,6 +18,7 @@ func init() {
Get("/sdk", new(SdkAction)).
GetPost("/sdk/upload", new(SdkUploadAction)).
Post("/sdk/upload/delete", new(SdkUploadDeleteAction)).
Get("/sdk/check", new(SdkCheckAction)).
Get("/sdk/download", new(SdkDownloadAction)).
Get("/sdk/doc", new(SdkDocAction)).
GetPost("/app/settings", new(AppSettingsAction)).

View File

@@ -7,11 +7,20 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
timeutil "github.com/iwind/TeaGo/utils/time"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
func listAppMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map, error) {
clusterNameMap, err := loadHTTPDNSClusterNameMap(parent)
if err != nil {
return nil, err
}
userMapByID, err := loadHTTPDNSUserMap(parent)
if err != nil {
return nil, err
}
resp, err := parent.RPC().HTTPDNSAppRPC().ListHTTPDNSApps(parent.AdminContext(), &pb.ListHTTPDNSAppsRequest{
Offset: 0,
Size: 10_000,
@@ -30,13 +39,22 @@ func listAppMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map,
return nil, err
}
result = append(result, appPBToMap(app, int64(len(domainResp.GetDomains()))))
result = append(result, appPBToMap(app, int64(len(domainResp.GetDomains())), clusterNameMap, userMapByID))
}
return result, nil
}
func findAppMap(parent *actionutils.ParentAction, appDbId int64) (maps.Map, error) {
clusterNameMap, err := loadHTTPDNSClusterNameMap(parent)
if err != nil {
return nil, err
}
userMapByID, err := loadHTTPDNSUserMap(parent)
if err != nil {
return nil, err
}
if appDbId > 0 {
resp, err := parent.RPC().HTTPDNSAppRPC().FindHTTPDNSApp(parent.AdminContext(), &pb.FindHTTPDNSAppRequest{
AppDbId: appDbId,
@@ -51,7 +69,7 @@ func findAppMap(parent *actionutils.ParentAction, appDbId int64) (maps.Map, erro
if err != nil {
return nil, err
}
return appPBToMap(resp.GetApp(), int64(len(domainResp.GetDomains()))), nil
return appPBToMap(resp.GetApp(), int64(len(domainResp.GetDomains())), clusterNameMap, userMapByID), nil
}
}
@@ -259,16 +277,37 @@ func toggleCustomRule(parent *actionutils.ParentAction, ruleId int64, isOn bool)
return err
}
func appPBToMap(app *pb.HTTPDNSApp, domainCount int64) maps.Map {
func appPBToMap(app *pb.HTTPDNSApp, domainCount int64, clusterNameMap map[int64]string, userMapByID map[int64]maps.Map) maps.Map {
signSecret := app.GetSignSecret()
primaryClusterID := app.GetPrimaryClusterId()
backupClusterID := app.GetBackupClusterId()
primaryClusterMap := maps.Map{"id": primaryClusterID, "name": clusterNameMap[primaryClusterID]}
backupClusterMap := maps.Map{"id": backupClusterID, "name": clusterNameMap[backupClusterID]}
var userMap maps.Map
if app.GetUserId() > 0 {
userMap = userMapByID[app.GetUserId()]
if userMap == nil {
userMap = maps.Map{
"id": app.GetUserId(),
"fullname": "用户#" + strconv.FormatInt(app.GetUserId(), 10),
"username": "-",
}
}
}
return maps.Map{
"id": app.GetId(),
"name": app.GetName(),
"appId": app.GetAppId(),
"clusterId": app.GetPrimaryClusterId(),
"primaryClusterId": app.GetPrimaryClusterId(),
"backupClusterId": app.GetBackupClusterId(),
"clusterId": primaryClusterID,
"primaryClusterId": primaryClusterID,
"backupClusterId": backupClusterID,
"primaryCluster": primaryClusterMap,
"backupCluster": backupClusterMap,
"userId": app.GetUserId(),
"user": userMap,
"isOn": app.GetIsOn(),
"domainCount": domainCount,
"sniPolicyText": "隐匿 SNI",
@@ -279,6 +318,39 @@ func appPBToMap(app *pb.HTTPDNSApp, domainCount int64) maps.Map {
}
}
func loadHTTPDNSClusterNameMap(parent *actionutils.ParentAction) (map[int64]string, error) {
resp, err := parent.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(parent.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
if err != nil {
return nil, err
}
result := map[int64]string{}
for _, cluster := range resp.GetClusters() {
result[cluster.GetId()] = cluster.GetName()
}
return result, nil
}
func loadHTTPDNSUserMap(parent *actionutils.ParentAction) (map[int64]maps.Map, error) {
resp, err := parent.RPC().UserRPC().ListEnabledUsers(parent.AdminContext(), &pb.ListEnabledUsersRequest{
Offset: 0,
Size: 10_000,
})
if err != nil {
return nil, err
}
result := map[int64]maps.Map{}
for _, user := range resp.GetUsers() {
result[user.GetId()] = maps.Map{
"id": user.GetId(),
"fullname": user.GetFullname(),
"username": user.GetUsername(),
}
}
return result, nil
}
func defaultLineField(value string) string {
value = strings.TrimSpace(value)
if len(value) == 0 {

View File

@@ -0,0 +1,67 @@
package apps
import (
"net/url"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
)
type SdkCheckAction struct {
actionutils.ParentAction
}
func (this *SdkCheckAction) Init() {
this.Nav("", "", "")
}
func (this *SdkCheckAction) RunGet(params struct {
Platform string
Version string
Type string
}) {
platform, _, _, filename, err := resolveSDKPlatform(params.Platform)
if err != nil {
this.Data["exists"] = false
this.Data["message"] = err.Error()
this.Success()
return
}
t := strings.ToLower(strings.TrimSpace(params.Type))
if t == "doc" {
docPath := findUploadedSDKDocPath(platform, params.Version)
if len(docPath) == 0 {
this.Data["exists"] = false
this.Data["message"] = "Documentation is unavailable, please upload first"
this.Success()
return
}
downloadURL := "/httpdns/apps/sdk/doc?platform=" + url.QueryEscape(platform)
if len(strings.TrimSpace(params.Version)) > 0 {
downloadURL += "&version=" + url.QueryEscape(strings.TrimSpace(params.Version))
}
this.Data["exists"] = true
this.Data["url"] = downloadURL
this.Success()
return
}
archivePath := findSDKArchivePath(filename, params.Version)
if len(archivePath) == 0 {
this.Data["exists"] = false
this.Data["message"] = "SDK package is unavailable, please upload first"
this.Success()
return
}
downloadURL := "/httpdns/apps/sdk/download?platform=" + url.QueryEscape(platform)
if len(strings.TrimSpace(params.Version)) > 0 {
downloadURL += "&version=" + url.QueryEscape(strings.TrimSpace(params.Version))
}
downloadURL += "&raw=1"
this.Data["exists"] = true
this.Data["url"] = downloadURL
this.Success()
}

View File

@@ -1,10 +1,11 @@
package apps
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"os"
"path/filepath"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
)
type SdkDocAction struct {
@@ -17,15 +18,18 @@ func (this *SdkDocAction) Init() {
func (this *SdkDocAction) RunGet(params struct {
Platform string
Version string
}) {
platform, _, readmeRelativePath, _, err := resolveSDKPlatform(params.Platform)
if err != nil {
this.Fail(err.Error())
this.Data["exists"] = false
this.Data["message"] = err.Error()
this.Success()
return
}
var data []byte
uploadedDocPath := findUploadedSDKDocPath(platform)
uploadedDocPath := findUploadedSDKDocPath(platform, params.Version)
if len(uploadedDocPath) > 0 {
data, err = os.ReadFile(uploadedDocPath)
}
@@ -44,11 +48,18 @@ func (this *SdkDocAction) RunGet(params struct {
}
if len(data) == 0 || err != nil {
this.Fail("当前服务器未找到 SDK 集成文档请先在“SDK 集成”页面上传对应平台文档")
this.Data["exists"] = false
this.Data["message"] = "SDK documentation is not found on server, please upload first"
this.Success()
return
}
downloadName := filepath.Base(uploadedDocPath)
if len(downloadName) == 0 || downloadName == "." || downloadName == string(filepath.Separator) {
downloadName = "httpdns-sdk-" + strings.ToLower(platform) + ".md"
}
this.AddHeader("Content-Type", "text/markdown; charset=utf-8")
this.AddHeader("Content-Disposition", "attachment; filename=\"httpdns-sdk-"+strings.ToLower(platform)+"-README.md\";")
this.AddHeader("Content-Disposition", "attachment; filename=\""+downloadName+"\";")
_, _ = this.ResponseWriter.Write(data)
}

View File

@@ -1,9 +1,11 @@
package apps
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"io"
"os"
"path/filepath"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
)
type SdkDownloadAction struct {
@@ -16,30 +18,48 @@ func (this *SdkDownloadAction) Init() {
func (this *SdkDownloadAction) RunGet(params struct {
Platform string
Version string
Raw int
}) {
_, _, _, filename, err := resolveSDKPlatform(params.Platform)
if err != nil {
this.Fail(err.Error())
this.Data["exists"] = false
this.Data["message"] = err.Error()
this.Success()
return
}
archivePath := findSDKArchivePath(filename)
archivePath := findSDKArchivePath(filename, params.Version)
if len(archivePath) == 0 {
this.Fail("当前服务器未找到 SDK 包请先在“SDK 集成”页面上传对应平台包: " + filename)
this.Data["exists"] = false
this.Data["message"] = "SDK archive not found on server, please upload first: " + filename
this.Success()
return
}
fp, err := os.Open(archivePath)
if err != nil {
this.Fail("打开 SDK 包失败: " + err.Error())
this.Data["exists"] = false
this.Data["message"] = "failed to open SDK archive: " + err.Error()
this.Success()
return
}
defer func() {
_ = fp.Close()
}()
downloadName := filepath.Base(archivePath)
if len(downloadName) == 0 || downloadName == "." || downloadName == string(filepath.Separator) {
downloadName = filename
}
if params.Raw == 1 {
this.AddHeader("Content-Type", "application/octet-stream")
this.AddHeader("X-SDK-Filename", downloadName)
} else {
this.AddHeader("Content-Type", "application/zip")
this.AddHeader("Content-Disposition", "attachment; filename=\""+filename+"\";")
this.AddHeader("Content-Disposition", "attachment; filename=\""+downloadName+"\";")
}
this.AddHeader("X-Accel-Buffering", "no")
_, _ = io.Copy(this.ResponseWriter, fp)
}

View File

@@ -2,18 +2,58 @@ package apps
import (
"errors"
"github.com/iwind/TeaGo/Tea"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/iwind/TeaGo/Tea"
)
func sdkUploadDir() string {
dirs := sdkUploadDirs()
if len(dirs) > 0 {
return dirs[0]
}
return filepath.Clean(Tea.Root + "/data/httpdns/sdk")
}
func sdkUploadDirs() []string {
candidates := []string{
filepath.Clean(Tea.Root + "/../data/httpdns/sdk"),
filepath.Clean(Tea.Root + "/data/httpdns/sdk"),
filepath.Clean(Tea.Root + "/../edge-admin/data/httpdns/sdk"),
filepath.Clean(Tea.Root + "/../edge-user/data/httpdns/sdk"),
filepath.Clean(Tea.Root + "/../../data/httpdns/sdk"),
}
results := make([]string, 0, len(candidates))
seen := map[string]bool{}
for _, dir := range candidates {
if len(dir) == 0 || seen[dir] {
continue
}
seen[dir] = true
results = append(results, dir)
}
return results
}
func sdkUploadSearchDirs() []string {
dirs := sdkUploadDirs()
results := make([]string, 0, len(dirs))
for _, dir := range dirs {
stat, err := os.Stat(dir)
if err == nil && stat.IsDir() {
results = append(results, dir)
}
}
if len(results) == 0 {
results = append(results, sdkUploadDir())
}
return results
}
func findFirstExistingDir(paths []string) string {
for _, path := range paths {
stat, err := os.Stat(path)
@@ -27,7 +67,7 @@ func findFirstExistingDir(paths []string) string {
func findFirstExistingFile(paths []string) string {
for _, path := range paths {
stat, err := os.Stat(path)
if err == nil && !stat.IsDir() {
if err == nil && !stat.IsDir() && stat.Size() > 0 {
return path
}
}
@@ -45,6 +85,9 @@ func findNewestExistingFile(paths []string) string {
if err != nil || stat.IsDir() {
continue
}
if stat.Size() <= 0 {
continue
}
if len(result.path) == 0 || stat.ModTime().After(result.modTime) || (stat.ModTime().Equal(result.modTime) && path > result.path) {
result.path = path
result.modTime = stat.ModTime()
@@ -85,22 +128,23 @@ func resolveSDKPlatform(platform string) (key string, relativeDir string, readme
}
}
func findSDKArchivePath(downloadFilename string) string {
searchDirs := []string{sdkUploadDir()}
func findSDKArchivePath(downloadFilename string, version string) string {
searchDirs := sdkUploadSearchDirs()
// 1) Exact filename first.
exactFiles := []string{}
normalizedVersion := strings.TrimSpace(version)
base := strings.TrimSuffix(downloadFilename, ".zip")
if len(normalizedVersion) > 0 {
versionFiles := []string{}
for _, dir := range searchDirs {
exactFiles = append(exactFiles, filepath.Join(dir, downloadFilename))
versionFiles = append(versionFiles, filepath.Join(dir, base+"-v"+normalizedVersion+".zip"))
}
path := findFirstExistingFile(exactFiles)
if len(path) > 0 {
if path := findFirstExistingFile(versionFiles); len(path) > 0 {
return path
}
return ""
}
// 2) Version-suffixed archives, e.g. httpdns-sdk-android-v1.4.8.zip
base := strings.TrimSuffix(downloadFilename, ".zip")
patternName := base + "-*.zip"
patternName := base + "-v*.zip"
matches := []string{}
for _, dir := range searchDirs {
found, _ := filepath.Glob(filepath.Join(dir, patternName))
@@ -115,28 +159,52 @@ func findSDKArchivePath(downloadFilename string) string {
return findNewestExistingFile(matches)
}
exactFiles := []string{}
for _, dir := range searchDirs {
exactFiles = append(exactFiles, filepath.Join(dir, downloadFilename))
}
if path := findFirstExistingFile(exactFiles); len(path) > 0 {
return path
}
return ""
}
func findUploadedSDKDocPath(platform string) string {
func findUploadedSDKDocPath(platform string, version string) string {
platform = strings.ToLower(strings.TrimSpace(platform))
if len(platform) == 0 {
return ""
}
searchDir := sdkUploadDir()
exact := filepath.Join(searchDir, "httpdns-sdk-"+platform+".md")
if file := findFirstExistingFile([]string{exact}); len(file) > 0 {
searchDirs := sdkUploadSearchDirs()
normalizedVersion := strings.TrimSpace(version)
if len(normalizedVersion) > 0 {
exactVersion := []string{}
for _, dir := range searchDirs {
exactVersion = append(exactVersion, filepath.Join(dir, "httpdns-sdk-"+platform+"-v"+normalizedVersion+".md"))
}
if file := findFirstExistingFile(exactVersion); len(file) > 0 {
return file
}
pattern := filepath.Join(searchDir, "httpdns-sdk-"+platform+"-*.md")
matches, _ := filepath.Glob(pattern)
if len(matches) == 0 {
return ""
}
matches := []string{}
for _, dir := range searchDirs {
pattern := filepath.Join(dir, "httpdns-sdk-"+platform+"-v*.md")
found, _ := filepath.Glob(pattern)
matches = append(matches, found...)
}
if len(matches) > 0 {
sort.Strings(matches)
return findNewestExistingFile(matches)
}
exact := []string{}
for _, dir := range searchDirs {
exact = append(exact, filepath.Join(dir, "httpdns-sdk-"+platform+".md"))
}
return findFirstExistingFile(exact)
}
func findLocalSDKDocPath(platform string) string {

View File

@@ -2,15 +2,16 @@ package apps
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/iwind/TeaGo/actions"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/iwind/TeaGo/actions"
)
type SdkUploadAction struct {
@@ -44,7 +45,7 @@ func (this *SdkUploadAction) RunPost(params struct {
AppId int64
Platform string
Version string
SDKFile *actions.File
SdkFile *actions.File
DocFile *actions.File
Must *actions.Must
@@ -63,7 +64,7 @@ func (this *SdkUploadAction) RunPost(params struct {
return
}
if params.SDKFile == nil && params.DocFile == nil {
if params.SdkFile == nil && params.DocFile == nil {
this.Fail("请至少上传一个文件")
return
}
@@ -75,24 +76,25 @@ func (this *SdkUploadAction) RunPost(params struct {
return
}
if params.SDKFile != nil {
filename := strings.ToLower(strings.TrimSpace(params.SDKFile.Filename))
if params.SdkFile != nil {
filename := strings.ToLower(strings.TrimSpace(params.SdkFile.Filename))
if !strings.HasSuffix(filename, ".zip") {
this.Fail("SDK 包仅支持 .zip 文件")
return
}
sdkData, readErr := params.SDKFile.Read()
sdkData, readErr := params.SdkFile.Read()
if readErr != nil {
this.Fail("读取 SDK 包失败: " + readErr.Error())
return
}
if len(sdkData) == 0 {
this.Fail("SDK 包文件为空,请重新上传")
return
}
baseName := strings.TrimSuffix(downloadFilename, ".zip")
err = saveSDKUploadFile(uploadDir, downloadFilename, sdkData)
if err == nil {
err = saveSDKUploadFile(uploadDir, baseName+"-v"+version+".zip", sdkData)
}
if err != nil {
this.Fail("保存 SDK 包失败: " + err.Error())
return
@@ -111,12 +113,12 @@ func (this *SdkUploadAction) RunPost(params struct {
this.Fail("读取集成文档失败: " + readErr.Error())
return
}
filename := "httpdns-sdk-" + platform + ".md"
err = saveSDKUploadFile(uploadDir, filename, docData)
if err == nil {
err = saveSDKUploadFile(uploadDir, "httpdns-sdk-"+platform+"-v"+version+".md", docData)
if len(docData) == 0 {
this.Fail("集成文档文件为空,请重新上传")
return
}
err = saveSDKUploadFile(uploadDir, "httpdns-sdk-"+platform+"-v"+version+".md", docData)
if err != nil {
this.Fail("保存集成文档失败: " + err.Error())
return
@@ -151,12 +153,6 @@ func saveSDKUploadFile(baseDir string, filename string, data []byte) error {
}
func listUploadedSDKFiles() []map[string]interface{} {
dir := sdkUploadDir()
entries, err := os.ReadDir(dir)
if err != nil {
return []map[string]interface{}{}
}
type item struct {
Name string
Platform string
@@ -166,7 +162,12 @@ func listUploadedSDKFiles() []map[string]interface{} {
UpdatedAt int64
}
items := make([]item, 0)
byName := map[string]item{}
for _, dir := range sdkUploadSearchDirs() {
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
for _, entry := range entries {
if entry.IsDir() {
continue
@@ -182,14 +183,24 @@ func listUploadedSDKFiles() []map[string]interface{} {
continue
}
items = append(items, item{
current := item{
Name: name,
Platform: platform,
FileType: fileType,
Version: version,
SizeBytes: info.Size(),
UpdatedAt: info.ModTime().Unix(),
})
}
old, exists := byName[name]
if !exists || current.UpdatedAt >= old.UpdatedAt {
byName[name] = current
}
}
}
items := make([]item, 0, len(byName))
for _, it := range byName {
items = append(items, it)
}
sort.Slice(items, func(i, j int) bool {
@@ -200,14 +211,14 @@ func listUploadedSDKFiles() []map[string]interface{} {
})
result := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
for _, it := range items {
result = append(result, map[string]interface{}{
"name": item.Name,
"platform": item.Platform,
"fileType": item.FileType,
"version": item.Version,
"sizeText": formatSDKFileSize(item.SizeBytes),
"updatedAt": time.Unix(item.UpdatedAt, 0).Format("2006-01-02 15:04:05"),
"name": it.Name,
"platform": it.Platform,
"fileType": it.FileType,
"version": it.Version,
"sizeText": formatSDKFileSize(it.SizeBytes),
"updatedAt": time.Unix(it.UpdatedAt, 0).Format("2006-01-02 15:04:05"),
})
}
return result
@@ -231,7 +242,7 @@ func parseSDKUploadFilename(filename string) (platform string, version string, f
}
main := strings.TrimSuffix(strings.TrimPrefix(filename, "httpdns-sdk-"), ext)
version = "latest"
version = ""
if idx := strings.Index(main, "-v"); idx > 0 && idx+2 < len(main) {
version = main[idx+2:]
main = main[:idx]
@@ -241,6 +252,9 @@ func parseSDKUploadFilename(filename string) (platform string, version string, f
switch main {
case "android", "ios", "flutter":
platform = main
if len(version) == 0 {
version = "-"
}
return platform, version, fileType, true
default:
return "", "", "", false

View File

@@ -1,10 +1,11 @@
package apps
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"os"
"path/filepath"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
)
type SdkUploadDeleteAction struct {
@@ -42,16 +43,17 @@ func (this *SdkUploadDeleteAction) RunPost(params struct {
return
}
fullPath := filepath.Join(sdkUploadDir(), filename)
for _, dir := range sdkUploadDirs() {
fullPath := filepath.Join(dir, filename)
_, err := os.Stat(fullPath)
if err != nil {
this.Success()
return
continue
}
if err = os.Remove(fullPath); err != nil {
this.Fail("删除失败: " + err.Error())
return
}
}
this.Success()
}

View File

@@ -10,8 +10,10 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
type ClusterSettingsAction struct {
@@ -48,6 +50,7 @@ func (this *ClusterSettingsAction) RunGet(params struct {
"installDir": cluster.GetString("installDir"),
"isOn": cluster.GetBool("isOn"),
"isDefaultCluster": cluster.GetBool("isDefault"),
"isDefaultBackupCluster": false,
}
if settings.GetInt("cacheTtl") <= 0 {
settings["cacheTtl"] = 30
@@ -59,6 +62,19 @@ func (this *ClusterSettingsAction) RunGet(params struct {
settings["installDir"] = "/opt/edge-httpdns"
}
defaultBackupResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
})
if err != nil {
this.ErrorPage(err)
return
}
defaultBackupClusterId := int64(0)
if defaultBackupResp != nil && len(defaultBackupResp.GetValueJSON()) > 0 {
defaultBackupClusterId = types.Int64(string(defaultBackupResp.GetValueJSON()))
}
settings["isDefaultBackupCluster"] = defaultBackupClusterId == params.ClusterId
listenAddresses := []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolHTTPS,
@@ -112,6 +128,7 @@ func (this *ClusterSettingsAction) RunPost(params struct {
InstallDir string
IsOn bool
IsDefaultCluster bool
IsDefaultBackupCluster bool
Addresses []byte
SslPolicyJSON []byte
@@ -137,7 +154,15 @@ func (this *ClusterSettingsAction) RunPost(params struct {
params.InstallDir = "/opt/edge-httpdns"
}
if params.IsDefaultCluster && !params.IsOn {
this.Fail("默认集群必须保持启用状态")
this.Fail("默认集群必须保持启用状态")
return
}
if params.IsDefaultBackupCluster && !params.IsOn {
this.Fail("默认备用集群必须保持启用状态")
return
}
if params.IsDefaultCluster && params.IsDefaultBackupCluster {
this.Fail("默认主集群和默认备用集群不能是同一个集群")
return
}
@@ -195,5 +220,33 @@ func (this *ClusterSettingsAction) RunPost(params struct {
return
}
backupClusterValue := int64(0)
if params.IsDefaultBackupCluster {
backupClusterValue = params.ClusterId
} else {
readResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
})
if err != nil {
this.ErrorPage(err)
return
}
if readResp != nil && len(readResp.GetValueJSON()) > 0 {
oldBackupClusterId := types.Int64(string(readResp.GetValueJSON()))
if oldBackupClusterId != params.ClusterId {
backupClusterValue = oldBackupClusterId
}
}
}
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
ValueJSON: []byte(strconv.FormatInt(backupClusterValue, 10)),
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,11 +1,13 @@
package clusters
import (
"strconv"
"strings"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/actions"
)
@@ -29,7 +31,8 @@ func (this *CreateAction) RunPost(params struct {
FallbackTimeout int32
InstallDir string
IsOn bool
IsDefault bool
IsDefaultPrimary bool
IsDefaultBackup bool
Must *actions.Must
}) {
@@ -49,6 +52,19 @@ func (this *CreateAction) RunPost(params struct {
params.Must.Field("name", params.Name).Require("请输入集群名称")
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
if params.IsDefaultPrimary && !params.IsOn {
this.Fail("默认主集群必须保持启用状态")
return
}
if params.IsDefaultBackup && !params.IsOn {
this.Fail("默认备用集群必须保持启用状态")
return
}
if params.IsDefaultPrimary && params.IsDefaultBackup {
this.Fail("默认主集群和默认备用集群不能是同一个集群")
return
}
resp, err := this.RPC().HTTPDNSClusterRPC().CreateHTTPDNSCluster(this.AdminContext(), &pb.CreateHTTPDNSClusterRequest{
Name: params.Name,
ServiceDomain: params.GatewayDomain,
@@ -56,13 +72,24 @@ func (this *CreateAction) RunPost(params struct {
FallbackTimeoutMs: params.FallbackTimeout,
InstallDir: params.InstallDir,
IsOn: params.IsOn,
IsDefault: params.IsDefault,
IsDefault: params.IsDefaultPrimary,
})
if err != nil {
this.ErrorPage(err)
return
}
if params.IsDefaultBackup {
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
Code: string(systemconfigs.SettingCodeHTTPDNSDefaultBackupClusterId),
ValueJSON: []byte(strconv.FormatInt(resp.GetClusterId(), 10)),
})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["clusterId"] = resp.GetClusterId()
this.Success()
}

View File

@@ -102,6 +102,8 @@ func (this *IndexAction) RunPost(params struct {
NsIsOn bool
HttpdnsIsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
@@ -112,6 +114,15 @@ func (this *IndexAction) RunPost(params struct {
Gt(0, "请选择一个集群")
var config = userconfigs.DefaultUserRegisterConfig()
{
// 先读取现有配置,避免保存时把未出现在当前表单里的字段重置为默认值
resp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{
Code: systemconfigs.SettingCodeUserRegisterConfig,
})
if err == nil && len(resp.ValueJSON) > 0 {
_ = json.Unmarshal(resp.ValueJSON, config)
}
}
config.IsOn = params.IsOn
config.ComplexPassword = params.ComplexPassword
config.RequireVerification = params.RequireVerification
@@ -142,6 +153,7 @@ func (this *IndexAction) RunPost(params struct {
config.ADIsOn = params.AdIsOn
config.NSIsOn = params.NsIsOn
config.HTTPDNSIsOn = params.HttpdnsIsOn
configJSON, err := json.Marshal(config)
if err != nil {
@@ -157,10 +169,15 @@ func (this *IndexAction) RunPost(params struct {
return
}
if params.FeatureOp != "keep" {
featureOp := params.FeatureOp
if featureOp != "overwrite" && featureOp != "append" && featureOp != "keep" {
featureOp = "keep"
}
if featureOp != "keep" {
_, err = this.RPC().UserRPC().UpdateAllUsersFeatures(this.AdminContext(), &pb.UpdateAllUsersFeaturesRequest{
FeatureCodes: params.Features,
Overwrite: params.FeatureOp == "overwrite",
Overwrite: featureOp == "overwrite",
})
if err != nil {
this.ErrorPage(err)

View File

@@ -0,0 +1,28 @@
{$layout "layout_popup"}
<h3 v-if="!isUpdating">添加端口绑定</h3>
<h3 v-if="isUpdating">修改端口绑定</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="supportRange" :value="supportRange ? 1 : 0"/>
<table class="ui table definition selectable">
<tr>
<td>网络协议</td>
<td>
<select class="ui dropdown auto-width" name="protocol" v-model="protocol" @change="changeProtocol">
<option v-for="p in protocols" :value="p.code">{{p.name}}</option>
</select>
</td>
</tr>
<tr>
<td class="title">端口 *</td>
<td>
<input type="text" name="address" ref="focus" v-model="address"/>
<p class="comment">可以是一个数字端口通常不超过65535也可以是"地址:端口"的方式。<span v-if="supportRange">支持端口范围,形式为<code-label>port1-port2</code-label></span>
<span v-if="from.length == 0 && protocol == 'http'">HTTP常用端口为<a href="" title="点击添加" @click.prevent="addPort('80')">80</a></span>
<span v-if="from.length == 0 && protocol == 'https'">HTTPS常用端口为<a href="" title="点击添加" @click.prevent="addPort('443')">443</a></span>
</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,47 @@
Tea.context(function () {
this.success = NotifyPopup;
this.isUpdating = false
this.address = ""
this.protocol = this.protocols[0].code
// 初始化
// from 用来标记是否为特殊的节点
if (this.from.length == 0) {
if (this.protocol == "http") {
this.address = "80"
} else if (this.protocol == "https") {
this.address = "443"
}
}
if (window.parent.UPDATING_ADDR != null) {
this.isUpdating = true
let addr = window.parent.UPDATING_ADDR
this.protocol = addr.protocol
if (addr.host.length == 0) {
this.address = addr.portRange
} else {
this.address = addr.host.quoteIP() + ":" + addr.portRange
}
}
this.changeProtocol = function () {
if (this.from.length > 0) {
return
}
switch (this.protocol) {
case "http":
this.address = "80"
break
case "https":
this.address = "443"
break
}
}
this.addPort = function (port) {
this.address = port
}
});

View File

@@ -67,8 +67,24 @@
<table class="ui table selectable definition" v-show="activeSection == 'basic'">
<tr>
<td class="title">App ID</td>
<td><code>{{settings.appId}}</code></td>
<td class="title">主集群</td>
<td>
<select class="ui dropdown auto-width" name="primaryClusterId" v-model="settings.primaryClusterId">
<option :value="0">[不设置]</option>
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
<p class="comment httpdns-note">未设置时,按默认主集群处理(当前默认主集群:{{settings.defaultPrimaryClusterName || '-' }})。</p>
</td>
</tr>
<tr>
<td class="title">备集群</td>
<td>
<select class="ui dropdown auto-width" name="backupClusterId" v-model="settings.backupClusterId">
<option :value="0">[不设置]</option>
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
<p class="comment httpdns-note">未设置时,按默认备用集群处理(当前默认备用集群:{{settings.defaultBackupClusterName || '-' }})。</p>
</td>
</tr>
<tr>
<td class="title">应用启用</td>
@@ -86,14 +102,34 @@
</table>
<table class="ui table selectable definition httpdns-auth-table" v-show="activeSection == 'auth'">
<tr>
<td class="title">App ID</td>
<td>
<code>{{settings.appId}}</code>
<a href="" class="httpdns-mini-icon" title="复制 App ID" @click.prevent="copySecret(settings.appId, 'App ID')"><i class="copy outline icon"></i></a>
</td>
</tr>
<tr>
<td class="title">主服务域名</td>
<td>
<code v-if="settings.primaryServiceDomain && settings.primaryServiceDomain.length > 0">{{settings.primaryServiceDomain}}</code>
<span class="grey" v-else>未配置</span>
<a v-if="settings.primaryServiceDomain && settings.primaryServiceDomain.length > 0" href="" class="httpdns-mini-icon" title="复制主服务域名" @click.prevent="copySecret(settings.primaryServiceDomain, '主服务域名')"><i class="copy outline icon"></i></a>
</td>
</tr>
<tr>
<td class="title">备用服务域名</td>
<td>
<code v-if="settings.backupServiceDomain && settings.backupServiceDomain.length > 0">{{settings.backupServiceDomain}}</code>
<span class="grey" v-else>未配置</span>
<a v-if="settings.backupServiceDomain && settings.backupServiceDomain.length > 0" href="" class="httpdns-mini-icon" title="复制备用服务域名" @click.prevent="copySecret(settings.backupServiceDomain, '备用服务域名')"><i class="copy outline icon"></i></a>
</td>
</tr>
<tr>
<td class="title">请求验签</td>
<td>
<span
:class="settings.signEnabled ? 'httpdns-state-on' : 'httpdns-state-off'">{{settings.signEnabled
? "已开启" : "已关闭"}}</span>
<a href="" class="ui mini button basic" style="margin-left: .8em;"
@click.prevent="toggleSignEnabled">{{settings.signEnabled ? "关闭请求验签" : "开启请求验签"}}</a>
<span :class="settings.signEnabled ? 'httpdns-state-on' : 'httpdns-state-off'">{{settings.signEnabled ? "已开启" : "已关闭"}}</span>
<a href="" class="ui mini button basic" style="margin-left: .8em;" @click.prevent="toggleSignEnabled">{{settings.signEnabled ? "关闭请求验签" : "开启请求验签"}}</a>
<p class="comment httpdns-note">打开后,服务端会对请求进行签名校验。</p>
</td>
</tr>
@@ -102,14 +138,9 @@
<td>
<div class="httpdns-secret-line">
<code>{{signSecretVisible ? settings.signSecretPlain : settings.signSecretMasked}}</code>
<a href="" class="httpdns-mini-icon" @click.prevent="signSecretVisible = !signSecretVisible"
:title="signSecretVisible ? '隐藏明文' : '查看明文'"><i class="icon"
:class="signSecretVisible ? 'eye slash' : 'eye'"></i></a>
<a href="" class="httpdns-mini-icon" title="复制加签 Secret"
@click.prevent="copySecret(settings.signSecretPlain, '加签 Secret')"><i
class="copy outline icon"></i></a>
<a href="" class="httpdns-mini-icon" title="重置加签 Secret" @click.prevent="resetSignSecret"><i
class="redo icon"></i></a>
<a href="" class="httpdns-mini-icon" @click.prevent="signSecretVisible = !signSecretVisible" :title="signSecretVisible ? '隐藏明文' : '查看明文'"><i class="icon" :class="signSecretVisible ? 'eye slash' : 'eye'"></i></a>
<a href="" class="httpdns-mini-icon" title="复制加签 Secret" @click.prevent="copySecret(settings.signSecretPlain, '加签 Secret')"><i class="copy outline icon"></i></a>
<a href="" class="httpdns-mini-icon" title="重置加签 Secret" @click.prevent="resetSignSecret"><i class="redo icon"></i></a>
</div>
<p class="comment httpdns-note">最近更新:{{settings.signSecretUpdatedAt}}</p>
<p class="comment httpdns-note" v-if="!settings.signEnabled">请求验签已关闭,当前不使用加签 Secret。</p>

View File

@@ -34,7 +34,10 @@
<tr>
<td>所属用户</td>
<td>
<user-selector></user-selector>
<select class="ui dropdown auto-width" name="userId">
<option value="0">[不设置]</option>
<option v-for="user in users" :value="user.id">{{user.fullname}} ({{user.username}})</option>
</select>
<p class="comment">可以选择当前应用所属的平台用户。</p>
</td>
</tr>

View File

@@ -1,4 +1,4 @@
{$layout}
{$layout}
{$template "menu"}
<div class="ui margin"></div>
@@ -26,16 +26,22 @@
<table class="ui table selectable celled httpdns-apps-table" v-if="apps.length > 0">
<colgroup>
<col style="width:32%;" />
<col style="width:26%;" />
<col style="width:20%;" />
<col style="width:12%;" />
<col style="width:10%;" />
<col style="width:11%;" />
<col style="width:11%;" />
<col style="width:12%;" />
<col style="width:8%;" />
<col style="width:6%;" />
<col style="width:20%;" />
</colgroup>
<thead>
<tr>
<th>应用名称</th>
<th>AppID</th>
<th>主集群</th>
<th>备用集群</th>
<th>用户</th>
<th class="center">绑定域名数</th>
<th class="center">状态</th>
<th>操作</th>
@@ -54,6 +60,26 @@
<code>{{app.appId}}</code>
<copy-icon :text="app.appId"></copy-icon>
</td>
<td>
<span v-if="app.primaryCluster != null && app.primaryCluster.id > 0">
{{app.primaryCluster.name || ('#' + app.primaryCluster.id)}}
<link-icon :href="'/httpdns/clusters/cluster?clusterId=' + app.primaryCluster.id"></link-icon>
</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="app.backupCluster != null && app.backupCluster.id > 0">
{{app.backupCluster.name || ('#' + app.backupCluster.id)}}
<link-icon :href="'/httpdns/clusters/cluster?clusterId=' + app.backupCluster.id"></link-icon>
</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="app.user != null && app.user.id > 0">
{{app.user.fullname}} ({{app.user.username}})
</span>
<span v-else class="disabled">-</span>
</td>
<td class="center"><a :href="'/httpdns/apps/domains?appId=' + app.id">{{app.domainCount}}</a></td>
<td class="center">
<label-on :v-is-on="app.isOn"></label-on>

View File

@@ -49,8 +49,8 @@
</div>
<div class="extra content">
<div class="httpdns-sdk-actions">
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=android"><i class="icon download"></i> 下载 SDK</a>
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=android"><i class="icon book"></i> 下载文档</a>
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=android" @click="downloadSDK('android', $event)"><i class="icon download"></i> 下载 SDK</a>
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=android" @click="downloadDoc('android', $event)"><i class="icon book"></i> 下载文档</a>
</div>
</div>
</div>
@@ -63,8 +63,8 @@
</div>
<div class="extra content">
<div class="httpdns-sdk-actions">
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=ios"><i class="icon download"></i> 下载 SDK</a>
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=ios"><i class="icon book"></i> 下载文档</a>
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=ios" @click="downloadSDK('ios', $event)"><i class="icon download"></i> 下载 SDK</a>
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=ios" @click="downloadDoc('ios', $event)"><i class="icon book"></i> 下载文档</a>
</div>
</div>
</div>
@@ -77,8 +77,8 @@
</div>
<div class="extra content">
<div class="httpdns-sdk-actions">
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=flutter"><i class="icon download"></i> 下载 SDK</a>
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=flutter"><i class="icon book"></i> 下载文档</a>
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/download?platform=flutter" @click="downloadSDK('flutter', $event)"><i class="icon download"></i> 下载 SDK</a>
<a class="ui button compact mini basic" href="/httpdns/apps/sdk/doc?platform=flutter" @click="downloadDoc('flutter', $event)"><i class="icon book"></i> 下载文档</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,123 @@
Tea.context(function () {
this.downloadSDK = function (platform, event) {
this.checkAndDownload(platform, "sdk", event)
}
this.downloadDoc = function (platform, event) {
this.checkAndDownload(platform, "doc", event)
}
this.checkAndDownload = function (platform, type, event) {
if (event != null && typeof event.preventDefault == "function") {
event.preventDefault()
}
this.$get("/httpdns/apps/sdk/check")
.params({
platform: platform,
type: type
})
.success(function (resp) {
let data = (resp != null && resp.data != null) ? resp.data : {}
if (!data.exists) {
teaweb.warn(data.message || "当前暂无可下载文件")
return
}
if (typeof data.url == "string" && data.url.length > 0) {
this.downloadByBlob(data.url, platform, type)
return
}
teaweb.warn("下载地址生成失败,请稍后重试")
}.bind(this))
}
this.downloadByBlob = function (url, platform, type) {
let defaultFileName = "httpdns-sdk-" + platform + (type == "doc" ? ".md" : ".zip")
let xhr = new XMLHttpRequest()
xhr.open("GET", url, true)
xhr.responseType = "blob"
xhr.onload = function () {
if (xhr.status < 200 || xhr.status >= 300) {
teaweb.warn("下载失败HTTP " + xhr.status + "")
return
}
if (xhr.status == 204) {
teaweb.warn("下载被浏览器扩展或下载工具拦截,请暂时关闭相关扩展后重试")
return
}
let contentType = (xhr.getResponseHeader("Content-Type") || "").toLowerCase()
if (contentType.indexOf("application/json") >= 0) {
let reader = new FileReader()
reader.onload = function () {
try {
let json = JSON.parse(reader.result)
teaweb.warn((json && json.message) ? json.message : "下载失败,请稍后重试")
} catch (_e) {
teaweb.warn("下载失败,请稍后重试")
}
}
reader.readAsText(xhr.response)
return
}
let disposition = xhr.getResponseHeader("Content-Disposition") || ""
let fileName = xhr.getResponseHeader("X-SDK-Filename") || this.parseFileName(disposition) || defaultFileName
if (xhr.response == null || xhr.response.size <= 0) {
teaweb.warn("下载文件为空,请检查已上传 SDK 包是否完整")
return
}
this.saveBlob(xhr.response, fileName)
}.bind(this)
xhr.onerror = function () {
teaweb.warn("下载失败,请检查网络后重试")
}
xhr.send()
}
this.parseFileName = function (disposition) {
if (typeof disposition != "string" || disposition.length == 0) {
return ""
}
let utf8Match = disposition.match(/filename\*=UTF-8''([^;]+)/i)
if (utf8Match != null && utf8Match.length > 1) {
try {
return decodeURIComponent(utf8Match[1])
} catch (_e) {
}
}
let plainMatch = disposition.match(/filename="?([^";]+)"?/i)
if (plainMatch != null && plainMatch.length > 1) {
return plainMatch[1]
}
return ""
}
this.saveBlob = function (blob, fileName) {
if (window.navigator != null && typeof window.navigator.msSaveOrOpenBlob == "function") {
window.navigator.msSaveOrOpenBlob(blob, fileName)
return
}
let objectURL = window.URL.createObjectURL(blob)
let a = document.createElement("a")
a.style.display = "none"
a.href = objectURL
a.download = fileName
document.body.appendChild(a)
a.click()
setTimeout(function () {
window.URL.revokeObjectURL(objectURL)
if (a.parentNode != null) {
a.parentNode.removeChild(a)
}
}, 30000)
}
})

View File

@@ -1,4 +1,4 @@
{$layout}
{$layout}
<second-menu>
<a class="item" :href="'/httpdns/apps/domains?appId=' + app.id">{{app.name}}</a>
@@ -34,7 +34,7 @@
<td class="title">版本号 *</td>
<td>
<input type="text" name="version" v-model="version" maxlength="32"/>
<p class="comment">默认 `1.0.0`。同平台上传会覆盖“最新版本”下载内容</p>
<p class="comment">默认 `1.0.0`。同平台+同版本再次上传会覆盖该版本文件</p>
</td>
</tr>
<tr>
@@ -90,4 +90,3 @@
<div class="ui message" v-else>
暂无上传记录。
</div>

View File

@@ -1,4 +1,4 @@
Tea.context(function () {
Tea.context(function () {
this.platform = "android";
this.version = this.defaultVersion || "1.0.0";
this.isUploading = false;

View File

@@ -11,7 +11,6 @@
<div class="item"><strong>集群设置</strong></div>
</second-menu>
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
@@ -39,50 +38,65 @@
<td>节点安装根目录</td>
<td>
<input type="text" name="installDir" maxlength="100" v-model="settings.installDir" />
<p class="comment">边缘节点安装 HTTPDNS 服务的默认所在目录,此目录将被用于下发配置。通常保持默认即可。</p>
<p class="comment">边缘节点安装 HTTPDNS 服务的默认目录,通常保持默认即可。</p>
</td>
</tr>
<tr>
<td>默认解析 TTL</td>
<td>
<div class="ui input right labeled">
<input type="text" name="cacheTtl" maxlength="5" v-model="settings.cacheTtl"
style="width: 6em" />
<input type="text" name="cacheTtl" maxlength="5" v-model="settings.cacheTtl" style="width: 6em" />
<span class="ui label"></span>
</div>
<p class="comment">SDK HTTPDNS 请求域名解析时,返回的默认缓存有效期 (TTL)。SDK 超时后将重新发起请求</p>
<p class="comment">SDK 通过 HTTPDNS 解析域名时返回的默认 TTL</p>
</td>
</tr>
<tr>
<td>降级超时容忍度</td>
<td>
<div class="ui input right labeled">
<input type="text" name="fallbackTimeout" maxlength="5" v-model="settings.fallbackTimeout"
style="width: 6em" />
<input type="text" name="fallbackTimeout" maxlength="5" v-model="settings.fallbackTimeout" style="width: 6em" />
<span class="ui label">毫秒</span>
</div>
<p class="comment">HTTPDNS 节点回源查询其它 DNS 时的最大等待时间。超出此时间将触发服务降级逻辑(返回上一有效缓存或错误)。</p>
<p class="comment">节点回源查询上游 DNS 时的最大等待时间。</p>
</td>
</tr>
<tr>
<td>启用当前集群</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="settings.isOn"
@change="syncDefaultCluster" />
<input type="checkbox" name="isOn" value="1" v-model="settings.isOn" @change="syncDefaultCluster" />
<label></label>
</div>
<p class="comment">如果取消启用,整个集群的 HTTPDNS 解析服务将停止</p>
<p class="comment">取消启用后,该集群不会参与 HTTPDNS 服务</p>
</td>
</tr>
<tr>
<td>默认集群</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isDefaultCluster" value="1" v-model="settings.isDefaultCluster" />
<label>为默认部署集群</label>
<input type="checkbox" value="1" v-model="settings.defaultClusterEnabled" @change="syncDefaultClusterSelection" />
<label>设为默认集群</label>
</div>
<p class="comment">全局设置。如果应用未单独指定集群,将默认分配和部署到该集群中。</p>
<div class="ui form" style="margin-top: .8em;" v-if="settings.defaultClusterEnabled">
<div class="grouped fields" style="margin:0;">
<div class="field">
<div class="ui radio checkbox">
<input type="radio" v-model="settings.defaultClusterRole" value="primary" @change="syncDefaultClusterSelection" />
<label>主集群</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" v-model="settings.defaultClusterRole" value="backup" @change="syncDefaultClusterSelection" />
<label>备用集群</label>
</div>
</div>
</div>
</div>
<input type="hidden" name="isDefaultCluster" value="1" v-if="settings.defaultClusterEnabled && settings.defaultClusterRole == 'primary'" />
<input type="hidden" name="isDefaultBackupCluster" value="1" v-if="settings.defaultClusterEnabled && settings.defaultClusterRole == 'backup'" />
<p class="comment">同一时刻最多 1 个默认集群角色,新设置会自动取消旧设置。</p>
</td>
</tr>
</table>
@@ -91,15 +105,12 @@
<tr>
<td class="title">绑定端口 *</td>
<td>
<network-addresses-box :v-url="'/httpdns/addPortPopup'" :v-addresses="tlsConfig.listen"
:v-protocol="'tls'" :v-support-range="true"></network-addresses-box>
<network-addresses-box :v-url="'/httpdns/addPortPopup'" :v-addresses="tlsConfig.listen" :v-protocol="'tls'" :v-support-range="true"></network-addresses-box>
</td>
</tr>
</table>
<!-- SSL配置 -->
<ssl-config-box v-show="activeSection == 'tls'" :v-ssl-policy="tlsConfig.sslPolicy"
:v-protocol="'tls'"></ssl-config-box>
<ssl-config-box v-show="activeSection == 'tls'" :v-ssl-policy="tlsConfig.sslPolicy" :v-protocol="'tls'"></ssl-config-box>
<submit-btn></submit-btn>
</form>

View File

@@ -1,4 +1,4 @@
Tea.context(function () {
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
this.activeSection = this.activeSection || "basic"
@@ -8,9 +8,38 @@ Tea.context(function () {
this.settings = {}
}
// 兼容旧字段,转换成统一“默认集群 + 角色”表现
let isDefaultPrimary = !!this.settings.isDefaultCluster
let isDefaultBackup = !!this.settings.isDefaultBackupCluster
this.settings.defaultClusterEnabled = isDefaultPrimary || isDefaultBackup
this.settings.defaultClusterRole = isDefaultBackup ? "backup" : "primary"
this.syncDefaultCluster = function () {
if (!this.settings.isOn) {
this.settings.defaultClusterEnabled = false
this.settings.defaultClusterRole = "primary"
this.settings.isDefaultCluster = false
this.settings.isDefaultBackupCluster = false
return
}
this.syncDefaultClusterSelection()
}
this.syncDefaultClusterSelection = function () {
if (!this.settings.defaultClusterEnabled) {
this.settings.isDefaultCluster = false
this.settings.isDefaultBackupCluster = false
return
}
if (this.settings.defaultClusterRole === "backup") {
this.settings.isDefaultCluster = false
this.settings.isDefaultBackupCluster = true
} else {
this.settings.defaultClusterRole = "primary"
this.settings.isDefaultCluster = true
this.settings.isDefaultBackupCluster = false
}
}
})

View File

@@ -1,4 +1,4 @@
{$layout}
{$layout}
{$template "menu"}
<div class="ui margin"></div>
@@ -27,7 +27,7 @@
<input type="text" name="cacheTtl" maxlength="5" value="30" style="width: 6em" />
<span class="ui label"></span>
</div>
<p class="comment">SDK HTTPDNS 请求域名解析时,返回的默认缓存有效期 (TTL)。SDK 超时后将重新发起请求</p>
<p class="comment">SDK 通过 HTTPDNS 解析域名时返回的默认 TTL</p>
</td>
</tr>
<tr>
@@ -37,34 +37,50 @@
<input type="text" name="fallbackTimeout" maxlength="5" value="300" style="width: 6em" />
<span class="ui label">毫秒</span>
</div>
<p class="comment">HTTPDNS 节点回源查询其它 DNS 时的最大等待时间。超出此时间将触发服务降级逻辑(返回上一有效缓存或错误)。</p>
<p class="comment">节点回源查询上游 DNS 时的最大等待时间。</p>
</td>
</tr>
<tr>
<td>节点安装根目录</td>
<td>
<input type="text" name="installDir" maxlength="255" value="/opt/edge-httpdns" />
<p class="comment">边缘节点安装 HTTPDNS 服务的默认所在目录,此目录将被用于下发配置。通常保持默认即可</p>
<p class="comment">边缘节点安装 HTTPDNS 服务的默认目录</p>
</td>
</tr>
<tr>
<td>启用当前集群</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" checked />
<input type="checkbox" name="isOn" value="1" v-model="isOn" @change="syncDefaultClusterEnabled" checked />
<label></label>
</div>
<p class="comment">如果取消启用,整个集群的 HTTPDNS 解析服务将不被系统分配。</p>
</td>
</tr>
<tr>
<td>默认集群</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isDefault" value="1" />
<label>为默认部署集群</label>
<input type="checkbox" value="1" v-model="defaultClusterEnabled" @change="syncDefaultClusterEnabled" />
<label>设为默认集群</label>
</div>
<p class="comment">全局设置。如果应用未单独指定集群,将默认分配和部署到该集群中。</p>
<div class="ui form" style="margin-top: .8em;" v-if="defaultClusterEnabled">
<div class="grouped fields" style="margin:0;">
<div class="field">
<div class="ui radio checkbox">
<input type="radio" v-model="defaultClusterRole" value="primary" />
<label>主集群</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" v-model="defaultClusterRole" value="backup" />
<label>备用集群</label>
</div>
</div>
</div>
</div>
<input type="hidden" name="isDefaultPrimary" value="1" v-if="defaultClusterEnabled && defaultClusterRole == 'primary'" />
<input type="hidden" name="isDefaultBackup" value="1" v-if="defaultClusterEnabled && defaultClusterRole == 'backup'" />
</td>
</tr>
</table>

View File

@@ -1,4 +1,15 @@
Tea.context(function () {
Tea.context(function () {
this.isOn = true
this.defaultClusterEnabled = false
this.defaultClusterRole = "primary"
this.syncDefaultClusterEnabled = function () {
if (!this.isOn) {
this.defaultClusterEnabled = false
this.defaultClusterRole = "primary"
}
}
this.success = function (resp) {
let clusterId = 0
if (resp != null && resp.data != null && typeof resp.data.clusterId != "undefined") {

View File

@@ -256,5 +256,17 @@
<div class="margin"></div>
</div>
<div class="margin"></div>
<h4>HTTPDNS服务</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">开通HTTPDNS服务</td>
<td><checkbox name="httpdnsIsOn" v-model="config.httpdnsIsOn"></checkbox>
<p class="comment">选中表示自动为用户开通HTTPDNS服务。</p>
</td>
</tr>
</table>
<div class="margin"></div>
<submit-btn></submit-btn>
</form>

View File

@@ -7,7 +7,8 @@ Tea.context(function () {
this.mobileVerificationMoreOptions = false
this.mobileResetPasswordMoreOptions = false
this.featureOp = "overwrite"
// 默认不影响已有用户功能,避免保存注册设置时误改用户功能绑定
this.featureOp = "keep"
this.featuresVisible = false
this.showFeatures = function () {

View File

@@ -166,7 +166,7 @@ var File_models_model_httpdns_app_proto protoreflect.FileDescriptor
var file_models_model_httpdns_app_proto_rawDesc = []byte{
0x0a, 0x1e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x68,
0x74, 0x74, 0x70, 0x64, 0x6e, 0x73, 0x5f, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x02, 0x70, 0x62, 0x22, 0xee, 0x02, 0x0a, 0x0a, 0x48, 0x54, 0x54, 0x50, 0x44, 0x4e, 0x53,
0x12, 0x02, 0x70, 0x62, 0x22, 0x86, 0x03, 0x0a, 0x0a, 0x48, 0x54, 0x54, 0x50, 0x44, 0x4e, 0x53,
0x41, 0x70, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64,
@@ -189,7 +189,9 @@ var file_models_model_httpdns_app_proto_rawDesc = []byte{
0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72,
0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x64, 0x41, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61,
0x74, 0x65, 0x64, 0x41, 0x74, 0x42, 0x06, 0x5a, 0x04, 0x2e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70,
0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18,
0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x42, 0x06, 0x5a,
0x04, 0x2e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}

View File

@@ -724,7 +724,7 @@ var file_service_httpdns_app_proto_rawDesc = []byte{
0x1e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x68, 0x74,
0x74, 0x70, 0x64, 0x6e, 0x73, 0x5f, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
0x19, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2f, 0x72, 0x70, 0x63, 0x5f, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xcf, 0x01, 0x0a, 0x17, 0x43,
0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe7, 0x01, 0x0a, 0x17, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x54, 0x54, 0x50, 0x44, 0x4e, 0x53, 0x41, 0x70, 0x70, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x70,
@@ -737,11 +737,13 @@ var file_service_httpdns_app_proto_rawDesc = []byte{
0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x73, 0x4f, 0x6e, 0x18, 0x05,
0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x69, 0x73, 0x4f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x69,
0x67, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
0x0b, 0x73, 0x69, 0x67, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x34, 0x0a, 0x18,
0x0b, 0x73, 0x69, 0x67, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06,
0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73,
0x65, 0x72, 0x49, 0x64, 0x22, 0x34, 0x0a, 0x18,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x54, 0x54, 0x50, 0x44, 0x4e, 0x53, 0x41, 0x70, 0x70,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x70, 0x70, 0x44,
0x62, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x70, 0x70, 0x44, 0x62,
0x49, 0x64, 0x22, 0xb1, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x54, 0x54,
0x49, 0x64, 0x22, 0xc9, 0x01, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x48, 0x54, 0x54,
0x50, 0x44, 0x4e, 0x53, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18,
0x0a, 0x07, 0x61, 0x70, 0x70, 0x44, 0x62, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
0x07, 0x61, 0x70, 0x70, 0x44, 0x62, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
@@ -752,7 +754,9 @@ var file_service_httpdns_app_proto_rawDesc = []byte{
0x75, 0x70, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x73, 0x4f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
0x52, 0x04, 0x69, 0x73, 0x4f, 0x6e, 0x22, 0x33, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x52, 0x04, 0x69, 0x73, 0x4f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64,
0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x33,
0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x48, 0x54, 0x54, 0x50, 0x44, 0x4e, 0x53, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x70, 0x70, 0x44, 0x62, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x03, 0x52, 0x07, 0x61, 0x70, 0x70, 0x44, 0x62, 0x49, 0x64, 0x22, 0x31, 0x0a, 0x15, 0x46,

View File

@@ -5,16 +5,16 @@ type SettingCode = string
const (
SettingCodeNodeMonitor SettingCode = "nodeMonitor" // 监控节点状态
SettingCodeClusterHealthCheck SettingCode = "clusterHealthCheck" // 集群健康检查
SettingCodeIPListVersion SettingCode = "ipListVersion" // IP名单版本号
SettingCodeAdminSecurityConfig SettingCode = "adminSecurityConfig" // 管理安全设置
SettingCodeAdminUIConfig SettingCode = "adminUIConfig" // 管理界面设置
SettingCodeDatabaseConfigSetting SettingCode = "databaseConfig" // 数据库相关配置
SettingCodeIPListVersion SettingCode = "ipListVersion" // IP名单版本号
SettingCodeAdminSecurityConfig SettingCode = "adminSecurityConfig" // 管理安全设置
SettingCodeAdminUIConfig SettingCode = "adminUIConfig" // 管理界面设置
SettingCodeDatabaseConfigSetting SettingCode = "databaseConfig" // 数据库配置
SettingCodeAccessLogQueue SettingCode = "accessLogQueue" // 访问日志队列
SettingCodeCheckUpdates SettingCode = "checkUpdates" // 检查自动更新配置
SettingCodeCheckUpdates SettingCode = "checkUpdates" // 自动更新配置
SettingCodeUserServerConfig SettingCode = "userServerConfig" // 用户服务设置
SettingCodeUserRegisterConfig SettingCode = "userRegisterConfig" // 用户注册配置
SettingCodeUserUIConfig SettingCode = "userUIConfig" // 用户界面配置
SettingCodeStandaloneInstanceInitialized SettingCode = "standaloneInstanceInitialized" // 单体实例初始化状态
SettingCodeStandaloneInstanceInitialized SettingCode = "standaloneInstanceInitialized" // 单体实例是否已经被初始化0 未被初始化, 1 已经成功初始化
SettingCodeHTTPDNSDefaultBackupClusterId SettingCode = "httpdnsDefaultBackupClusterId" // HTTPDNS默认备用集群ID
)

View File

@@ -9,6 +9,7 @@ type UserFeatureCode = string
const (
UserFeatureCodePlan UserFeatureCode = "plan"
UserFeatureCodeHTTPDNS UserFeatureCode = "httpdns"
UserFeatureCodeServerTCP UserFeatureCode = "server.tcp"
UserFeatureCodeServerTCPPort UserFeatureCode = "server.tcp.port"
@@ -212,6 +213,12 @@ func FindAllUserFeatures() []*UserFeature {
Description: "用户可以购买和管理套餐。",
SupportPlan: false,
},
{
Name: "HTTPDNS",
Code: UserFeatureCodeHTTPDNS,
Description: "用户可以使用 HTTPDNS 应用管理、访问日志和解析测试。",
SupportPlan: false,
},
}
}

View File

@@ -52,6 +52,8 @@ type UserRegisterConfig struct {
// 开通DNS服务
NSIsOn bool `json:"nsIsOn"` // 是否开启智能DNS服务
// 开通HTTPDNS服务
HTTPDNSIsOn bool `json:"httpdnsIsOn"` // 是否开启用户端HTTPDNS服务
// 开通高防服务
ADIsOn bool `json:"adIsOn"` // 是否开启高防服务
@@ -63,6 +65,7 @@ func DefaultUserRegisterConfig() *UserRegisterConfig {
ComplexPassword: true,
CDNIsOn: true,
NSIsOn: false,
HTTPDNSIsOn: false,
Features: []string{
UserFeatureCodeServerAccessLog,
UserFeatureCodeServerViewAccessLog,

View File

@@ -0,0 +1,165 @@
# Android SDK 集成文档Edge HTTPDNS
## 1. 版本与依赖
- SDK 模块:`EdgeHttpDNS/sdk/android/httpdns-sdk`
- `minSdkVersion`19
- `targetSdkVersion`33
- `compileSdk`33
将发布包中的 `jar/aar` 放到应用模块 `libs/`,在 `app/build.gradle` 中添加:
```gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
}
```
## 2. SNI 行为说明(关键)
当前 SDK 行为与代码一致:
1. `/resolve` 请求链路SDK -> 你的 HTTPDNS 服务域名)
- 走域名 HTTPS
- 默认 TLS 行为(会带 SNI
2. 业务请求链路(拿到 CDN IP 后发起业务 HTTPS
- 使用 `HttpDnsHttpAdapter`:按 IP 建连,`Host` 保留原域名,并清空 SNINo-SNI
## 3. 初始化 SDK推荐用 V1 客户端)
### Kotlin
```kotlin
import com.new.sdk.android.httpdns.HttpDnsV1Client
import com.new.sdk.android.httpdns.HttpDnsService
val service: HttpDnsService = HttpDnsV1Client.init(
context = applicationContext,
appId = "your-app-id",
primaryServiceHost = "httpdns.example.com",
backupServiceHost = "httpdns-backup.example.com", // 可空字符串
servicePort = 443,
signSecret = "your-sign-secret" // 可空字符串
)
```
### Java
```java
import com.new.sdk.android.httpdns.HttpDnsService;
import com.new.sdk.android.httpdns.HttpDnsV1Client;
HttpDnsService service = HttpDnsV1Client.init(
getApplicationContext(),
"your-app-id",
"httpdns.example.com",
"httpdns-backup.example.com", // 可传 ""
443,
"your-sign-secret" // 可传 ""
);
```
## 4. 解析域名获取 CDN IP
### Kotlin
```kotlin
import com.new.sdk.android.httpdns.HTTPDNSResult
val result: HTTPDNSResult = HttpDnsV1Client.resolveHost(
service = service,
host = "api.example.com",
qtype = "A", // "A" 或 "AAAA"
cip = null // 可选,客户端 IP 透传
)
val ips = result.ips ?: emptyArray()
```
### Java
```java
import com.new.sdk.android.httpdns.HTTPDNSResult;
HTTPDNSResult result = HttpDnsV1Client.resolveHost(
service,
"api.example.com",
"A",
null
);
String[] ips = result.getIps();
```
## 5. 业务请求接入方式
#使用 `HttpDnsHttpAdapter`IP 直连 + No-SNI
业务请求侧做“隐匿 SNI”。该适配器仅支持 HTTPS URL。
```kotlin
import com.new.sdk.android.httpdns.HttpDnsV1Client
import com.new.sdk.android.httpdns.network.HttpDnsAdapterOptions
import com.new.sdk.android.httpdns.network.HttpDnsAdapterRequest
val adapter = HttpDnsV1Client.buildHttpClientAdapter(
service,
HttpDnsAdapterOptions.Builder()
.setConnectTimeoutMillis(3000)
.setReadTimeoutMillis(5000)
.setRequestIpType(com.new.sdk.android.httpdns.RequestIpType.auto)
.build()
)
val req = HttpDnsAdapterRequest(
"GET",
"https://api.example.com/path?x=1"
)
val resp = adapter.execute(req)
val code = resp.statusCode
val bodyBytes = resp.body
val usedIp = resp.usedIp
```
## 6. 预解析与常用接口
```kotlin
service.setPreResolveHosts(listOf("api.example.com", "img.example.com"))
val r1 = service.getHttpDnsResultForHostSync("api.example.com", com.new.sdk.android.httpdns.RequestIpType.auto)
val r2 = service.getHttpDnsResultForHostSyncNonBlocking("api.example.com", com.new.sdk.android.httpdns.RequestIpType.auto)
```
- `Sync`:允许阻塞等待刷新结果(上限受 timeout 等配置影响)
- `NonBlocking`:快速返回当前可用缓存/结果,不阻塞等待
## 7. 验证建议
1. 验证 `/resolve`
- 抓包看目标应为 `https://<serviceHost>:<servicePort>/resolve...`
2. 验证业务请求(若使用 `HttpDnsHttpAdapter`
- 目标地址应是 CDN IP
- HTTP `Host` 应为原域名
- TLS ClientHello 不应携带 SNINo-SNI
## 8. 混淆配置
```proguard
-keep class com.new.sdk.android.** { *; }
```
## 9. 常见问题
1. HTTPDNS 没生效
- 检查是否真正使用了 SDK 返回 IP或用了 `HttpDnsHttpAdapter`
- 检查失败回退逻辑是否总是直接走了系统 DNS
2. 使用 `HttpDnsHttpAdapter` 仍失败
- 只支持 HTTPS URL
3. 线上不要开启不安全证书
- `HttpDnsAdapterOptions.Builder#setAllowInsecureCertificatesForDebugOnly(true)` 仅限调试环境

View File

@@ -0,0 +1,64 @@
1.4.8版本ChangelogHTTPDNS功能全量发布
1、通过智能解析把用户就近调度到最优节点显著降低首包时延与连接抖动。
2、支持按地域、运营商、国内/海外精细分流,提升弱网与跨网访问稳定性。
3、解析链路内置签名鉴权与请求追踪增强安全性与可观测性。
4、无命中规则时自动回源兜底保障解析连续可用。
5、支持 A/AAAA 双栈与多记录返回,兼容不同终端网络环境。
6、提供Android、iOS、Flutter 多端SDK 开箱可用,支持预解析、缓存与同步/非阻塞解析。
7、提供 IP 直连适配能力(含 Host 保留与 No-SNI 模式),适配复杂 HTTPS 场景。
8、控制台支持应用/域名/规则全流程配置与在线验证,缩短问题定位和发布周期。
9、节点支持在线安装升级与日志上报降低运维复杂度并提升可维护性。
1.4.8版本升级步骤
1、备份现有配置与数据
将 edge-api、edge-admin、edge-user 等组件目录下的 configs 文件夹,以及平台的 MySQL 数据库进行全量备份;
2、停止旧版本进程管理端各组件
killall -9 edge-api
killall -9 edge-admin
killall -9 edge-user
3、上传并解压新版本包以 Linux x64 环境为例):
unzip -o edge-admin-linux-amd64-v1.4.8.zip -d /data/
unzip -o edge-user-linux-amd64-v1.4.8.zip -d /data/
4、依次运行edge-api、edge-admin、edge-user
# 启动 API 服务
cd /data/edge-api/bin
chmod +x edge-api
nohup ./edge-api 2>&1 &
# 启动管理后台
cd /data/edge-admin/bin
chmod +x edge-admin
nohup ./edge-admin 2>&1 &
# 启动租户控制台
cd /data/edge-user/bin
chmod +x edge-user
nohup ./edge-user 2>&1 &
5、检查版本状态
登录管理后台,确认系统版本显示为 1.4.8
6、配置主备集群
进入“HTTPDNS -> 集群列表 -> 集群设置”,按需勾选“默认主集群”或“默认备用集群”角色,以便后续应用自动关联;
7、在线升级 HTTPDNS 节点:
进入“HTTPDNS -> 节点列表”,点击对应节点的“详情”,在“安装信息”页面点击 **[在线安装]** 或 **[升级]**。系统会自动下发最新的 edge-httpdns 二进制文件并完成重启。
8、验证节点在线状态
等待 30 秒左右,确认节点状态恢复为“在线”,并验证硬件负载监控数据是否正常上报。
9、业务解析验证
使用控制台“解析测试”工具,验证域名在当前环境下是否能正确返回调度的 IP 地址。
10、完成升级。
特别说明:
1、在线升级模式Edge HTTPDNS 节点支持通过管理平台一键在线升级,无需手动上传文件和重启进程。
2、离线安装模式如节点服务器无法连接控制台可手动上传 edge-httpdns 压缩包并解压,更新 bin 目录下的程序文件后手动执行 `./edge-httpdns restart` 即可。
3、SNI 隐匿功能:请确保关联的 CDN 边缘节点也已同步更新至配套版本(会自动升级)。

View File

@@ -0,0 +1,133 @@
# Flutter SDK 集成文档Edge HTTPDNS
## 1. 版本与依赖
- SDK 插件:`EdgeHttpDNS/sdk/flutter/new_httpdns`
- 环境要求Flutter 2.15+ / Dart 2.15+
`pubspec.yaml` 中引用本地插件:
```yaml
dependencies:
new_httpdns:
path: path/to/sdk/flutter/new_httpdns
```
执行 `flutter pub get` 完成安装。
## 2. SNI 行为说明(关键)
1. **/resolve 请求链路**SDK -> 你的 HTTPDNS 服务域名)
- 走标准 HTTPS默认携带 SNI用于路由到边缘控制节点
2. **业务请求链路**(拿到 CDN IP 后发起业务 HTTPS
- **IP 直连 + No-SNI**:使用 `TrustAPPHttpdnsHttpAdapter` 进行请求。
- 逻辑:解析域名 -> 拿到 IP 列表 -> `uri.replace(host: ip)` -> `req.headers.host = uri.host` -> **清空 SNI**
- 仅支持 HTTPS URL。
## 3. 初始化 SDK推荐用 TrustAPP 封装)
### Dart
```dart
import 'package:new_httpdns/new_httpdns.dart';
bool ok = await TrustAPPHttpdns.init(
appId: "your-app-id",
primaryServiceHost: "httpdns.example.com",
backupServiceHost: "httpdns-backup.example.com",
servicePort: 443,
secretKey: "your-sign-secret" // 可选,开启签名校验需传入
);
if (ok) {
print("Edge HTTPDNS 初始化成功");
}
```
## 4. 解析域名获取 CDN IP
### Dart
```dart
// V1 风格解析接口
Map<String, dynamic> result = await TrustAPPHttpdns.resolveHost(
"api.example.com",
qtype: 'A', // 可选 'A' 或 'AAAA'
cip: '1.2.3.4' // 可选,模拟客户端 IP
);
List<String> ipv4s = result['ipv4'];
int ttl = result['ttl'];
```
## 5. 业务请求接入方式
使用 `TrustAPPHttpdnsHttpAdapter` 实现“SNI 隐匿”业务请求。
### Dart
```dart
final adapter = TrustAPPHttpdns.createHttpAdapter(
options: const TrustAPPHttpdnsAdapterOptions(
connectTimeoutMs: 3000,
readTimeoutMs: 5000,
ipType: 'auto', // auto/ipv4/ipv6
)
);
try {
final res = await adapter.request(
Uri.parse("https://api.example.com/path?x=1"),
method: 'GET',
headers: {'Custom-Header': 'Value'},
body: null
);
print("Status Code: ${res.statusCode}");
print("Body Length: ${res.body.length}");
print("Used IP: ${res.usedIp}");
} catch (e) {
print("请求失败: $e");
}
```
## 6. 其他常用接口
```dart
// 1. 设置预解析域名
await TrustAPPHttpdns.setPreResolveHosts(["api.example.com", "img.example.com"]);
// 2. 只有开启缓存时可用
Map<String, List<String>> cacheRes = await TrustAPPHttpdns.resolveHostSyncNonBlocking("api.example.com");
// 3. 开启持久化缓存(重启 App 后任然可用)
await TrustAPPHttpdns.setPersistentCacheIPEnabled(true);
// 4. 控制台日志(建议仅调试开启)
await TrustAPPHttpdns.setLogEnabled(true);
```
## 7. 验证建议
1. **验证 /resolve**
- 观察控制台日志或抓包工具,解析请求应指向 `https://<serviceHost>:<servicePort>/resolve...`
2. **验证业务请求**
- 如果使用 `TrustAPPHttpdnsHttpAdapter`,观察抓包:
- TCP 连接 IP 为 CDN 私有/边缘 IP。
- HTTP `Host` 为原始域名。
- TLS ClientHello 中 **无 SNI** 扩展。
## 8. 平台配置事项
- **Android**: 参照 Android SDK 文档配置混淆。
- **iOS**: 如果是手动集成 Flutter 插件,请确保 iOS 模块已包含依赖的静态库,并设置 `Allow Arbitrary Loads` (如果启用 HTTP)。
## 9. 常见问题
1. **Flutter 端报错NO_IP_AVAILABLE**
- SDK 尚未解析出有效结果,请确认域名是否已在控制台添加并配置规则。
2. **请求报错TLS_EMPTY_SNI_FAILED**
- 仅支持 HTTPS 网站。如果所有 IP 尝试均失败,请检查网络权限及服务端防火墙。

View File

@@ -0,0 +1,98 @@
# Edge HTTPDNS 用户使用手册
欢迎使用 Edge HTTPDNS 服务。本文档旨在帮助您快速完成应用创建、域名配置及解析测试,实现精准、安全的业务调度。
---
## 1. 快速入门流程
1. **创建应用**获取接入凭证AppId 和 SecretKey
2. **添加域名**:登记需要通过 HTTPDNS 解析的业务域名。
3. **自定义解析规则**:设置域名对应的 IP 地址及智能分流规则。
4. **解析测试**:通过沙箱工具验证解析是否生效。
5. **集成 SDK**:将解析功能集成至您的 App 中。
---
## 2. 应用管理
应用是您接入 HTTPDNS 的基础单元。
### 2.1 创建应用
1. 登录用户控制台,点击 **HTTPDNS -> 应用列表**
2. 点击 **创建应用**
3. 填写应用名称如“我的安卓App”
4. 系统会自动关联默认的服务域名,无需手动选择。
### 2.2 获取接入凭证
在应用详情页面,您可以找到:
* **AppId**:应用的唯一识别 ID。
* **SecretKey**:签名密钥。**请务必妥善保管,切勿泄露。** 在 SDK 初始化时使用此密钥可开启“签名鉴权”,防止解析接口被他人盗刷。
---
## 3. 域名与记录配置
### 3.1 添加域名
1. 进入应用详情页,切换至 **域名管理** 标签。
2. 点击 **添加域名**,输入您的业务域名(如 `api.example.com`)。
### 3.2 自定义解析规则
**作用**:自定义解析规则允许您根据终端用户的网络环境(如运营商)或物理位置,为其分配最优的访问地址。通过精细化的线路调度,可以有效降低跨境或跨网访问带来的延迟,提升 App 的响应速度。
点击域名后的 **解析规则**,进入详细设置:
* **解析类型**:支持 **A 记录 (IPv4)****AAAA 记录 (IPv6)**
* **线路选择**:可选择针对特定运营商(如:移动、电信、联通)或特定地域(如:浙江省、海外)进行精准匹配。若不选择则代表全局默认配置。
* **解析结果**:填写您的服务器目标 IP 地址。
* **TTL**:解析结果在客户端缓存的时间。默认为 30 秒,建议保持默认以兼顾调度灵活性。
---
## 4. 配合 CDN 实现网络加速与安全
Edge HTTPDNS 不仅仅提供域名解析功能,更通过与 CDN 节点的深度集成,解决了移动端常见的 **HTTPS SNI 隐匿访问**及 **跨运营商加速**问题。
### 4.1 自动获取 CDN 边缘节点
如果您在系统内开通了 **CDN 加速服务**,只需将业务域名配置在 CDN 平台中,并将其 CNAME 解析指向 CDN 提供的地址:
* **智能调度**HTTPDNS 会自动识别该域名已接入 CDN并针对终端用户的地理位置和运营商智能返回最优的 **CDN 边缘节点 IP**
* **无感知兜底**:如果域名未接入 CDN 或未配置解析规则HTTPDNS 将自动回源查询 **权威 DNS**,并返回业务真实的源站 IP确保解析永不中断。
### 4.2 解决 HTTPS SNI 隐匿问题
在使用 IP 直连(如 `https://1.2.3.4/path`)访问 HTTPS 业务时,传统的网络库会因为无法获取正确的 Host 导致 SSL 握手失败。
**我们的方案:**
* **配合 CDN 节点**:我们的 CDN 节点已针对 HTTPDNS 进行了特殊适配。
* **SDK 自动适配**SDK 内部集成了标准适配器。在您的代码中,只需配合解析出的 IP 设置 HTTP 请求头的 `Host` 字段,即可透明地完成 SNI 握手,无需复杂的 SSL 改写逻辑。
* **稳定性保障**:通过 CDN 节点的全局负载均衡即使某个节点异常HTTPDNS 也会实时踢除并将流量导向其他可用节点,确保业务高可用。
---
## 4. 调试与验证
### 4.1 在线解析测试
在左侧菜单进入 **解析测试**
1. 选择您创建的 **应用**、**HTTPDNS 服务域名** 和 **待解析域名**
2. **模拟客户端 IP**(可选):输入特定地区的 IP验证该地区的解析结果是否符合预期地域调度验证
3. 点击 **在线解析**,查看返回的具体 IP 列表。
### 4.2 访问日志查询
**访问日志** 中,您可以实时监控解析请求:
* 查看各个 AppId 下域名的解析成功率。
* 查看请求的来源 IP、耗时以及命中的路由规则。
---
## 5. 获取 SDK
**应用详情 -> SDK下载** 中:
* 您可以下载最新版本的 Android、iOS 或 Flutter SDK 压缩包。
* 查看配套的 **SDK 集成文档**
---
## 6. 常见问题 (FAQ)
* **Q为什么我设置了记录解析测试却返回为空**
* A请检查记录是否已启用或者检查该域名是否已被添加到对应的 AppId 允许列表下。
* **Q如何应对冷启动时的解析延迟**
* A建议在 SDK 初始化后调用“解析预热”接口,提前将热点域名加载至缓存。
* **QSecretKey 泄露了怎么办?**
* A请在应用设置中重置 SecretKey并在 App 代码中同步更新。

View File

@@ -0,0 +1,82 @@
# Edge HTTPDNS 管理员配置手册
本文档汇总了 Edge HTTPDNS 的核心配置流程,重点介绍了集群管理、节点在线安装以及多集群调度的详细操作。
---
## 1. 集群管理
集群是 HTTPDNS 服务的基本组织单元。通过设置“默认”角色,可以实现应用配置的自动关联与 SDK 侧的高可用容灾。
### 1.1 默认集群角色定义
在“集群设置”中,您可以将集群开启为 **“设为默认集群”**,并指派以下角色:
* **默认主集群**
* **自动关联**:当在控制台“添加应用”时,系统会自动将该应用关联到此默认主集群。
* **服务首选**SDK 初始化后,会优先使用该集群的服务域名进行解析请求。
* **默认备用集群**
* **自动容灾**当默认主集群的节点全部宕机或网络不可达时SDK 会自动切换至默认备用集群进行解析,确保业务不中断。
* **自动关联**:与主集群一样,新应用创建时也会自动关联此备用集群信息。
> **注意**:同一时刻,系统内仅允许存在一个“默认主集群”和一个“默认备用集群”。新设置的默认集群会自动取消之前的旧设置。
### 1.2 核心参数配置
* **服务域名**:该集群对外提供 HTTPDNS 服务的接入地址。
* **降级超时容忍度**:指节点回源查询上游 DNS 时的最大等待时间(单位:毫秒)。若超过此阈值未获得结果,将视作解析失败。该选项可在“集群设置”中统一调整。
* **默认 TTL**:解析结果在客户端缓存的缺省时长(默认 30s
* **TLS 设置**
* **端口绑定**:通常绑定 `443`
* **SSL 证书**:必须配置合法证书,否则 SDK 的 HTTPS 请求将失败。
* **TLS 版本**:默认支持 TLS 1.1 及以上版本。
---
## 2. 节点安装与维护
节点是处理解析请求的实体。Edge HTTPDNS 支持“在线安装”。
### 2.1 在线安装
1. **创建节点**:在集群下点击“创建节点”,填写名称及公网 IP。
2. **配置 SSH**:在节点详情中点击“设置 SSH”输入服务器的 Host、Port 及登录授权。
3. **启动安装**:在“安装信息”页面点击 **[开始安装]**。
* 自动下发二进制文件及服务脚本。
* 自动生成 `configs/api_httpdns.yaml`(包含节点识别需要的 `nodeId``secret`)。
4. **实时状态**:安装成功后,节点状态变为 **[在线]**,控制台每 30 秒更新一次节点的 CPU、内存及负载数据。
---
## 3. 应用与解析规则
### 3.1 接入应用管理
* **AppId/SecretKey**:创建应用后生成的凭证。应用在创建时已根据上述“默认集群”设定自动关联了服务入口。
* **鉴权配置**:开启“签名鉴权”可配合 SDK 的 `setSecretKey` 接口,杜绝接口被盗刷风险。
### 3.2 智能解析策略
* **线路/地域匹配**:根据来源运营商和地理位置返回最优 IP。
---
## 4. 调试与监控
### 4.1 解析测试
**调试工具 -> 解析测试** 中,您可以模拟客户端请求:
* 支持指定 **目标应用** 与 **所属集群**
* 支持模拟 **客户端 IP** 以验证 ECS 掩码及地域调度效果。
* 实时展示请求 URL、客户端归属地、命中线路以及解析结果IP 列表与 TTL
### 4.2 访问日志
**访问日志** 菜单中,可实时查阅所有终端发起的解析请求:
* 记录包括:请求时间、客户端 IP/操作系统、SDK 版本、解析域名、耗时以及最终返回的 IP 结果。
* 支持按 AppID、域名、状态成功/失败)或关键字搜索排查。
### 4.3 运行日志
记录服务端及节点的底层运行事件:
* 包括节点心跳、SSL 证书加载、API 连接状态等系统级信息。
* 分为 Error、Warning、Info 等级别,是排查节点离线或连接故障的首要工具。
---
## 5. 常见问题
* **节点与 API 时间偏离**:若节点时间与 API Server 相差超过 30s会导致鉴权失败SIGN_INVALID。请务必开启 NTP 时间同步。
* **SDK 无法连接备用集群**:请检查默认备用集群的 SSL 证书是否有效,以及防火墙端口是否开放。

Binary file not shown.

View File

@@ -0,0 +1,124 @@
# iOS SDK 集成文档Edge HTTPDNS
## 1. 版本与依赖
- SDK 模块:`EdgeHttpDNS/sdk/ios/NewHttpDNS`
- 支持系统iOS 11.0+
- 集成方式:
- **CocoaPods**:在 `Podfile` 中添加 `pod 'NewHTTPDNS', :path => 'path/to/sdk/ios'`
- **手动集成**:将 `NewHttpDNS` 源码或编译后的静态库导入项目并添加依赖的系统库Foundation, CFNetwork, SystemConfiguration
## 2. SNI 行为说明(关键)
1. **/resolve 请求链路**SDK -> 你的 HTTPDNS 服务域名)
- 使用标准 HTTPS 请求。
- 默认携带 SNI用于通过 WAF/CDN 识别服务域名)。
2. **业务请求链路**(拿到 CDN IP 后通过 `HttpdnsEdgeService` 发起业务 HTTPS
- **IP 直连 + No-SNI**SDK 会建立与 IP 的连接,并将 `NSURLRequest``URL` 替换为 IP同时保留 `Host` 头部为原域名。
- **证书校验**:由于清空了 SNI常规 SNI 校验会跳过,需确保后端节点支持 Host 匹配证书。
## 3. 初始化 SDK推荐用 EdgeService 封装)
### Objective-C
```objective-c
#import <NewHttpDNS/HttpdnsEdgeService.h>
HttpdnsEdgeService *service = [[HttpdnsEdgeService alloc] initWithAppId:@"your-app-id"
primaryServiceHost:@"httpdns.example.com"
backupServiceHost:@"httpdns-backup.example.com"
servicePort:443
signSecret:@"your-sign-secret"];
```
### Swift
```swift
import NewHttpDNS
let service = HttpdnsEdgeService(appId: "your-app-id",
primaryServiceHost: "httpdns.example.com",
backupServiceHost: "httpdns-backup.example.com",
servicePort: 443,
signSecret: "your-sign-secret")
```
## 4. 解析域名获取 CDN IP
### Objective-C
```objective-c
[service resolveHost:@"api.example.com"
queryType:@"A"
completion:^(HttpdnsEdgeResolveResult * _Nullable result, NSError * _Nullable error) {
if (result) {
NSLog(@"IPv4s: %@", result.ipv4s);
NSLog(@"TTL: %ld", (long)result.ttl);
}
}];
```
### Swift
```swift
service.resolveHost("api.example.com", queryType: "A") { result, error in
if let ips = result?.ipv4s {
print("Resolved IPs: \(ips)")
}
}
```
## 5. 业务请求接入方式
使用 `HttpdnsEdgeService` 提供的 `requestURL` 方法,自动处理 IP 直连与 SNI 隐藏。
### Objective-C
```objective-c
NSURL *url = [NSURL URLWithString:@"https://api.example.com/path?x=1"];
[service requestURL:url
method:@"GET"
headers:@{@"Custom-Header": @"Value"}
body:nil
completion:^(NSData * _Nullable data, NSHTTPURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error) {
NSLog(@"Status Code: %ld", (long)response.statusCode);
// 处理 data
}
}];
```
### Swift
```swift
let url = URL(string: "https://api.example.com/path?x=1")!
service.requestURL(url, method: "GET", headers: ["Custom-Header": "Value"], body: nil) { data, response, error in
if let resp = response {
print("Status Code: \(resp.statusCode)")
}
}
```
## 6. 验证建议
1. **验证 /resolve**
- 观察网络请求,应指向 `https://httpdns.example.com/resolve?appId=...&dn=...`。
- 确认返回 JSON 包含 `code: "SUCCESS"`。
2. **验证业务请求**
- 确认请求握手阶段不携带 SNI 扩展。
- 确认请求的 TCP 连接目标为解析出的私有 IP/CDN IP。
## 7. 常见问题
1. **编译报错:找不到头文件**
- 请确认 `Header Search Paths` 包含 SDK 路径。
- 如果使用 CocoaPods请确保执行 `pod install` 并打开 `.xcworkspace`。
2. **请求返回 403 (Sign Invalid)**
- 确认控制台已开启“签名校验”,且本地传入的 `signSecret` 与控制台一致。
- 确认系统时间正常(差值超过 30s 可能导致签名失效)。
3. **HTTPS 证书验证失败**
- 检查 `HttpdnsEdgeService` 是否能正确匹配证书,通常是在 No-SNI 模式下通过 `Host` 字段匹配。

View File

@@ -130,8 +130,14 @@ func NewResolveServer(quitCh <-chan struct{}, snapshotManager *SnapshotManager)
IdleTimeout: 75 * time.Second,
MaxHeaderBytes: 8 * 1024,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
MinVersion: tls.VersionTLS11,
// /resolve is a small JSON API; pin to HTTP/1.1 to avoid ALPN/h2 handshake variance
// across some clients and middleboxes.
NextProtos: []string{"http/1.1"},
},
// Disable automatic HTTP/2 upgrade on TLS listeners. This keeps handshake behavior
// deterministic for SDK resolve calls.
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){},
}
return instance

View File

@@ -0,0 +1,77 @@
# HTTPDNS Android SDK (SNI Hidden v1.0.0)
## 1. Init
```java
import com.Trust.sdk.android.httpdns.HttpDns;
import com.Trust.sdk.android.httpdns.HttpDnsService;
import com.Trust.sdk.android.httpdns.InitConfig;
String appId = "app1f1ndpo9";
new InitConfig.Builder()
.setContext(context)
.setPrimaryServiceHost("httpdns-a.example.com")
.setBackupServiceHost("httpdns-b.example.com")
.setServicePort(443)
.setSecretKey("your-sign-secret") // optional if sign is enabled
.setEnableHttps(true)
.buildFor(appId);
HttpDnsService httpDnsService = HttpDns.getService(appId);
```
## 2. Resolve
```java
HTTPDNSResult result = httpDnsService.getHttpDnsResultForHostSyncNonBlocking(
"api.business.com",
RequestIpType.auto,
null,
null
);
```
## 3. Official HTTP Adapter (IP + Empty-SNI + Host)
```java
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterOptions;
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterRequest;
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterResponse;
import com.Trust.sdk.android.httpdns.network.HttpDnsHttpAdapter;
HttpDnsHttpAdapter adapter = HttpDns.buildHttpClientAdapter(
httpDnsService,
new HttpDnsAdapterOptions.Builder()
.setConnectTimeoutMillis(3000)
.setReadTimeoutMillis(5000)
.setRequestIpType(RequestIpType.auto)
.setAllowInsecureCertificatesForDebugOnly(false)
.build()
);
HttpDnsAdapterResponse response = adapter.execute(
new HttpDnsAdapterRequest("GET", "https://api.business.com/v1/ping")
);
```
Behavior is fixed:
- Resolve by `/resolve`.
- Connect to resolved IP over HTTPS.
- Keep `Host` header as business domain.
- No fallback to domain direct request.
## 4. Public Errors
- `NO_IP_AVAILABLE`
- `TLS_EMPTY_SNI_FAILED`
- `HOST_ROUTE_REJECTED`
- `RESOLVE_SIGN_INVALID`
## 5. Removed Public Params
Do not use legacy public parameters:
- `accountId`
- `serviceDomain`
- `endpoint`
- `aesSecretKey`

View File

@@ -0,0 +1,68 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/ryan/Downloads/adt-bundle-mac-x86_64-20131030/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-optimizationpasses 3
-dontoptimize
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
-overloadaggressively
#-allowaccessmodification
-useuniqueclassmembernames
-dontwarn com.alibaba.sdk.android.httpdns.net.HttpDnsNetworkDetector
-keeppackagenames com.alibaba.sdk.android.httpdns
-flattenpackagehierarchy com.alibaba.sdk.android.httpdns
-keep class com.alibaba.sdk.android.httpdns.HttpDns{*;}
-keep interface com.alibaba.sdk.android.httpdns.HttpDnsService{*;}
-keep class com.alibaba.sdk.android.httpdns.impl.ErrorImpl{*;}
-keep interface com.alibaba.sdk.android.httpdns.SyncService{*;}
-keep class com.alibaba.sdk.android.httpdns.InitConfig{*;}
-keep class com.alibaba.sdk.android.httpdns.InitConfig$Builder{*;}
-keep class com.alibaba.sdk.android.httpdns.RequestIpType{*;}
-keep interface com.alibaba.sdk.android.httpdns.DegradationFilter{*;}
-keep interface com.alibaba.sdk.android.httpdns.NotUseHttpDnsFilter{*;}
-keep interface com.alibaba.sdk.android.httpdns.HttpDnsCallback{*;}
-keep class com.alibaba.sdk.android.httpdns.ranking.IPRankingBean{*;}
-keep interface com.alibaba.sdk.android.httpdns.ILogger{*;}
-keep interface com.alibaba.sdk.android.httpdns.CacheTtlChanger{*;}
-keep class com.alibaba.sdk.android.httpdns.NetType{*;}
-keepclasseswithmembers class com.alibaba.sdk.android.httpdns.log.HttpDnsLog {
public static *** setLogger(***);
public static *** removeLogger(***);
public static *** enable(***);
}
-keep class com.alibaba.sdk.android.httpdns.HTTPDNSResult{*;}
-keepclasseswithmembers class com.alibaba.sdk.android.httpdns.HttpDnsSettings {
public static *** setDailyReport(***);
public static *** setNetworkChecker(***);
}
-keep class com.alibaba.sdk.android.httpdns.net.HttpDnsNetworkDetector {
public <methods>;
public <fields>;
}
-keep interface com.alibaba.sdk.android.httpdns.HttpDnsSettings$NetworkChecker{*;}
-keep interface com.alibaba.sdk.android.httpdns.HttpDnsSettings$NetworkDetector{*;}
-keep class com.alibaba.sdk.android.httpdns.utils.CommonUtil{
public <methods>;
public <fields>;
}
-keep enum com.alibaba.sdk.android.httpdns.Region {*;}
-keep class com.alibaba.sdk.android.httpdns.exception.InitException{*;}

View File

@@ -6,20 +6,20 @@ Fetched at (UTC): `2026-02-18T09:31:28Z`
## Android SDK
- Upstream repository: `https://github.com/aliyun/alibabacloud-httpdns-android-sdk`
- Upstream repository: `https://github.com/TrustAPP/Trustcloud-httpdns-android-sdk`
- Locked commit: `eeb17d677161ec94b5f41a9d6437501ddc24e6d2`
- Local path: `EdgeHttpDNS/sdk/android`
## iOS SDK
- Upstream repository: `https://github.com/aliyun/alibabacloud-httpdns-ios-sdk`
- Upstream repository: `https://github.com/TrustAPP/Trustcloud-httpdns-ios-sdk`
- Locked commit: `19f5bacd1d1399a00ba654bb72ababb3e91d0a3a`
- Local path: `EdgeHttpDNS/sdk/ios`
## Flutter Plugin SDK
- Upstream repository: `https://github.com/aliyun/alicloud-flutter-demo`
- Upstream repository: `https://github.com/TrustAPP/Trust-flutter-demo`
- Locked commit: `588b807e5480d8592c57d439a6b1c52e8c313569`
- Imported subtree: `httpdns_flutter_demo/packages/aliyun_httpdns`
- Local path: `EdgeHttpDNS/sdk/flutter/aliyun_httpdns`
- Imported subtree: `httpdns_flutter_demo/packages/TrustAPP_httpdns`
- Local path: `EdgeHttpDNS/sdk/flutter/TrustAPP_httpdns`

View File

@@ -1,27 +1,27 @@
# Third-Party Notices for EdgeHttpDNS SDK Sources
This directory includes third-party source snapshots imported from Alibaba Cloud open-source repositories.
This directory includes third-party source snapshots imported from Trust Cloud open-source repositories.
## 1) Android SDK (`EdgeHttpDNS/sdk/android`)
- Source: `https://github.com/aliyun/alibabacloud-httpdns-android-sdk`
- Source: `https://github.com/TrustAPP/Trustcloud-httpdns-android-sdk`
- Commit: `eeb17d677161ec94b5f41a9d6437501ddc24e6d2`
- License file in imported source:
- `EdgeHttpDNS/sdk/android/LICENSE`
- Observed license: MIT License
## 2) Flutter plugin (`EdgeHttpDNS/sdk/flutter/aliyun_httpdns`)
## 2) Flutter plugin (`EdgeHttpDNS/sdk/flutter/TrustAPP_httpdns`)
- Source: `https://github.com/aliyun/alicloud-flutter-demo`
- Source: `https://github.com/TrustAPP/Trust-flutter-demo`
- Commit: `588b807e5480d8592c57d439a6b1c52e8c313569`
- Imported subtree: `httpdns_flutter_demo/packages/aliyun_httpdns`
- Imported subtree: `httpdns_flutter_demo/packages/TrustAPP_httpdns`
- License file in imported source:
- `EdgeHttpDNS/sdk/flutter/aliyun_httpdns/LICENSE`
- `EdgeHttpDNS/sdk/flutter/TrustAPP_httpdns/LICENSE`
- Observed license: MIT License
## 3) iOS SDK (`EdgeHttpDNS/sdk/ios`)
- Source: `https://github.com/aliyun/alibabacloud-httpdns-ios-sdk`
- Source: `https://github.com/TrustAPP/Trustcloud-httpdns-ios-sdk`
- Commit: `19f5bacd1d1399a00ba654bb72ababb3e91d0a3a`
- Current status:
- No standalone top-level `LICENSE` file was found in the upstream repository snapshot imported here.

View File

@@ -1,11 +1,11 @@
# HTTPDNS Android SDK (SNI Hidden v1.0.0)
# HTTPDNS Android SDK (SNI Hidden v1.0.0)
## 1. Init
```java
import com.alibaba.sdk.android.httpdns.HttpDns;
import com.alibaba.sdk.android.httpdns.HttpDnsService;
import com.alibaba.sdk.android.httpdns.InitConfig;
import com.Trust.sdk.android.httpdns.HttpDns;
import com.Trust.sdk.android.httpdns.HttpDnsService;
import com.Trust.sdk.android.httpdns.InitConfig;
String appId = "app1f1ndpo9";
@@ -35,10 +35,10 @@ HTTPDNSResult result = httpDnsService.getHttpDnsResultForHostSyncNonBlocking(
## 3. Official HTTP Adapter (IP + Empty-SNI + Host)
```java
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterOptions;
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterRequest;
import com.alibaba.sdk.android.httpdns.network.HttpDnsAdapterResponse;
import com.alibaba.sdk.android.httpdns.network.HttpDnsHttpAdapter;
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterOptions;
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterRequest;
import com.Trust.sdk.android.httpdns.network.HttpDnsAdapterResponse;
import com.Trust.sdk.android.httpdns.network.HttpDnsHttpAdapter;
HttpDnsHttpAdapter adapter = HttpDns.buildHttpClientAdapter(
httpDnsService,

View File

@@ -26,7 +26,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
forTest {
// 注意这里的配置并不是需要编译forTest的app而是避免httpdns-sdk在AndroidStudio改为end2end运行测试时 BuildVariants报错
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慺orTest鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
initWith release
debuggable true
}
@@ -67,7 +67,7 @@ android {
}
end2end {
// 注意这里的配置并不是需要编译end2end的app而是避免httpdns-sdk在AndroidStudio改为end2end运行测试时 BuildVariants报错
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慹nd2end鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
}
}
}

View File

@@ -33,10 +33,10 @@ public class HttpDnsActivity extends BaseActivity {
public static final String SCHEMA_HTTP = "http://";
public static final String TAOBAO_URL = "www.taobao.com";
public static final String ALIYUN_URL = "www.aliyun.com";
public static final String Aliyun_URL = "www.Aliyun.com";
public static final String[] hosts = new String[]{
TAOBAO_URL, ALIYUN_URL
TAOBAO_URL, Aliyun_URL
};
public static String getUrl(String schema, String host) {
@@ -44,15 +44,15 @@ public class HttpDnsActivity extends BaseActivity {
}
/**
* 要请求的schema
* 瑕佽姹傜殑schema
*/
private String schema = SCHEMA_HTTPS;
/**
* 要请求的域名
* 瑕佽姹傜殑鍩熷悕
*/
private String host = ALIYUN_URL;
private String host = Aliyun_URL;
/**
* 要解析的ip类型
* 瑕佽В鏋愮殑ip绫诲瀷
*/
private RequestIpType requestIpType = RequestIpType.v4;
@@ -69,77 +69,77 @@ public class HttpDnsActivity extends BaseActivity {
okHttpRequest = new OkHttpRequest(this);
networkRequest = httpUrlConnectionRequest;
addFourButton("切换实例", v -> {
addFourButton("鍒囨崲瀹炰緥", v -> {
MyApp.getInstance().changeHolder();
sendLog("httpdns实例已切换");
}, "获取配置", v -> {
sendLog("httpdns瀹炰緥宸插垏鎹?);
}, "鑾峰彇閰嶇疆", v -> {
sendLog(MyApp.getInstance().getCurrentHolder().getCurrentConfig());
sendLog("要解析的域名是" + host);
sendLog("要解析的ip类型是" + requestIpType.name());
sendLog("要模拟请求的url是" + getUrl(schema, host));
sendLog("模拟请求的网络框架是" + (networkRequest == httpUrlConnectionRequest ? " HttpUrlConnection" : "OkHttp"));
}, "清除配置缓存", v -> {
sendLog("瑕佽В鏋愮殑鍩熷悕鏄? + host);
sendLog("瑕佽В鏋愮殑ip绫诲瀷鏄? + requestIpType.name());
sendLog("瑕佹ā鎷熻姹傜殑url鏄? + getUrl(schema, host));
sendLog("妯℃嫙璇锋眰鐨勭綉缁滄鏋舵槸" + (networkRequest == httpUrlConnectionRequest ? " HttpUrlConnection" : "OkHttp"));
}, "娓呴櫎閰嶇疆缂撳瓨", v -> {
MyApp.getInstance().getCurrentHolder().cleanSp();
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "配置缓存清除, 重启生效");
}, "清除日志", v -> cleanLog());
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "閰嶇疆缂撳瓨娓呴櫎, 閲嶅惎鐢熸晥");
}, "娓呴櫎鏃ュ織", v -> cleanLog());
addTwoButton("开启https", v -> {
addTwoButton("寮€鍚痟ttps", v -> {
MyApp.getInstance().getCurrentHolder().setEnableHttps(true);
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "开启https");
}, "关闭https", v -> {
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "寮€鍚痟ttps");
}, "鍏抽棴https", v -> {
MyApp.getInstance().getCurrentHolder().setEnableHttps(false);
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "关闭https");
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "鍏抽棴https");
});
addTwoButton("允许过期IP", v -> {
addTwoButton("鍏佽杩囨湡IP", v -> {
MyApp.getInstance().getCurrentHolder().setEnableExpiredIp(true);
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "允许过期IP");
}, "不允许过期IP", v -> {
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "鍏佽杩囨湡IP");
}, "涓嶅厑璁歌繃鏈烮P", v -> {
MyApp.getInstance().getCurrentHolder().setEnableExpiredIp(false);
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "不允许过期IP");
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "涓嶅厑璁歌繃鏈烮P");
});
addTwoButton("允许持久化缓存", v -> {
addTwoButton("鍏佽鎸佷箙鍖栫紦瀛?, v -> {
MyApp.getInstance().getCurrentHolder().setEnableCacheIp(true);
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "允许持久化缓存");
}, "不允许持久化缓存", v -> {
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "鍏佽鎸佷箙鍖栫紦瀛?);
}, "涓嶅厑璁告寔涔呭寲缂撳瓨", v -> {
MyApp.getInstance().getCurrentHolder().setEnableCacheIp(false);
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "不允许持久化缓存");
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "涓嶅厑璁告寔涔呭寲缂撳瓨");
});
addThreeButton("设置中国大陆", v -> {
addThreeButton("璁剧疆涓浗澶ч檰", v -> {
MyApp.getInstance().getCurrentHolder().setRegion(null);
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "切换到中国大陆");
}, "设置中国香港", v -> {
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "鍒囨崲鍒颁腑鍥藉ぇ闄?);
}, "璁剧疆涓浗棣欐腐", v -> {
MyApp.getInstance().getCurrentHolder().setRegion("hk");
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "切换到中国香港");
}, "设置新加坡", v -> {
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "鍒囨崲鍒颁腑鍥介?);
}, "璁剧疆鏂板姞鍧?, v -> {
MyApp.getInstance().getCurrentHolder().setRegion("sg");
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "切换到新加坡");
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "鍒囨崲鍒版柊鍔犲潯");
});
addEditTextButton("超时时长 ms", "设置超时ms", view -> {
addEditTextButton("瓒呮椂鏃堕暱 ms", "璁剧疆瓒呮椂ms", view -> {
EditText et = (EditText) view;
int timeout = Integer.parseInt(et.getEditableText().toString());
MyApp.getInstance().getCurrentHolder().setTimeout(timeout);
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "设置超时 " + timeout);
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "璁剧疆瓒呮椂 " + timeout);
});
addTwoButton("开启降级", v -> {
// 注意降级过滤器现在需要通过InitConfig设置重启应用生效
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "降级功能需要通过InitConfig配置");
}, "关闭降级", v -> {
// 注意降级过滤器现在需要通过InitConfig设置重启应用生效
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "降级功能需要通过InitConfig配置");
addTwoButton("檷绾?, v -> {
// 娉ㄦ剰锛氶檷绾ц繃婊ゅ櫒鐜板湪闇€瑕侀€氳繃InitConfig璁剧疆锛岄噸鍚簲鐢ㄧ敓鏁?
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "闄嶇骇鍔熻兘闇€瑕侀€氳繃InitConfig閰嶇疆");
}, "鍏抽棴闄嶇骇", v -> {
// 娉ㄦ剰锛氶檷绾ц繃婊ゅ櫒鐜板湪闇€瑕侀€氳繃InitConfig璁剧疆锛岄噸鍚簲鐢ㄧ敓鏁?
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "闄嶇骇鍔熻兘闇€瑕侀€氳繃InitConfig閰嶇疆");
});
addTwoButton("开启网络变化后预解析", v -> {
// 注意此功能现在需要通过InitConfig设置重启应用生效
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "网络变化后预解析需要通过InitConfig配置");
}, "关闭网络变化后预解析", v -> {
// 注意此功能现在需要通过InitConfig设置重启应用生效
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "网络变化后预解析需要通过InitConfig配置");
addTwoButton("寮€鍚綉缁滃彉鍖栧悗棰勮В鏋?, v -> {
// 娉ㄦ剰锛氭鍔熻兘鐜板湪闇€瑕侀€氳繃InitConfig璁剧疆锛岄噸鍚簲鐢ㄧ敓鏁?
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "缃戠粶鍙樺寲鍚庨瑙f瀽闇瑕侀氳繃InitConfig閰嶇疆");
}, "鍏抽棴缃戠粶鍙樺寲鍚庨瑙f瀽", v -> {
// 娉ㄦ剰锛氭鍔熻兘鐜板湪闇€瑕侀€氳繃InitConfig璁剧疆锛岄噸鍚簲鐢ㄧ敓鏁?
sendLog(MyApp.getInstance().getCurrentHolder().getAccountId() + "缃戠粶鍙樺寲鍚庨瑙f瀽闇瑕侀氳繃InitConfig閰嶇疆");
});
addView(R.layout.item_autocomplete_edittext_button, view -> {
@@ -147,21 +147,21 @@ public class HttpDnsActivity extends BaseActivity {
final EditText etOne = view.findViewById(R.id.etOne);
Button btnOne = view.findViewById(R.id.btnOne);
actvOne.setHint("请输入域名");
actvOne.setHint("璇疯緭鍏ュ煙鍚?);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(),
android.R.layout.simple_dropdown_item_1line, hosts);
actvOne.setAdapter(adapter);
etOne.setHint("请输入自定义ttl");
etOne.setHint("璇疯緭鍏ヨ嚜瀹氫箟ttl");
btnOne.setText("指定域名ttl s");
btnOne.setText("鎸囧畾鍩熷悕ttl s");
btnOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String host = actvOne.getEditableText().toString();
int ttl = Integer.parseInt(etOne.getEditableText().toString());
MyApp.getInstance().getCurrentHolder().setHostTtl(host, ttl);
sendLog("指定域名" + host + "的ttl为" + ttl + "");
sendLog("鎸囧畾鍩熷悕" + host + "鐨則tl涓? + ttl + "?);
}
});
});
@@ -171,124 +171,124 @@ public class HttpDnsActivity extends BaseActivity {
final EditText etOne = view.findViewById(R.id.etOne);
Button btnOne = view.findViewById(R.id.btnOne);
actvOne.setHint("域名");
actvOne.setHint("鍩熷悕");
ArrayAdapter<String> adapter = new ArrayAdapter<>(getApplicationContext(),
android.R.layout.simple_dropdown_item_1line, hosts);
actvOne.setAdapter(adapter);
etOne.setHint("请输入端口");
etOne.setHint("璇疯緭鍏ョ鍙?);
btnOne.setText("添加ipRanking配置");
btnOne.setText("娣诲姞ipRanking閰嶇疆");
btnOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String host = actvOne.getEditableText().toString();
int port = Integer.parseInt(etOne.getEditableText().toString());
MyApp.getInstance().getCurrentHolder().addIpProbeItem(new IPRankingBean(host, port));
sendLog("添加域名" + host + " 探测");
sendLog("娣诲姞鍩熷悕" + host + " ");
}
});
});
addAutoCompleteTextViewButton(hosts, "主站域名", "添加主站域名", view -> {
addAutoCompleteTextViewButton(hosts, "涓荤珯鍩熷悕", "娣诲姞涓荤珯鍩熷悕", view -> {
AutoCompleteTextView actvOne = (AutoCompleteTextView) view;
String host = actvOne.getEditableText().toString();
MyApp.getInstance().getCurrentHolder().addHostWithFixedIp(host);
sendLog("添加主站域名" + host);
sendLog("娣诲姞涓荤珯鍩熷悕" + host);
});
addAutoCompleteTextViewButton(hosts, "域名", "删除指定域名的缓存", view -> {
addAutoCompleteTextViewButton(hosts, "鍩熷悕", "鍒犻櫎鎸囧畾鍩熷悕鐨勭紦瀛?, view -> {
AutoCompleteTextView actvOne = (AutoCompleteTextView) view;
String host = actvOne.getEditableText().toString();
ArrayList<String> list = new ArrayList<>();
list.add(host);
MyApp.getInstance().getService().cleanHostCache(list);
sendLog("清除指定host的缓存" + host);
sendLog("娓呴櫎鎸囧畾host鐨勭紦瀛? + host);
});
addOneButton("清除所有缓存", v -> {
addOneButton("娓呴櫎鎵鏈夌紦瀛?, v -> {
MyApp.getInstance().getService().cleanHostCache(null);
sendLog("清除所有缓存");
sendLog("娓呴櫎鎵€鏈夌紦瀛?);
});
addFourButton("获取当前网络状态", v -> {
addFourButton("鑾峰彇褰撳墠缃戠粶鐘舵?, v -> {
NetType type = HttpDnsNetworkDetector.getInstance().getNetType(getApplicationContext());
sendLog("获取网络状态 " + type.name());
}, "禁用网络状态缓存", v -> {
sendLog("鑾峰彇缃戠粶鐘舵€?" + type.name());
}, "绂佺敤缃戠粶鐘舵€佺紦瀛?, v -> {
HttpDnsNetworkDetector.getInstance().disableCache(true);
sendLog("网络状态 禁用缓存 ");
}, "开启网络状态缓存", v -> {
sendLog("缃戠粶鐘舵?绂佺敤缂撳瓨 ");
}, "綉缁滅姸鎬佺紦瀛?, v -> {
HttpDnsNetworkDetector.getInstance().disableCache(false);
sendLog("网络状态 开启缓存 ");
}, "清除网络状态缓存", v -> {
sendLog("缃戠粶鐘舵€?寮€鍚紦瀛?");
}, "娓呴櫎缃戠粶鐘舵€佺紦瀛?, v -> {
HttpDnsNetworkDetector.getInstance().cleanCache(false);
sendLog("网络状态清除缓存 ");
sendLog("缃戠粶鐘舵佹竻闄ょ紦瀛?");
});
addTwoButton("禁止读取IP", v -> {
addTwoButton("绂佹璇诲彇IP", v -> {
HttpDnsNetworkDetector.getInstance().setCheckInterface(false);
sendLog("查询网络状态时 禁止读取IP");
}, "允许读取IP", v -> {
sendLog("鏌ヨ缃戠粶鐘舵佹椂 绂佹璇诲彇IP");
}, "鍏佽璇诲彇IP", v -> {
HttpDnsNetworkDetector.getInstance().setCheckInterface(true);
sendLog("查询网络状态时 允许读取IP");
sendLog("鏌ヨ缃戠粶鐘舵佹椂 鍏佽璇诲彇IP");
});
addAutoCompleteTextViewButton(hosts, "域名", "设置检测网络使用的域名", view -> {
addAutoCompleteTextViewButton(hosts, "鍩熷悕", "璁剧疆妫娴嬬綉缁滀娇鐢ㄧ殑鍩熷悕", view -> {
AutoCompleteTextView actvOne = (AutoCompleteTextView) view;
String host = actvOne.getEditableText().toString();
HttpDnsNetworkDetector.getInstance().setHostToCheckNetType(host);
sendLog("设置检测网络状态使用的域名为" + host);
sendLog("璁剧疆妫娴嬬綉缁滅姸鎬佷娇鐢ㄧ殑鍩熷悕涓? + host);
});
addTwoButton("模拟请求使用https请求", v -> {
addTwoButton("妯℃嫙璇锋眰浣跨敤https璇锋眰", v -> {
schema = SCHEMA_HTTPS;
sendLog("测试url使用https");
}, "模拟请求使用http请求", v -> {
sendLog("娴嬭瘯url浣跨敤https");
}, "妯℃嫙璇锋眰浣跨敤http璇锋眰", v -> {
schema = SCHEMA_HTTP;
sendLog("测试url使用http");
sendLog("娴嬭瘯url浣跨敤http");
});
addTwoButton("HttpUrlConnection", v -> {
networkRequest = httpUrlConnectionRequest;
sendLog("指定网络实现方式为HttpUrlConnection");
sendLog("鎸囧畾缃戠粶瀹炵幇鏂瑰紡涓篐ttpUrlConnection");
}, "Okhttp", v -> {
networkRequest = okHttpRequest;
sendLog("指定网络实现方式为okhttp");
sendLog("鎸囧畾缃戠粶瀹炵幇鏂瑰紡涓簅khttp");
});
addFourButton("指定v4", v -> {
addFourButton("鎸囧畾v4", v -> {
requestIpType = RequestIpType.v4;
sendLog("要解析的IP类型指定为ipv4");
}, "指定v6", v -> {
sendLog("瑕佽В鏋愮殑IP绫诲瀷鎸囧畾涓篿pv4");
}, "鎸囧畾v6", v -> {
requestIpType = RequestIpType.v6;
sendLog("要解析的IP类型指定为ipv6");
}, "都解析", v -> {
sendLog("瑕佽В鏋愮殑IP绫诲瀷鎸囧畾涓篿pv6");
}, "閮借В鏋?, v -> {
requestIpType = RequestIpType.both;
sendLog("要解析的IP类型指定为ipv4和ipv6");
}, "自动判断", v -> {
sendLog("瑕佽В鏋愮殑IP绫诲瀷鎸囧畾涓篿pv4鍜宨pv6");
}, "姩鍒ゆ柇", v -> {
requestIpType = RequestIpType.auto;
sendLog("要解析的IP类型根据网络情况自动判断");
sendLog("瑕佽В鏋愮殑IP绫诲瀷鏍规嵁缃戠粶鎯呭喌鑷姩鍒ゆ柇");
});
addAutoCompleteTextViewButton(hosts, "域名", "指定要解析的域名", new OnButtonClick() {
addAutoCompleteTextViewButton(hosts, "鍩熷悕", "鎸囧畾瑕佽В鏋愮殑鍩熷悕", new OnButtonClick() {
@Override
public void onBtnClick(View view) {
AutoCompleteTextView actvOne = (AutoCompleteTextView) view;
host = actvOne.getEditableText().toString();
sendLog("要解析的域名" + host);
sendLog("瑕佽В鏋愮殑鍩熷悕" + host);
}
});
addTwoButton("异步解析", v -> worker.execute(new Runnable() {
addTwoButton("寮傛瑙f瀽", v -> worker.execute(new Runnable() {
@Override
public void run() {
sendLog("开始发起网络请求");
sendLog("网络实现方式为" + (networkRequest == httpUrlConnectionRequest ? "HttpUrlConnection" : "okhttp"));
sendLog("濮嬪彂璧风綉缁滆?);
sendLog("缃戠粶瀹炵幇鏂瑰紡涓? + (networkRequest == httpUrlConnectionRequest ? "HttpUrlConnection" : "okhttp"));
String url = getUrl(schema, host);
sendLog("url is " + url);
sendLog("httpdns 使用 异步解析api");
sendLog("指定解析ip类型为" + requestIpType.name());
sendLog("httpdns 浣跨敤 寮傛瀽api");
sendLog("鎸囧畾瑙瀽ip绫诲瀷涓? + requestIpType.name());
networkRequest.updateHttpDnsConfig(true, requestIpType);
try {
String response = networkRequest.httpGet(url);
@@ -298,40 +298,40 @@ public class HttpDnsActivity extends BaseActivity {
response = response.substring(0, 30) + "...";
}
}
sendLog("请求结束 response is " + response + " 完整记录请看logcat日志");
sendLog("璇锋眰缁撴潫 response is " + response + " 瀹屾暣璁板綍璇风湅logcat鏃ュ織");
} catch (Exception e) {
e.printStackTrace();
sendLog("请求结束 发生异常 " + e.getClass().getName() + e.getMessage() + " 完整记录请看logcat日志");
sendLog("璇锋眰缁撴潫 鍙戠敓寮傚父 " + e.getClass().getName() + e.getMessage() + " 瀹屾暣璁板綍璇风湅logcat鏃ュ織");
}
}
}), "同步解析", v -> worker.execute(() -> {
sendLog("开始发起网络请求");
sendLog("网络实现方式为" + (networkRequest == httpUrlConnectionRequest ? "HttpUrlConnection" : "okhttp"));
}), "鍚屾瑙f瀽", v -> worker.execute(() -> {
sendLog("寮€濮嬪彂璧风綉缁滆姹?);
sendLog("缃戠粶瀹炵幇鏂瑰紡涓? + (networkRequest == httpUrlConnectionRequest ? "HttpUrlConnection" : "okhttp"));
String url = getUrl(schema, host);
sendLog("url is " + url);
sendLog("httpdns 使用 同步解析api");
sendLog("指定解析ip类型为" + requestIpType.name());
sendLog("httpdns 浣跨敤 鍚屾瑙f瀽api");
sendLog("鎸囧畾瑙瀽ip绫诲瀷涓? + requestIpType.name());
networkRequest.updateHttpDnsConfig(false, requestIpType);
try {
String response = networkRequest.httpGet(url);
if (response != null && response.length() > 30) {
response = response.substring(0, 30) + "...";
}
sendLog("请求结束 response is " + response + " 完整记录请看logcat日志");
sendLog("璇锋眰缁撴潫 response is " + response + " 瀹屾暣璁板綍璇风湅logcat鏃ュ織");
} catch (Exception e) {
e.printStackTrace();
sendLog("请求结束 发生异常 " + e.getClass().getName() + e.getMessage() + " 完整记录请看logcat日志");
sendLog("璇锋眰缁撴潫 鍙戠敓寮傚父 " + e.getClass().getName() + e.getMessage() + " 瀹屾暣璁板綍璇风湅logcat鏃ュ織");
}
}));
addThreeButton("发起预解析", v -> worker.execute(() -> {
addThreeButton("鍙戣捣棰勮В鏋?, v -> worker.execute(() -> {
ArrayList<String> tmp = new ArrayList<>();
Collections.addAll(tmp, hosts);
MyApp.getInstance().getService().setPreResolveHosts(tmp, requestIpType);
sendLog("已发起预解析请求");
}), "跳转SDNS测试界面",
v -> startActivity(new Intent(HttpDnsActivity.this, SDNSActivity.class)), "跳转Webview测试界面", new View.OnClickListener() {
sendLog("宸插彂璧烽瑙f瀽璇锋眰");
}), "璺宠浆SDNS娴嬭瘯鐣岄潰",
v -> startActivity(new Intent(HttpDnsActivity.this, SDNSActivity.class)), "璺宠浆Webview娴嬭瘯鐣岄潰", new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(HttpDnsActivity.this, WebViewActivity.class));
@@ -340,17 +340,18 @@ public class HttpDnsActivity extends BaseActivity {
final String[] validHosts = new String[]{
"www.aliyun.com",
"www.Aliyun.com",
"www.taobao.com"
};
addTwoButton("并发异步请求", v -> {
addTwoButton("骞跺彂寮傛璇锋眰", v -> {
ThreadUtil.multiThreadTest(validHosts, 100, 20, 10 * 60 * 1000, true, requestIpType);
sendLog("异步api并发测试开始大约耗时10分钟请最后查看logcat日志确认结果建议关闭httpdns日志避免日志量过大");
}, "并发同步请求", v -> {
sendLog("寮傛api骞跺彂娴嬭瘯寮€濮嬶紝澶х害鑰楁椂10鍒嗛挓锛岃鏈€鍚庢煡鐪媗ogcat鏃ュ織锛岀璁ょ粨鏋滐紝寤鸿鍏抽棴httpdns鏃ュ織锛岄伩鍏嶆棩蹇楅噺杩囧ぇ");
}, "骞跺彂鍚屾璇锋眰", v -> {
ThreadUtil.multiThreadTest(validHosts, 100, 20, 10 * 60 * 1000, false, requestIpType);
sendLog("同步api并发测试开始大约耗时10分钟请最后查看logcat日志确认结果建议关闭httpdns日志避免日志量过大");
sendLog("鍚屾api骞跺彂娴嬭瘯寮€濮嬶紝澶х害鑰楁椂10鍒嗛挓锛岃鏈€鍚庢煡鐪媗ogcat鏃ュ織锛岀璁ょ粨鏋滐紝寤鸿鍏抽棴httpdns鏃ュ織锛岄伩鍏嶆棩蹇楅噺杩囧ぇ");
});
}
}

View File

@@ -21,8 +21,8 @@ import java.util.Iterator;
import java.util.List;
/**
* 保存Httpdns 及 相关配置,
* 方便修改
* 淇濆瓨Httpdns 鍙?鐩稿叧閰嶇疆锛?
* 鏂逛究淇敼
*/
public class HttpDnsHolder {
@@ -74,7 +74,7 @@ public class HttpDnsHolder {
}
/**
* 初始化httpdns的配置
* 鍒濆鍖杊ttpdns鐨勯厤缃?
*
* @param context
*/
@@ -94,7 +94,7 @@ public class HttpDnsHolder {
}
});
// 初始化httpdns 的配置此步骤需要在第一次获取HttpDnsService实例之前
// 鍒濆鍖杊ttpdns 鐨勯厤缃紝姝ゆ楠ら渶瑕佸湪绗竴娆¤幏鍙朒ttpDnsService瀹炰緥涔嬪墠
new InitConfig.Builder()
.setEnableExpiredIp(enableExpiredIp)
.setEnableCacheIp(enableCacheIp)
@@ -126,7 +126,7 @@ public class HttpDnsHolder {
public void setEnableExpiredIp(final boolean enableExpiredIp) {
this.enableExpiredIp = enableExpiredIp;
// 注意:此配置需要重启应用生效,因为现在通过InitConfig设置
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
@@ -137,7 +137,7 @@ public class HttpDnsHolder {
public void setEnableCacheIp(final boolean enableCacheIp) {
this.enableCacheIp = enableCacheIp;
// 注意:此配置需要重启应用生效,因为现在通过InitConfig设置
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
@@ -148,7 +148,7 @@ public class HttpDnsHolder {
public void setTimeout(final int timeout) {
this.timeout = timeout;
// 注意:此配置需要重启应用生效,因为现在通过InitConfig设置
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
@@ -159,7 +159,7 @@ public class HttpDnsHolder {
public void setEnableHttps(final boolean enableHttps) {
this.enableHttps = enableHttps;
// 注意:此配置需要重启应用生效,因为现在通过InitConfig设置
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
@@ -184,7 +184,7 @@ public class HttpDnsHolder {
this.hostListWithFixedIp = new ArrayList<>();
}
this.hostListWithFixedIp.add(host);
// 重启生效
// 閲嶅惎鐢熸晥
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
@@ -198,7 +198,7 @@ public class HttpDnsHolder {
this.ipRankingList = new ArrayList<>();
}
this.ipRankingList.add(ipProbeItem);
// 注意:此配置需要重启应用生效,因为现在通过InitConfig设置
// 娉ㄦ剰锛氭閰嶇疆闇€瑕侀噸鍚簲鐢ㄧ敓鏁堬紝鍥犱负鐜板湪閫氳繃InitConfig璁剧疆
SpUtil.writeSp(context, getSpName(accountId), new SpUtil.OnGetSpEditor() {
@Override
public void onGetSpEditor(SharedPreferences.Editor editor) {
@@ -232,15 +232,15 @@ public class HttpDnsHolder {
public String getCurrentConfig() {
StringBuilder sb = new StringBuilder();
sb.append("当前配置 accountId : ").append(accountId).append("\n")
.append("是否允许过期IP : ").append(enableExpiredIp).append("\n")
.append("是否开启本地缓存 : ").append(enableCacheIp).append("\n")
.append("是否开启HTTPS : ").append(enableHttps).append("\n")
.append("当前region设置 : ").append(region).append("\n")
.append("当前超时设置 : ").append(timeout).append("\n")
.append("当前探测配置 : ").append(convertProbeList(ipRankingList)).append("\n")
.append("当前缓存配置 : ").append(convertTtlCache(ttlCache)).append("\n")
.append("当前主站域名配置 : ").append(convertHostList(hostListWithFixedIp)).append("\n")
sb.append("褰撳墠閰嶇疆 accountId : ").append(accountId).append("\n")
.append("鏄惁鍏佽杩囨湡IP : ").append(enableExpiredIp).append("\n")
.append("鏄惁寮€鍚湰鍦扮紦瀛?: ").append(enableCacheIp).append("\n")
.append("鏄惁寮€鍚疕TTPS : ").append(enableHttps).append("\n")
.append("褰撳墠region璁剧疆 : ").append(region).append("\n")
.append("褰撳墠瓒呮椂璁剧疆 : ").append(timeout).append("\n")
.append("褰撳墠鎺㈡祴閰嶇疆 : ").append(convertProbeList(ipRankingList)).append("\n")
.append("褰撳墠缂撳瓨閰嶇疆 : ").append(convertTtlCache(ttlCache)).append("\n")
.append("褰撳墠涓荤珯鍩熷悕閰嶇疆 : ").append(convertHostList(hostListWithFixedIp)).append("\n")
;
return sb.toString();
}
@@ -340,3 +340,4 @@ public class HttpDnsHolder {
return null;
}
}

View File

@@ -23,8 +23,8 @@ public class MyApp extends Application {
return instance;
}
private final HttpDnsHolder holderA = new HttpDnsHolder("请替换为测试用A实例的accountId", "请替换为测试用A实例的secret");
private final HttpDnsHolder holderB = new HttpDnsHolder("请替换为测试用B实例的accountId", null);
private final HttpDnsHolder holderA = new HttpDnsHolder("璇锋浛鎹负娴嬭瘯鐢ˋ瀹炰緥鐨刟ccountId", "璇锋浛鎹负娴嬭瘯鐢ˋ瀹炰緥鐨剆ecret");
private final HttpDnsHolder holderB = new HttpDnsHolder("璇锋浛鎹负娴嬭瘯鐢˙瀹炰緥鐨刟ccountId", null);
private HttpDnsHolder current = holderA;
@@ -33,9 +33,9 @@ public class MyApp extends Application {
super.onCreate();
instance = this;
// 开启logcat 日志 默认关闭, 开发测试过程中可以开启
// 寮€鍚痩ogcat 鏃ュ織 榛樿鍏抽棴, 寮€鍙戞祴璇曡繃绋嬩腑鍙互寮€鍚?
HttpDnsLog.enable(true);
// 注入日志接口接受httpdns的日志开发测试过程中可以开启, 基础日志需要先enable才生效一些错误日志不需要
// 娉ㄥ叆鏃ュ織鎺ュ彛锛屾帴鍙梙ttpdns鐨勬棩蹇楋紝寮€鍙戞祴璇曡繃绋嬩腑鍙互寮€鍚? 鍩虹鏃ュ織闇€瑕佸厛enable鎵嶇敓鏁堬紝涓€浜涢敊璇棩蹇椾笉闇€瑕?
HttpDnsLog.setLogger(new ILogger() {
@Override
public void log(String msg) {
@@ -43,7 +43,7 @@ public class MyApp extends Application {
}
});
// 初始化httpdns的配置
// 鍒濆鍖杊ttpdns鐨勯厤缃?
holderA.init(this);
holderB.init(this);
@@ -90,3 +90,4 @@ public class MyApp extends Application {
}
}

View File

@@ -5,12 +5,12 @@ import com.alibaba.sdk.android.httpdns.RequestIpType;
public interface NetworkRequest {
/**
* 设置httpdns的配置
* 璁剧疆httpdns鐨勯厤缃?
*/
void updateHttpDnsConfig(boolean async, RequestIpType requestIpType);
/**
* get请求
* get璇锋眰
*
* @param url
* @return
@@ -18,3 +18,4 @@ public interface NetworkRequest {
String httpGet(String url) throws Exception;
}

View File

@@ -11,17 +11,17 @@ import com.aliyun.ams.httpdns.demo.base.BaseActivity;
import java.util.HashMap;
import static com.aliyun.ams.httpdns.demo.HttpDnsActivity.ALIYUN_URL;
import static com.aliyun.ams.httpdns.demo.HttpDnsActivity.Aliyun_URL;
public class SDNSActivity extends BaseActivity {
private final HashMap<String, String> globalParams = new HashMap<>();
/**
* 要请求的域名
* 瑕佽姹傜殑鍩熷悕
*/
private String host = HttpDnsActivity.ALIYUN_URL;
private String host = HttpDnsActivity.Aliyun_URL;
/**
* 要解析的ip类型
* 瑕佽В鏋愮殑ip绫诲瀷
*/
private RequestIpType requestIpType = RequestIpType.v4;
@@ -29,7 +29,7 @@ public class SDNSActivity extends BaseActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addEditTextEditTextButton("key", "value", "添加全局配置", new OnButtonClickMoreView() {
addEditTextEditTextButton("key", "value", "娣诲姞鍏ㄥ眬閰嶇疆", new OnButtonClickMoreView() {
@Override
public void onBtnClick(View[] views) {
EditText etOne = (EditText) views[0];
@@ -40,56 +40,56 @@ public class SDNSActivity extends BaseActivity {
globalParams.put(key, value);
// 注意SDNS全局参数现在需要通过InitConfig设置重启应用生效
sendLog("添加全局参数 " + key + " : " + value);
// 娉ㄦ剰锛歋DNS鍏ㄥ眬鍙傛暟鐜板湪闇€瑕侀€氳繃InitConfig璁剧疆锛岄噸鍚簲鐢ㄧ敓鏁?
sendLog("娣诲姞鍏ㄥ眬鍙傛暟 " + key + " : " + value);
}
});
addOneButton("清除全局配置", new View.OnClickListener() {
addOneButton("娓呴櫎鍏ㄥ眬閰嶇疆", new View.OnClickListener() {
@Override
public void onClick(View v) {
globalParams.clear();
// 注意SDNS全局参数现在需要通过InitConfig设置重启应用生效
sendLog("清除全局参数");
// 娉ㄦ剰锛歋DNS鍏ㄥ眬鍙傛暟鐜板湪闇€瑕侀€氳繃InitConfig璁剧疆锛岄噸鍚簲鐢ㄧ敓鏁?
sendLog("娓呴櫎鍏ㄥ眬鍙傛暟");
}
});
addFourButton("指定v4", new View.OnClickListener() {
addFourButton("鎸囧畾v4", new View.OnClickListener() {
@Override
public void onClick(View v) {
requestIpType = RequestIpType.v4;
sendLog("要解析的IP类型指定为ipv4");
sendLog("瑕佽В鏋愮殑IP绫诲瀷鎸囧畾涓篿pv4");
}
}, "指定v6", new View.OnClickListener() {
}, "鎸囧畾v6", new View.OnClickListener() {
@Override
public void onClick(View v) {
requestIpType = RequestIpType.v6;
sendLog("要解析的IP类型指定为ipv6");
sendLog("瑕佽В鏋愮殑IP绫诲瀷鎸囧畾涓篿pv6");
}
}, "都解析", new View.OnClickListener() {
}, "閮借В鏋?, new View.OnClickListener() {
@Override
public void onClick(View v) {
requestIpType = RequestIpType.both;
sendLog("要解析的IP类型指定为ipv4和ipv6");
sendLog("瑕佽В鏋愮殑IP绫诲瀷鎸囧畾涓篿pv4鍜宨pv6");
}
}, "自动判断", new View.OnClickListener() {
}, "姩鍒ゆ柇", new View.OnClickListener() {
@Override
public void onClick(View v) {
requestIpType = RequestIpType.auto;
sendLog("要解析的IP类型根据网络情况自动判断");
sendLog("瑕佽В鏋愮殑IP绫诲瀷鏍规嵁缃戠粶鎯呭喌鑷姩鍒ゆ柇");
}
});
addAutoCompleteTextViewButton(HttpDnsActivity.hosts, "域名", "指定要解析的域名", new OnButtonClick() {
addAutoCompleteTextViewButton(HttpDnsActivity.hosts, "鍩熷悕", "鎸囧畾瑕佽В鏋愮殑鍩熷悕", new OnButtonClick() {
@Override
public void onBtnClick(View view) {
AutoCompleteTextView actvOne = (AutoCompleteTextView) view;
host = actvOne.getEditableText().toString();
sendLog("要解析的域名" + host);
sendLog("瑕佽В鏋愮殑鍩熷悕" + host);
}
});
addEditTextEditTextButton("key", "value", "发起请求", new OnButtonClickMoreView() {
addEditTextEditTextButton("key", "value", "鍙戣捣璇锋眰", new OnButtonClickMoreView() {
@Override
public void onBtnClick(View[] views) {
EditText etOne = (EditText) views[0];
@@ -102,31 +102,32 @@ public class SDNSActivity extends BaseActivity {
map.put(key, value);
sendLog("发起SDNS请求 requestIpType is " + requestIpType.name());
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(host, requestIpType, map, "测试SDNS");
sendLog("结果 " + result);
sendLog("鍙戣捣SDNS璇锋眰 requestIpType is " + requestIpType.name());
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(host, requestIpType, map, "娴嬭瘯SDNS");
sendLog("缁撴灉 " + result);
}
});
addTwoButton("scale1参数请求", new View.OnClickListener() {
addTwoButton("scale1鍙傛暟璇锋眰", new View.OnClickListener() {
@Override
public void onClick(View v) {
HashMap<String, String> map = new HashMap<>();
map.put("scale", "scale1");
sendLog("发起SDNS请求 requestIpType is " + requestIpType.name() + " scale : scale1");
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(host, requestIpType, map, "测试1");
sendLog("结果 " + result);
sendLog("鍙戣捣SDNS璇锋眰 requestIpType is " + requestIpType.name() + " scale : scale1");
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(host, requestIpType, map, "娴嬭瘯1");
sendLog("缁撴灉 " + result);
}
}, "scale2参数请求", new View.OnClickListener() {
}, "scale2鍙傛暟璇锋眰", new View.OnClickListener() {
@Override
public void onClick(View v) {
HashMap<String, String> map = new HashMap<>();
map.put("scale", "scale2");
sendLog("发起SDNS请求 requestIpType is " + requestIpType.name() + " scale : scale2");
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(host, requestIpType, map, "测试2");
sendLog("结果 " + result);
sendLog("鍙戣捣SDNS璇锋眰 requestIpType is " + requestIpType.name() + " scale : scale2");
HTTPDNSResult result = MyApp.getInstance().getService().getIpsByHostAsync(host, requestIpType, map, "娴嬭瘯2");
sendLog("缁撴灉 " + result);
}
});
}
}

View File

@@ -55,10 +55,10 @@ public class WebViewActivity extends Activity {
public boolean
onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
webView.goBack();//返回上个页面
webView.goBack();//杩斿洖涓婁釜椤甸潰
return true;
}
return super.onKeyDown(keyCode, event);//退出Activity
return super.onKeyDown(keyCode, event);//閫€鍑篈ctivity
}
private void initBar() {
@@ -86,7 +86,7 @@ public class WebViewActivity extends Activity {
Map<String, String> headerFields = request.getRequestHeaders();
String url = request.getUrl().toString();
Log.e(TAG, "url:" + url);
// 无法拦截body拦截方案只能正常处理不带body的请求
// 鏃犳硶鎷︽埅body锛屾嫤鎴柟妗堝彧鑳芥甯稿鐞嗕笉甯ody鐨勮姹傦紱
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))
&& method.equalsIgnoreCase("get")) {
try {
@@ -97,7 +97,7 @@ public class WebViewActivity extends Activity {
return super.shouldInterceptRequest(view, request);
}
// 注*对于POST请求的Body数据WebResourceRequest接口中并没有提供这里无法处理
// 娉?锛氬浜嶱OST璇锋眰鐨凚ody鏁版嵁锛學ebResourceRequest鎺ュ彛涓苟娌℃湁鎻愪緵锛岃繖閲屾棤娉曞鐞?
String contentType = connection.getContentType();
String mime = getMime(contentType);
String charset = getCharset(contentType);
@@ -110,18 +110,18 @@ public class WebViewActivity extends Activity {
Log.e(TAG, "mime:" + mime + "; charset:" + charset);
// 无mime类型的请求不拦截
// 鏃爉ime绫诲瀷鐨勮姹備笉鎷︽埅
if (TextUtils.isEmpty(mime)) {
Log.e(TAG, "no MIME");
return super.shouldInterceptRequest(view, request);
} else {
// 二进制资源无需编码信息
// 浜岃繘鍒惰祫婧愭棤闇€缂栫爜淇℃伅
if (!TextUtils.isEmpty(charset) || (isBinaryRes(mime))) {
WebResourceResponse resourceResponse = new WebResourceResponse(mime, charset, httpURLConnection.getInputStream());
resourceResponse.setStatusCodeAndReasonPhrase(statusCode, response);
Map<String, String> responseHeader = new HashMap<String, String>();
for (String key : headerKeySet) {
// HttpUrlConnection可能包含key为null的报头指向该http请求状态码
// HttpUrlConnection鍙兘鍖呭惈key涓簄ull鐨勬姤澶达紝鎸囧悜璇ttp璇锋眰鐘舵€佺爜
responseHeader.put(key, httpURLConnection.getHeaderField(key));
}
resourceResponse.setResponseHeaders(responseHeader);
@@ -142,7 +142,7 @@ public class WebViewActivity extends Activity {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// API < 21 只能拦截URL参数
// API < 21 鍙兘鎷︽埅URL鍙傛暟
return super.shouldInterceptRequest(view, url);
}
});
@@ -153,7 +153,7 @@ public class WebViewActivity extends Activity {
/**
* 从contentType中获取MIME类型
* 浠巆ontentType涓幏鍙朚IME绫诲瀷
*
* @param contentType
* @return
@@ -166,7 +166,7 @@ public class WebViewActivity extends Activity {
}
/**
* 从contentType中获取编码信息
* 浠巆ontentType涓幏鍙栫紪鐮佷俊鎭?
*
* @param contentType
* @return
@@ -191,7 +191,7 @@ public class WebViewActivity extends Activity {
/**
* 是否是二进制资源,二进制资源可以不需要编码信息
* 鏄惁鏄簩杩涘埗璧勬簮锛屼簩杩涘埗璧勬簮鍙互涓嶉渶瑕佺紪鐮佷俊鎭?
*/
private boolean isBinaryRes(String mime) {
if (mime.startsWith("image")
@@ -209,10 +209,10 @@ public class WebViewActivity extends Activity {
URL url = null;
try {
url = new URL(path);
// 异步接口获取IP
// 寮傛鎺ュ彛鑾峰彇IP
String ip = MyApp.getInstance().getService().getIpByHostAsync(url.getHost());
if (ip != null) {
// 通过HTTPDNS获取IP成功进行URL替换和HOST头设置
// 閫氳繃HTTPDNS鑾峰彇IP鎴愬姛锛岃繘琛孶RL鏇挎崲鍜孒OST澶磋缃?
Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
String newUrl = path.replaceFirst(url.getHost(), ip);
conn = (HttpURLConnection) new URL(newUrl).openConnection();
@@ -222,7 +222,7 @@ public class WebViewActivity extends Activity {
conn.setRequestProperty(field.getKey(), field.getValue());
}
}
// 设置HTTP请求头Host
// 璁剧疆HTTP璇锋眰澶碒ost鍩?
conn.setRequestProperty("Host", url.getHost());
} else {
return null;
@@ -235,9 +235,9 @@ public class WebViewActivity extends Activity {
WebviewTlsSniSocketFactory sslSocketFactory = new WebviewTlsSniSocketFactory(
(HttpsURLConnection)conn);
// sni场景创建SSLScocket
// sni鍦烘櫙锛屽垱寤篠SLScocket
httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
// https场景,证书校验
// https鍦烘櫙锛岃瘉涔︽牎楠?
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
@@ -251,7 +251,7 @@ public class WebViewActivity extends Activity {
}
int code = conn.getResponseCode();// Network block
if (needRedirect(code)) {
// 原有报头中含有cookie放弃拦截
// 鍘熸湁鎶ュご涓惈鏈塩ookie锛屾斁寮冩嫤鎴?
if (containCookie(headers)) {
return null;
}
@@ -264,7 +264,7 @@ public class WebViewActivity extends Activity {
if (location != null) {
if (!(location.startsWith("http://") || location
.startsWith("https://"))) {
//某些时候会省略host只返回后面的path所以需要补全url
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏rl
URL originalUrl = new URL(path);
location = originalUrl.getProtocol() + "://"
+ originalUrl.getHost() + location;
@@ -272,7 +272,7 @@ public class WebViewActivity extends Activity {
Log.e(TAG, "code: " + code + "; location: " + location + "; path " + path);
return recursiveRequest(location, headers, path);
} else {
// 无法获取location信息,让浏览器获取
// 鏃犳硶鑾峰彇location淇℃伅锛岃娴忚鍣ㄨ幏鍙?
return null;
}
} else {
@@ -297,7 +297,7 @@ public class WebViewActivity extends Activity {
/**
* header中是否含有cookie
* header涓槸鍚﹀惈鏈塩ookie
*/
private boolean containCookie(Map<String, String> headers) {
for (Map.Entry<String, String> headerField : headers.entrySet()) {
@@ -400,3 +400,4 @@ public class WebViewActivity extends Activity {
}
}
}

View File

@@ -66,7 +66,7 @@ public class BaseActivity extends Activity {
}
/**
* 发送日志到界面
* 鍙戦€佹棩蹇楀埌鐣岄潰
*
* @param log
*/
@@ -261,3 +261,4 @@ public class BaseActivity extends Activity {
void onBtnClick(View[] views);
}
}

View File

@@ -33,7 +33,7 @@ import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
/**
* 使用HttpUrlConnection 实现请求
* 浣跨敤HttpUrlConnection 瀹炵幇璇锋眰
*/
public class HttpUrlConnectionRequest implements NetworkRequest {
@@ -55,7 +55,7 @@ public class HttpUrlConnectionRequest implements NetworkRequest {
@Override
public String httpGet(String url) throws Exception {
Log.d(TAG, "使用httpUrlConnection 请求" + url + " 异步接口 " + async + " ip类型 " + type.name());
Log.d(TAG, "浣跨敤httpUrlConnection 璇锋眰" + url + " 寮傛鎺ュ彛 " + async + " ip绫诲瀷 " + type.name());
HttpURLConnection conn = getConnection(url);
InputStream in = null;
@@ -67,13 +67,13 @@ public class HttpUrlConnectionRequest implements NetworkRequest {
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
errStr = readStringFrom(streamReader).toString();
}
Log.d(TAG, "请求失败 " + conn.getResponseCode() + " err " + errStr);
Log.d(TAG, "璇锋眰澶辫触 " + conn.getResponseCode() + " err " + errStr);
throw new Exception("Status Code : " + conn.getResponseCode() + " Msg : " + errStr);
} else {
in = conn.getInputStream();
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String responseStr = readStringFrom(streamReader).toString();
Log.d(TAG, "请求成功 " + responseStr);
Log.d(TAG, "璇锋眰鎴愬姛 " + responseStr);
return responseStr;
}
}
@@ -82,29 +82,29 @@ public class HttpUrlConnectionRequest implements NetworkRequest {
final String host = new URL(url).getHost();
HttpURLConnection conn = null;
HTTPDNSResult result;
/* 切换为新版标准api */
/* 鍒囨崲涓烘柊鐗堟爣鍑哸pi */
if (async) {
result = MyApp.getInstance().getService().getHttpDnsResultForHostAsync(host, type);
} else {
result = MyApp.getInstance().getService().getHttpDnsResultForHostSync(host, type);
}
Log.d(TAG, "httpdns 解析 " + host + " 结果为 " + result + " ttl is " + Util.getTtl(result));
Log.d(TAG, "httpdns 瑙f瀽 " + host + " 缁撴灉涓?" + result + " ttl is " + Util.getTtl(result));
// 这里需要根据实际情况选择使用ipv6地址 还是 ipv4地址 下面示例的代码优先使用了ipv6地址
// 杩欓噷闇€瑕佹牴鎹疄闄呮儏鍐甸€夋嫨浣跨敤ipv6鍦板潃 杩樻槸 ipv4鍦板潃锛?涓嬮潰绀轰緥鐨勪唬鐮佷紭鍏堜娇鐢ㄤ簡ipv6鍦板潃
if (result.getIpv6s() != null && result.getIpv6s().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v4) {
String newUrl = url.replace(host, "[" + result.getIpv6s()[0] + "]");
conn = (HttpURLConnection) new URL(newUrl).openConnection();
conn.setRequestProperty("Host", host);
Log.d(TAG, "使用ipv6地址 " + newUrl);
Log.d(TAG, "浣跨敤ipv6鍦板潃 " + newUrl);
} else if (result.getIps() != null && result.getIps().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v6) {
String newUrl = url.replace(host, result.getIps()[0]);
conn = (HttpURLConnection) new URL(newUrl).openConnection();
conn.setRequestProperty("Host", host);
Log.d(TAG, "使用ipv4地址 " + newUrl);
Log.d(TAG, "浣跨敤ipv4鍦板潃 " + newUrl);
}
if (conn == null) {
Log.d(TAG, "httpdns 未返回解析结果走localdns");
Log.d(TAG, "httpdns 鏈繑鍥炶В鏋愮粨鏋滐紝璧發ocaldns");
conn = (HttpURLConnection) new URL(url).openConnection();
}
conn.setConnectTimeout(30000);
@@ -115,9 +115,9 @@ public class HttpUrlConnectionRequest implements NetworkRequest {
WebviewTlsSniSocketFactory sslSocketFactory = new WebviewTlsSniSocketFactory(
(HttpsURLConnection)conn);
// sni场景创建SSLSocket
// sni鍦烘櫙锛屽垱寤篠SLSocket
httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
// https场景,证书校验
// https鍦烘櫙锛岃瘉涔︽牎楠?
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
@@ -131,14 +131,14 @@ public class HttpUrlConnectionRequest implements NetworkRequest {
}
int code = conn.getResponseCode();// Network block
if (needRedirect(code)) {
//临时重定向和永久重定向location的大小写有区分
//涓存椂閲嶅畾鍚戝拰姘镐箙閲嶅畾鍚憀ocation鐨勫ぇ灏忓啓鏈夊尯鍒?
String location = conn.getHeaderField("Location");
if (location == null) {
location = conn.getHeaderField("location");
}
if (!(location.startsWith("http://") || location
.startsWith("https://"))) {
//某些时候会省略host只返回后面的path所以需要补全url
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏rl
URL originalUrl = new URL(url);
location = originalUrl.getProtocol() + "://"
+ originalUrl.getHost() + location;
@@ -255,3 +255,4 @@ public class HttpUrlConnectionRequest implements NetworkRequest {
return sb;
}
}

View File

@@ -27,7 +27,7 @@ import okhttp3.Request;
import okhttp3.Response;
/**
* okhttp实现网络请求
* okhttp瀹炵幇缃戠粶璇锋眰
*/
public class OkHttpRequest implements NetworkRequest {
@@ -39,35 +39,35 @@ public class OkHttpRequest implements NetworkRequest {
public OkHttpRequest(final Context context) {
client = new OkHttpClient.Builder()
// 这里配置连接池是为了方便测试httpdns能力正式代码请不要配置
// 杩欓噷閰嶇疆杩炴帴姹狅紝鏄负浜嗘柟渚挎祴璇昲ttpdns鑳藉姏锛屾寮忎唬鐮佽涓嶈閰嶇疆
.connectionPool(new ConnectionPool(0, 10 * 1000, TimeUnit.MICROSECONDS))
.dns(new Dns() {
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
HTTPDNSResult result;
/* 切换为新版标准api */
/* 鍒囨崲涓烘柊鐗堟爣鍑哸pi */
if (async) {
result = MyApp.getInstance().getService().getHttpDnsResultForHostAsync(hostname, type);
} else {
result = MyApp.getInstance().getService().getHttpDnsResultForHostSync(hostname, type);
}
Log.d(TAG, "httpdns 解析 " + hostname + " 结果为 " + result + " ttl is " + Util.getTtl(result));
Log.d(TAG, "httpdns 瑙f瀽 " + hostname + " 缁撴灉涓?" + result + " ttl is " + Util.getTtl(result));
List<InetAddress> inetAddresses = new ArrayList<>();
// 这里需要根据实际情况选择使用ipv6地址 还是 ipv4地址 下面示例的代码优先使用了ipv6地址
// 杩欓噷闇€瑕佹牴鎹疄闄呮儏鍐甸€夋嫨浣跨敤ipv6鍦板潃 杩樻槸 ipv4鍦板潃锛?涓嬮潰绀轰緥鐨勪唬鐮佷紭鍏堜娇鐢ㄤ簡ipv6鍦板潃
Log.d(TAG, "netType is: " + HttpDnsNetworkDetector.getInstance().getNetType(context));
if (result.getIpv6s() != null && result.getIpv6s().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v4) {
for (int i = 0; i < result.getIpv6s().length; i++) {
inetAddresses.addAll(Arrays.asList(InetAddress.getAllByName(result.getIpv6s()[i])));
}
Log.d(TAG, "使用ipv6地址" + inetAddresses);
Log.d(TAG, "浣跨敤ipv6鍦板潃" + inetAddresses);
} else if (result.getIps() != null && result.getIps().length > 0 && HttpDnsNetworkDetector.getInstance().getNetType(context) != NetType.v6) {
for (int i = 0; i < result.getIps().length; i++) {
inetAddresses.addAll(Arrays.asList(InetAddress.getAllByName(result.getIps()[i])));
}
Log.d(TAG, "使用ipv4地址" + inetAddresses);
Log.d(TAG, "浣跨敤ipv4鍦板潃" + inetAddresses);
}
if (inetAddresses.size() == 0) {
Log.d(TAG, "httpdns 未返回IPlocaldns");
Log.d(TAG, "httpdns 鏈繑鍥濱P锛岃蛋localdns");
return Dns.SYSTEM.lookup(hostname);
}
return inetAddresses;
@@ -84,14 +84,15 @@ public class OkHttpRequest implements NetworkRequest {
@Override
public String httpGet(String url) throws Exception {
Log.d(TAG, "使用okhttp 请求" + url + " 异步接口 " + async + " ip类型 " + type.name());
Log.d(TAG, "浣跨敤okhttp 璇锋眰" + url + " 寮傛鎺ュ彛 " + async + " ip绫诲瀷 " + type.name());
Response response = client.newCall(new Request.Builder().url(url).build()).execute();
int code = response.code();
String body = response.body().string();
Log.d(TAG, "使用okhttp 请求结果 code " + code + " body " + body);
Log.d(TAG, "浣跨敤okhttp 璇锋眰缁撴灉 code " + code + " body " + body);
if (code != HttpURLConnection.HTTP_OK) {
throw new Exception("请求失败 code " + code + " body " + body);
throw new Exception("璇锋眰澶辫触 code " + code + " body " + body);
}
return body;
}
}

View File

@@ -25,3 +25,4 @@ public class SpUtil {
void onGetSpEditor(SharedPreferences.Editor editor);
}
}

View File

@@ -26,14 +26,14 @@ public class ThreadUtil {
}
final int tmpValidCount = validCount;
Log.d(MyApp.TAG,
threadCount + "线程并发,执行" + executeTime + ", 总域名" + hostCount + "个,有效"
+ validCount + "");
threadCount + "绾跨▼骞跺彂锛屾墽琛? + executeTime + ", 鎬诲煙鍚? + hostCount + "涓紝鏈夋晥"
+ validCount + "涓?);
new Thread(new Runnable() {
@Override
public void run() {
final ArrayList<String> hosts = new ArrayList<>(hostCount);
for (int i = 0; i < hostCount - tmpValidCount; i++) {
hosts.add("test" + i + ".aliyun.com");
hosts.add("test" + i + ".Aliyun.com");
}
hosts.addAll(Arrays.asList(validHosts).subList(0, tmpValidCount));
@@ -125,3 +125,4 @@ public class ThreadUtil {
}).start();
}
}

View File

@@ -6,8 +6,8 @@ import java.lang.reflect.Field;
public class Util {
/**
* 获取ttl
* 此方法是用于测试自定义ttl是否生效
* 鑾峰彇ttl锛?
* 姝ゆ柟娉曟槸鐢ㄤ簬娴嬭瘯鑷畾涔塼tl鏄惁鐢熸晥
*/
public static int getTtl(HTTPDNSResult result) {
try {
@@ -22,3 +22,4 @@ public class Util {
return -1;
}
}

View File

@@ -31,7 +31,7 @@
android:id="@+id/bar_more"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="更多"
android:text="鏇村"
android:textSize="16sp"
android:gravity="center"
android:paddingLeft="10dp"

View File

@@ -1,31 +1,31 @@
<resources>
<string name="app_name">【阿里云HttpDns】Demo程序</string>
<string name="app_name">銆愰樋閲屼簯HttpDns銆慏emo绋嬪簭</string>
<string name="action_settings">Settings</string>
<string name="normal_parse">普通解析</string>
<string name="request_taobao">解析淘宝域名</string>
<string name="request_apple">解析apple域名</string>
<string name="request_douban">解析豆瓣域名</string>
<string name="https_parse">HTTPS开关</string>
<string name="timeout">设置超时</string>
<string name="set_expired">允许过期域名</string>
<string name="set_cache">开启持久化缓存</string>
<string name="set_degration_filter">降级策略</string>
<string name="set_pre_resolve">预解析</string>
<string name="normal_parse">鏅€氳В鏋?/string>
<string name="request_taobao">瑙f瀽娣樺疂鍩熷悕</string>
<string name="request_apple">瑙f瀽apple鍩熷悕</string>
<string name="request_douban">瑙f瀽璞嗙摚鍩熷悕</string>
<string name="https_parse">HTTPS寮€鍏?/string>
<string name="timeout">璁剧疆瓒呮椂</string>
<string name="set_expired">鍏佽杩囨湡鍩熷悕</string>
<string name="set_cache">寮€鍚寔涔呭寲缂撳瓨</string>
<string name="set_degration_filter">闄嶇骇绛栫暐</string>
<string name="set_pre_resolve">棰勮В鏋?/string>
<string name="set_region">region</string>
<string name="sync_request">同步解析</string>
<string name="multi_sync_request">同步解析并发</string>
<string name="multi_request">并发解析</string>
<string name="sync_request">鍚屾瑙f瀽</string>
<string name="multi_sync_request">鍚屾瑙f瀽骞跺彂</string>
<string name="multi_request">骞跺彂瑙f瀽</string>
<string name="main_about_us">关于我们</string>
<string name="main_helper">帮助中心</string>
<string name="main_clear_text">清除当前消息</string>
<string name="main_about_us">鍏充簬鎴戜滑</string>
<string name="main_helper">甯姪涓績</string>
<string name="main_clear_text">娓呴櫎褰撳墠娑堟伅</string>
<!--关于我们页面-->
<!--鍏充簬鎴戜滑椤甸潰-->
<string name="layout_aboutus_arr">All Rights Reserved.</string>
<string name="layout_aboutus_company">阿里云(软件)有限公司版权所有</string>
<string name="layout_aboutus_copyright">Copyright © 2009 - 2016 Aliyun.com</string>
<string name="layout_aboutus_company">闃块噷浜?杞欢)鏈夐檺鍏徃鐗堟潈鎵€鏈?/string>
<string name="layout_aboutus_copyright">Copyright 2009 - 2016 Aliyun.com</string>
<string name="layout_aboutus_app_version">1.1.3</string>
<!--帮助中心页面-->
<string name="layout_helpus_content">Q : 什么是用户体验Demo\nA : 用户体验Demo就是阿里云平台为您自动创建的、用来体验HttpDns服务和反馈建议用的一个小Demo让您体验便捷、高效的HttpDns服务。\n\nQ : 如何联系我们?\nA : App Demo相关问题请搜索钉钉群号11777313</string>
<!--甯姪涓績椤甸潰-->
<string name="layout_helpus_content">Q : 浠€涔堟槸鐢ㄦ埛浣撻獙Demo锛焅nA : 鐢ㄦ埛浣撻獙Demo灏辨槸闃块噷浜戝钩鍙颁负鎮ㄨ嚜鍔ㄥ垱寤虹殑銆佺敤鏉ヤ綋楠孒ttpDns鏈嶅姟鍜屽弽棣堝缓璁敤鐨勪竴涓皬Demo锛岃鎮ㄤ綋楠屼究鎹枫€侀珮鏁堢殑HttpDns鏈嶅姟銆俓n\nQ : 濡備綍鑱旂郴鎴戜滑锛焅nA : App Demo鐩稿叧闂锛岃鎼滅储閽夐拤缇ゅ彿锛?1777313</string>
</resources>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<Alibaba-anchors>
<certificates src="system" />
</trust-anchors>
</Alibaba-anchors>
</base-config>
</network-security-config>

View File

@@ -22,9 +22,9 @@ android {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
buildConfigField "String", "HTTPDNS_VERSION", "\"${gradle.httpVersion}\""
buildConfigField "String", "ACCOUNT_ID", "\"请替换为测试用实例的accountId\""
buildConfigField "String", "SECRET_KEY", "\"请替换为测试用实例的secret\""
buildConfigField "String", "AES_SECRET_KEY", "\"请替换为测试用实例的aes\""
buildConfigField "String", "ACCOUNT_ID", "\"璇锋浛鎹负娴嬭瘯鐢ㄥ疄渚嬬殑accountId\""
buildConfigField "String", "SECRET_KEY", "\"璇锋浛鎹负娴嬭瘯鐢ㄥ疄渚嬬殑secret\""
buildConfigField "String", "AES_SECRET_KEY", "\"璇锋浛鎹负娴嬭瘯鐢ㄥ疄渚嬬殑aes\""
}
buildTypes {
@@ -40,7 +40,7 @@ android {
}
forTest {
// 注意这里的配置并不是需要编译forTest的app而是避免httpdns-sdk在AndroidStudio改为end2end运行测试时 BuildVariants报错
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慺orTest鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
initWith release
debuggable true
}
@@ -92,7 +92,7 @@ android {
}
end2end {
// 注意这里的配置并不是需要编译end2end的app而是避免httpdns-sdk在AndroidStudio改为end2end运行测试时 BuildVariants报错
// 娉ㄦ剰杩欓噷鐨勯厤缃紝骞朵笉鏄渶瑕佺紪璇慹nd2end鐨刟pp锛岃€屾槸閬垮厤httpdns-sdk鍦ˋndroidStudio鏀逛负end2end杩愯娴嬭瘯鏃?BuildVariants鎶ラ敊
}
}

View File

@@ -22,3 +22,4 @@ class ExampleInstrumentedTest {
assertEquals("com.alibaba.ams.emas.demo", appContext.packageName)
}
}

View File

@@ -11,7 +11,7 @@ object BatchResolveCacheHolder {
if (cacheData == null) {
batchResolveBothList.add("www.baidu.com")
batchResolveBothList.add("m.baidu.com")
batchResolveBothList.add("www.aliyun.com")
batchResolveBothList.add("www.Aliyun.com")
batchResolveBothList.add("www.taobao.com")
batchResolveBothList.add("www.163.com")
batchResolveBothList.add("www.sohu.com")
@@ -89,3 +89,4 @@ object BatchResolveCacheHolder {
return jsonObject.toString()
}
}

View File

@@ -14,3 +14,4 @@ class HttpDnsApplication : Application() {
}

View File

@@ -35,3 +35,4 @@ object HttpDnsServiceHolder {
}
}

View File

@@ -49,3 +49,4 @@ class MainActivity : AppCompatActivity() {
}
}
}

View File

@@ -90,3 +90,4 @@ object PreResolveCacheHolder {
return jsonObject.toString()
}
}

View File

@@ -45,3 +45,4 @@ class SingleLiveData<T> : MutableLiveData<T>() {
value = null
}
}

View File

@@ -48,3 +48,4 @@ object TtlCacheHolder {
}
}

View File

@@ -113,7 +113,7 @@ fun String?.toBlackList(): MutableList<String>? {
fun getAccountPreference(context: Context): SharedPreferences {
return context.getSharedPreferences(
"aliyun_httpdns_${BuildConfig.ACCOUNT_ID}",
"Aliyun_httpdns_${BuildConfig.ACCOUNT_ID}",
Context.MODE_PRIVATE
)
}
@@ -138,3 +138,4 @@ fun readStringFrom(streamReader: BufferedReader): StringBuilder {
}
return sb
}

View File

@@ -52,3 +52,4 @@ const val KEY_PRE_RESOLVE_HOST_LIST = "pre_resolve_host_list"
const val KEY_SDNS_GLOBAL_PARAMS = "sdns_global_params"
const val KEY_BATCH_RESOLVE_HOST_LIST = "batch_resolve_host_list"

View File

@@ -52,7 +52,7 @@ class HttpURLConnectionRequest(private val context: Context, private val request
var ipURL: String? = null
dnsService?.let {
//替换为最新的api
//鏇挎崲涓烘渶鏂扮殑api
Log.d("HttpURLConnection", "start lookup $host via $resolveMethod")
var httpDnsResult = HTTPDNSResult("", null, null, null, false, false)
if (resolveMethod == "getHttpDnsResultForHostSync(String host, RequestIpType type)") {
@@ -83,7 +83,7 @@ class HttpURLConnectionRequest(private val context: Context, private val request
}
}
Log.d("HttpURLConnection", "httpdns $host 解析结果 $httpDnsResult")
Log.d("HttpURLConnection", "httpdns $host 瑙f瀽缁撴灉 $httpDnsResult")
val ipStackType = HttpDnsNetworkDetector.getInstance().getNetType(context)
val isV6 = ipStackType == NetType.v6 || ipStackType == NetType.both
val isV4 = ipStackType == NetType.v4 || ipStackType == NetType.both
@@ -104,9 +104,9 @@ class HttpURLConnectionRequest(private val context: Context, private val request
if (conn is HttpsURLConnection) {
val sslSocketFactory = TLSSNISocketFactory(conn)
// SNI场景创建SSLSocket
// SNI鍦烘櫙锛屽垱寤篠SLSocket
conn.sslSocketFactory = sslSocketFactory
// https场景,证书校验
// https鍦烘櫙锛岃瘉涔︽牎楠?
conn.hostnameVerifier = HostnameVerifier { _, session ->
val requestHost = conn.getRequestProperty("Host") ?:conn.getURL().host
HttpsURLConnection.getDefaultHostnameVerifier().verify(requestHost, session)
@@ -115,13 +115,13 @@ class HttpURLConnectionRequest(private val context: Context, private val request
val responseCode = conn.responseCode
if (needRedirect(responseCode)) {
//临时重定向和永久重定向location的大小写有区分
//涓存椂閲嶅畾鍚戝拰姘镐箙閲嶅畾鍚憀ocation鐨勫ぇ灏忓啓鏈夊尯鍒?
var location = conn.getHeaderField("Location")
if (location == null) {
location = conn.getHeaderField("location")
}
if (!(location!!.startsWith("http://") || location.startsWith("https://"))) {
//某些时候会省略host只返回后面的path所以需要补全url
//鏌愪簺鏃跺€欎細鐪佺暐host锛屽彧杩斿洖鍚庨潰鐨刾ath锛屾墍浠ラ渶瑕佽ˉ鍏rl
val originalUrl = URL(url)
location = (originalUrl.protocol + "://"
+ originalUrl.host + location)
@@ -136,3 +136,4 @@ class HttpURLConnectionRequest(private val context: Context, private val request
}
}

View File

@@ -9,3 +9,4 @@ import com.alibaba.ams.emas.demo.ui.resolve.Response
interface IRequest {
fun get(url: String): Response
}

View File

@@ -74,7 +74,7 @@ import java.util.concurrent.TimeUnit
override fun lookup(hostname: String): List<InetAddress> {
Log.d(tag, "start lookup $hostname via $mResolveMethod")
val dnsService = HttpDnsServiceHolder.getHttpDnsService(mContext.get()!!)
//修改为最新的通俗易懂的api
//淇敼涓烘渶鏂扮殑閫氫織鏄撴噦鐨刟pi
var httpDnsResult: HTTPDNSResult? = null
val inetAddresses = mutableListOf<InetAddress>()
if (mResolveMethod == "getHttpDnsResultForHostSync(String host, RequestIpType type)") {
@@ -113,11 +113,11 @@ import java.util.concurrent.TimeUnit
}
}
Log.d(tag, "httpdns $hostname 解析结果 $httpDnsResult")
Log.d(tag, "httpdns $hostname 瑙f瀽缁撴灉 $httpDnsResult")
httpDnsResult?.let { processDnsResult(it, inetAddresses) }
if (inetAddresses.isEmpty()) {
Log.d(tag, "httpdns 未返回IPlocal dns")
Log.d(tag, "httpdns 鏈繑鍥濱P锛岃蛋local dns")
return Dns.SYSTEM.lookup(hostname)
}
return inetAddresses
@@ -147,3 +147,4 @@ import java.util.concurrent.TimeUnit
}
}
}

View File

@@ -8,3 +8,4 @@ class OkHttpLog: HttpLoggingInterceptor.Logger {
Log.d("Okhttp", message)
}
}

View File

@@ -24,3 +24,4 @@ class OkHttpRequest constructor(
}
}

View File

@@ -81,3 +81,4 @@ class TLSSNISocketFactory(connection: HttpsURLConnection): SSLSocketFactory() {
return arrayOfNulls(0)
}
}

View File

@@ -176,3 +176,4 @@ class BasicSettingFragment : Fragment(), IBasicShowDialog {
})
}
}

View File

@@ -37,54 +37,54 @@ class BasicSettingViewModel(application: Application) : AndroidViewModel(applica
var secretKeySetByConfig = true
/**
* 是否开启鉴权模式
* 鏄惁寮€鍚壌鏉冩ā寮?
*/
var enableAuthMode = true
/**
* 是否开启加密模式
* 鏄惁寮€鍚姞瀵嗘ā寮?
*/
var enableEncryptMode = true
/**
* 是否允许过期IP
* 鏄惁鍏佽杩囨湡IP
*/
var enableExpiredIP = false
/**
* 是否开启本地缓存
* 鏄惁寮€鍚湰鍦扮紦瀛?
*/
var enableCacheIP = false
/**
* 是否允许HTTPS
* 鏄惁鍏佽HTTPS
*/
var enableHttps = false
/**
* 是否开启降级
* 鏄惁寮€鍚檷绾?
*/
var enableDegrade = false
/**
* 是否允许网络切换自动刷新
* 鏄惁鍏佽缃戠粶鍒囨崲鑷姩鍒锋柊
*/
var enableAutoRefresh = false
/**
* 是否允许打印日志
* 鏄惁鍏佽鎵撳嵃鏃ュ織
*/
var enableLog = false
/**
* 当前Region
* 褰撳墠Region
*/
var currentRegion = SingleLiveData<String>().apply {
value = ""
}
/**
* 当前超时
* 褰撳墠瓒呮椂
*/
var currentTimeout = SingleLiveData<String>().apply {
value = "2000ms"
@@ -188,7 +188,7 @@ class BasicSettingViewModel(application: Application) : AndroidViewModel(applica
}
fun setRegion() {
//弹窗选择region
//寮圭獥閫夋嫨region
showDialog?.showSelectRegionDialog()
}
@@ -253,21 +253,21 @@ class BasicSettingViewModel(application: Application) : AndroidViewModel(applica
val timeout = preferences.getInt(KEY_TIMEOUT, 2000)
val region = preferences.getString(KEY_REGION, "cn")
val enableDegradationLocalDns = preferences.getBoolean(KEY_ENABLE_DEGRADE, false);
//自定义ttl
//鑷畾涔塼tl
val ttlCacheStr = preferences.getString(KEY_TTL_CHANGER, null)
TtlCacheHolder.convertTtlCacheData(ttlCacheStr)
//IP探测
//IP鎺㈡祴
val ipRankingItemJson = preferences.getString(KEY_IP_RANKING_ITEMS, null)
//主站域名
//涓荤珯鍩熷悕
val hostListWithFixedIpJson =
preferences.getString(KEY_HOST_WITH_FIXED_IP, null)
val tagsJson = preferences.getString(KEY_TAGS, null)
//预解析
//棰勮В鏋?
val preResolveHostList = preferences.getString(KEY_PRE_RESOLVE_HOST_LIST, null)
preResolveHostList?.let { Log.d("httpdns:HttpDnsApplication", "pre resolve list: $it") }
PreResolveCacheHolder.convertPreResolveCacheData(preResolveHostList)
//批量解析
//鎵归噺瑙f瀽
val batchResolveHostList = preferences.getString(KEY_BATCH_RESOLVE_HOST_LIST, null)
BatchResolveCacheHolder.convertBatchResolveCacheData(batchResolveHostList)
@@ -359,3 +359,4 @@ class BasicSettingViewModel(application: Application) : AndroidViewModel(applica
return getApplication<HttpDnsApplication>().getString(resId, *args)
}
}

Some files were not shown because too many files have changed in this diff Show More