@@ -56,6 +56,7 @@ require ( | |||||
github.com/gomodule/redigo v2.0.0+incompatible | github.com/gomodule/redigo v2.0.0+incompatible | ||||
github.com/google/go-github/v24 v24.0.1 | github.com/google/go-github/v24 v24.0.1 | ||||
github.com/gorilla/context v1.1.1 | github.com/gorilla/context v1.1.1 | ||||
github.com/gorilla/websocket v1.4.0 | |||||
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect | github.com/hashicorp/go-retryablehttp v0.6.6 // indirect | ||||
github.com/huandu/xstrings v1.3.0 | github.com/huandu/xstrings v1.3.0 | ||||
github.com/issue9/assert v1.3.2 // indirect | github.com/issue9/assert v1.3.2 // indirect | ||||
@@ -394,6 +394,7 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ | |||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= | github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= | ||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= | github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= | ||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= | github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= | ||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= | |||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= | ||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= | ||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= | ||||
@@ -1118,10 +1118,7 @@ func UpdateJob(job *Cloudbrain) error { | |||||
} | } | ||||
func updateJob(e Engine, job *Cloudbrain) error { | func updateJob(e Engine, job *Cloudbrain) error { | ||||
var sess *xorm.Session | |||||
sess = e.Where("job_id = ?", job.JobID) | |||||
//_, err := sess.Cols("status", "container_id", "container_ip").Update(job) | |||||
_, err := sess.Update(job) | |||||
_, err := e.ID(job.ID).AllCols().Update(job) | |||||
return err | return err | ||||
} | } | ||||
@@ -1397,6 +1397,8 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats, | |||||
if opts.MilestoneID > 0 { | if opts.MilestoneID > 0 { | ||||
sess.And("issue.milestone_id = ?", opts.MilestoneID) | sess.And("issue.milestone_id = ?", opts.MilestoneID) | ||||
} else if opts.MilestoneID == -1 { //only search for issues do not have milestone | |||||
sess.And("issue.milestone_id = ?", 0) | |||||
} | } | ||||
if opts.AssigneeID > 0 { | if opts.AssigneeID > 0 { | ||||
@@ -353,7 +353,7 @@ func GetMilestonesByRepoID(repoID int64, state api.StateType, listOptions ListOp | |||||
} | } | ||||
miles := make([]*Milestone, 0, listOptions.PageSize) | miles := make([]*Milestone, 0, listOptions.PageSize) | ||||
return miles, sess.Asc("deadline_unix").Asc("id").Find(&miles) | |||||
return miles, sess.Desc("id").Find(&miles) | |||||
} | } | ||||
// GetMilestones returns a list of milestones of given repository and status. | // GetMilestones returns a list of milestones of given repository and status. | ||||
@@ -649,53 +649,41 @@ func (repo *Repository) GetAssignees() (_ []*User, err error) { | |||||
return repo.getAssignees(x) | return repo.getAssignees(x) | ||||
} | } | ||||
func (repo *Repository) getReviewersPrivate(e Engine, doerID, posterID int64) (users []*User, err error) { | |||||
users = make([]*User, 0, 20) | |||||
if err = e. | |||||
SQL("SELECT * FROM `user` WHERE id in (SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? "+ | |||||
" UNION SELECT owner_id FROM `repository` WHERE id = ?) AND id NOT IN ( ?, ?) ORDER BY name", | |||||
repo.ID, AccessModeWrite, repo.ID, | |||||
doerID, posterID). | |||||
Find(&users); err != nil { | |||||
return nil, err | |||||
} | |||||
return users, nil | |||||
} | |||||
func (repo *Repository) getReviewersPublic(e Engine, doerID, posterID int64) (_ []*User, err error) { | |||||
users := make([]*User, 0) | |||||
const SQLCmd = "SELECT * FROM `user` WHERE id IN ( " + | |||||
"SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? " + | |||||
" UNION" + | |||||
" SELECT owner_id FROM `repository` WHERE id = ?)" + | |||||
" AND id NOT IN ( ?, ?) ORDER BY name " | |||||
if err = e. | |||||
SQL(SQLCmd, | |||||
repo.ID, AccessModeWrite, repo.ID, doerID, posterID). | |||||
Find(&users); err != nil { | |||||
return nil, err | |||||
} | |||||
return users, nil | |||||
} | |||||
func (repo *Repository) getReviewers(e Engine, doerID, posterID int64) (users []*User, err error) { | func (repo *Repository) getReviewers(e Engine, doerID, posterID int64) (users []*User, err error) { | ||||
if err = repo.getOwner(e); err != nil { | if err = repo.getOwner(e); err != nil { | ||||
return nil, err | return nil, err | ||||
} | } | ||||
if repo.IsPrivate || | |||||
(repo.Owner.IsOrganization() && repo.Owner.Visibility == api.VisibleTypePrivate) { | |||||
users, err = repo.getReviewersPrivate(x, doerID, posterID) | |||||
if repo.Owner.IsOrganization() { | |||||
const SQLCmd = "SELECT * FROM `user` WHERE id IN (" + | |||||
"SELECT DISTINCT(t3.user_id) FROM ( " + | |||||
"SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ?" + | |||||
"UNION select t2.uid as user_id from team t1 left join team_user t2 on t1.id = t2.team_id where t1.org_id = ? and t1.authorize = 4) t3)" + | |||||
" AND id NOT IN ( ?, ?) ORDER BY name " | |||||
if err = e. | |||||
SQL(SQLCmd, | |||||
repo.ID, AccessModeWrite, repo.ID, doerID, posterID). | |||||
Find(&users); err != nil { | |||||
return nil, err | |||||
} | |||||
} else { | } else { | ||||
users, err = repo.getReviewersPublic(x, doerID, posterID) | |||||
const SQLCmd = "SELECT * FROM `user` WHERE id IN ( " + | |||||
"SELECT DISTINCT(t3.user_id) FROM ( " + | |||||
"SELECT user_id FROM `access` WHERE repo_id = ? AND mode >= ? " + | |||||
" UNION" + | |||||
" SELECT owner_id as user_id FROM `repository` WHERE id = ?) t3)" + | |||||
" AND id NOT IN ( ?, ?) ORDER BY name " | |||||
if err = e. | |||||
SQL(SQLCmd, | |||||
repo.ID, AccessModeWrite, repo.ID, doerID, posterID). | |||||
Find(&users); err != nil { | |||||
return nil, err | |||||
} | |||||
} | } | ||||
return | |||||
return users, nil | |||||
} | } | ||||
// GetReviewers get all users can be requested to review | // GetReviewers get all users can be requested to review | ||||
@@ -2482,6 +2470,12 @@ func GetBlockChainUnSuccessRepos() ([]*Repository, error) { | |||||
Find(&repos) | Find(&repos) | ||||
} | } | ||||
func (repo *Repository) UpdateBlockChain() error { | |||||
_, err := x.Exec("UPDATE `repository` SET block_chain_status = ?, contract_address=? WHERE id = ?", repo.BlockChainStatus, repo.ContractAddress, repo.ID) | |||||
return err | |||||
} | |||||
func (repo *Repository) IncreaseCloneCnt() { | func (repo *Repository) IncreaseCloneCnt() { | ||||
sess := x.NewSession() | sess := x.NewSession() | ||||
defer sess.Close() | defer sess.Close() | ||||
@@ -24,6 +24,8 @@ const ( | |||||
RepoWatchModeAuto // 3 | RepoWatchModeAuto // 3 | ||||
) | ) | ||||
var ActionChan = make(chan *Action, 200) | |||||
// Watch is connection request for receiving repository notification. | // Watch is connection request for receiving repository notification. | ||||
type Watch struct { | type Watch struct { | ||||
ID int64 `xorm:"pk autoincr"` | ID int64 `xorm:"pk autoincr"` | ||||
@@ -277,9 +279,17 @@ func notifyWatchers(e Engine, actions ...*Action) error { | |||||
// NotifyWatchers creates batch of actions for every watcher. | // NotifyWatchers creates batch of actions for every watcher. | ||||
func NotifyWatchers(actions ...*Action) error { | func NotifyWatchers(actions ...*Action) error { | ||||
producer(actions...) | |||||
return notifyWatchers(x, actions...) | return notifyWatchers(x, actions...) | ||||
} | } | ||||
func producer(actions ...*Action) { | |||||
for _, action := range actions { | |||||
ActionChan <- action | |||||
} | |||||
} | |||||
// NotifyWatchersActions creates batch of actions for every watcher. | // NotifyWatchersActions creates batch of actions for every watcher. | ||||
func NotifyWatchersActions(acts []*Action) error { | func NotifyWatchersActions(acts []*Action) error { | ||||
sess := x.NewSession() | sess := x.NewSession() | ||||
@@ -445,8 +445,12 @@ type Contributor struct { | |||||
Email string | Email string | ||||
} | } | ||||
func GetContributors(repoPath string) ([]Contributor, error){ | |||||
cmd := NewCommand("shortlog", "-sne", "--all") | |||||
func GetContributors(repoPath string, branchOrTag ...string) ([]Contributor, error) { | |||||
targetBranchOrTag := "HEAD" | |||||
if len(branchOrTag) > 0 && branchOrTag[0] != "" { | |||||
targetBranchOrTag = branchOrTag[0] | |||||
} | |||||
cmd := NewCommand("shortlog", "-sne", targetBranchOrTag) | |||||
stdout, err := cmd.RunInDir(repoPath) | stdout, err := cmd.RunInDir(repoPath) | ||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
@@ -462,9 +466,9 @@ func GetContributors(repoPath string) ([]Contributor, error){ | |||||
} | } | ||||
number := oneCount[0:strings.Index(oneCount, "\t")] | number := oneCount[0:strings.Index(oneCount, "\t")] | ||||
commitCnt, _ := strconv.Atoi(number) | commitCnt, _ := strconv.Atoi(number) | ||||
committer := oneCount[strings.Index(oneCount, "\t")+1:strings.LastIndex(oneCount, " ")] | |||||
committer := oneCount[strings.Index(oneCount, "\t")+1 : strings.LastIndex(oneCount, " ")] | |||||
committer = strings.Trim(committer, " ") | committer = strings.Trim(committer, " ") | ||||
email := oneCount[strings.Index(oneCount, "<")+1:strings.Index(oneCount, ">")] | |||||
email := oneCount[strings.Index(oneCount, "<")+1 : strings.Index(oneCount, ">")] | |||||
contributorsInfo[i] = Contributor{ | contributorsInfo[i] = Contributor{ | ||||
commitCnt, committer, email, | commitCnt, committer, email, | ||||
} | } | ||||
@@ -899,7 +899,7 @@ model.manage.Accuracy = Accuracy | |||||
model.manage.F1 = F1 | model.manage.F1 = F1 | ||||
model.manage.Precision = Precision | model.manage.Precision = Precision | ||||
model.manage.Recall = Recall | model.manage.Recall = Recall | ||||
model.manage.sava_model = Sava Model | |||||
template.items = Template Items | template.items = Template Items | ||||
template.git_content = Git Content (Default Branch) | template.git_content = Git Content (Default Branch) | ||||
@@ -908,6 +908,7 @@ model.manage.Accuracy = 准确率 | |||||
model.manage.F1 = F1值 | model.manage.F1 = F1值 | ||||
model.manage.Precision = 精确率 | model.manage.Precision = 精确率 | ||||
model.manage.Recall = 召回率 | model.manage.Recall = 召回率 | ||||
model.manage.sava_model = 保存模型 | |||||
template.items=模板选项 | template.items=模板选项 | ||||
template.git_content=Git数据(默认分支) | template.git_content=Git数据(默认分支) | ||||
@@ -0,0 +1,53 @@ | |||||
package routers | |||||
import ( | |||||
"code.gitea.io/gitea/models" | |||||
"code.gitea.io/gitea/modules/context" | |||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/services/socketwrap" | |||||
"github.com/gorilla/websocket" | |||||
) | |||||
var upgrader = websocket.Upgrader{ | |||||
ReadBufferSize: 1024, | |||||
WriteBufferSize: 1024, | |||||
} | |||||
var SocketManager = socketwrap.NewClientsManager() | |||||
func ActionNotification(ctx *context.Context) { | |||||
conn, err := upgrader.Upgrade(ctx.Resp, ctx.Req.Request, nil) | |||||
if err != nil { | |||||
log.Warn("can not create connection.", err) | |||||
return | |||||
} | |||||
client := &socketwrap.Client{Manager: SocketManager, Conn: conn, Send: make(chan *models.Action, 256)} | |||||
WriteLastTenActionsIfHave(conn) | |||||
client.Manager.Register <- client | |||||
go client.WritePump() | |||||
} | |||||
func WriteLastTenActionsIfHave(conn *websocket.Conn) { | |||||
socketwrap.LastTenActionsQueue.Mutex.RLock() | |||||
{ | |||||
size := socketwrap.LastTenActionsQueue.Queue.Len() | |||||
if size > 0 { | |||||
tempE := socketwrap.LastTenActionsQueue.Queue.Front() | |||||
conn.WriteJSON(tempE) | |||||
for i := 1; i < size; i++ { | |||||
tempE = tempE.Next() | |||||
conn.WriteJSON(tempE) | |||||
} | |||||
} | |||||
} | |||||
socketwrap.LastTenActionsQueue.Mutex.RUnlock() | |||||
} | |||||
@@ -72,7 +72,7 @@ func HandleBlockChainInitNotify(ctx *context.Context) { | |||||
repo.BlockChainStatus = models.RepoBlockChainSuccess | repo.BlockChainStatus = models.RepoBlockChainSuccess | ||||
repo.ContractAddress = req.ContractAddress | repo.ContractAddress = req.ContractAddress | ||||
if err = models.UpdateRepositoryCols(repo, "block_chain_status", "contract_address"); err != nil { | |||||
if err = repo.UpdateBlockChain(); err != nil { | |||||
log.Error("UpdateRepositoryCols failed:%v", err.Error(), ctx.Data["msgID"]) | log.Error("UpdateRepositoryCols failed:%v", err.Error(), ctx.Data["msgID"]) | ||||
ctx.JSON(200, map[string]string{ | ctx.JSON(200, map[string]string{ | ||||
"code": "-1", | "code": "-1", | ||||
@@ -193,6 +193,8 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB | |||||
var mileIDs []int64 | var mileIDs []int64 | ||||
if milestoneID > 0 { | if milestoneID > 0 { | ||||
mileIDs = []int64{milestoneID} | mileIDs = []int64{milestoneID} | ||||
} else if milestoneID == -1 { //only search no milestone | |||||
mileIDs = []int64{0} | |||||
} | } | ||||
var issues []*models.Issue | var issues []*models.Issue | ||||
@@ -355,7 +357,8 @@ func Issues(ctx *context.Context) { | |||||
var err error | var err error | ||||
// Get milestones. | // Get milestones. | ||||
ctx.Data["Milestones"], err = models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateType(ctx.Query("state")), models.ListOptions{}) | |||||
ctx.Data["OpenMilestones"], err = models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateOpen, models.ListOptions{}) | |||||
ctx.Data["ClosedMilestones"], err = models.GetMilestonesByRepoID(ctx.Repo.Repository.ID, api.StateClosed, models.ListOptions{}) | |||||
if err != nil { | if err != nil { | ||||
ctx.ServerError("GetAllRepoMilestones", err) | ctx.ServerError("GetAllRepoMilestones", err) | ||||
return | return | ||||
@@ -1230,7 +1230,7 @@ func TrainJobShow(ctx *context.Context) { | |||||
ctx.Data["canNewJob"] = canNewJob | ctx.Data["canNewJob"] = canNewJob | ||||
//将运行参数转化为epoch_size = 3, device_target = Ascend的格式 | //将运行参数转化为epoch_size = 3, device_target = Ascend的格式 | ||||
for i, _ := range VersionListTasks { | |||||
for i, task := range VersionListTasks { | |||||
var parameters models.Parameters | var parameters models.Parameters | ||||
@@ -1251,6 +1251,9 @@ func TrainJobShow(ctx *context.Context) { | |||||
} else { | } else { | ||||
VersionListTasks[i].Parameters = "" | VersionListTasks[i].Parameters = "" | ||||
} | } | ||||
VersionListTasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain) | |||||
VersionListTasks[i].CanModify = cloudbrain.CanModifyJob(ctx, &task.Cloudbrain) | |||||
} | } | ||||
pager := context.NewPagination(VersionListCount, setting.UI.IssuePagingNum, page, 5) | pager := context.NewPagination(VersionListCount, setting.UI.IssuePagingNum, page, 5) | ||||
@@ -605,7 +605,7 @@ func getContributorInfo(contributorInfos []*ContributorInfo, email string) *Cont | |||||
func Home(ctx *context.Context) { | func Home(ctx *context.Context) { | ||||
if len(ctx.Repo.Units) > 0 { | if len(ctx.Repo.Units) > 0 { | ||||
//get repo contributors info | //get repo contributors info | ||||
contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath()) | |||||
contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath(), ctx.Repo.BranchName) | |||||
if err == nil && contributors != nil { | if err == nil && contributors != nil { | ||||
startTime := time.Now() | startTime := time.Now() | ||||
var contributorInfos []*ContributorInfo | var contributorInfos []*ContributorInfo | ||||
@@ -924,7 +924,9 @@ func ContributorsAPI(ctx *context.Context) { | |||||
count := 0 | count := 0 | ||||
errorCode := 0 | errorCode := 0 | ||||
errorMsg := "" | errorMsg := "" | ||||
contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath()) | |||||
branchOrTag := ctx.Query("name") | |||||
contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath(), branchOrTag) | |||||
var contributorInfos []*ContributorInfo | var contributorInfos []*ContributorInfo | ||||
if err == nil && contributors != nil { | if err == nil && contributors != nil { | ||||
contributorInfoHash := make(map[string]*ContributorInfo) | contributorInfoHash := make(map[string]*ContributorInfo) | ||||
@@ -315,6 +315,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
}) | }) | ||||
m.Get("/", routers.Home) | m.Get("/", routers.Home) | ||||
m.Get("/dashboard", routers.Dashboard) | m.Get("/dashboard", routers.Dashboard) | ||||
go routers.SocketManager.Run() | |||||
m.Get("/action/notification", routers.ActionNotification) | |||||
m.Get("/recommend/org", routers.RecommendOrgFromPromote) | m.Get("/recommend/org", routers.RecommendOrgFromPromote) | ||||
m.Get("/recommend/repo", routers.RecommendRepoFromPromote) | m.Get("/recommend/repo", routers.RecommendRepoFromPromote) | ||||
m.Group("/explore", func() { | m.Group("/explore", func() { | ||||
@@ -0,0 +1,50 @@ | |||||
package socketwrap | |||||
import ( | |||||
"code.gitea.io/gitea/models" | |||||
"code.gitea.io/gitea/modules/log" | |||||
"github.com/gorilla/websocket" | |||||
) | |||||
type Client struct { | |||||
Manager *ClientsManager | |||||
Conn *websocket.Conn | |||||
Send chan *models.Action | |||||
} | |||||
func (c *Client) WritePump() { | |||||
defer func() { | |||||
c.Manager.Unregister <- c | |||||
c.Conn.Close() | |||||
}() | |||||
for { | |||||
select { | |||||
case message, ok := <-c.Send: | |||||
if !ok { | |||||
c.Conn.WriteMessage(websocket.CloseMessage, []byte{}) | |||||
log.Warn("send socket is closed") | |||||
return | |||||
} | |||||
log.Warn("socket:", message) | |||||
err := c.Conn.WriteJSON(message) | |||||
if err != nil { | |||||
log.Warn("can not send message", err) | |||||
return | |||||
} | |||||
n := len(c.Send) | |||||
for i := 0; i < n; i++ { | |||||
err = c.Conn.WriteJSON(<-c.Send) | |||||
if err != nil { | |||||
log.Warn("can not send message", err) | |||||
return | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,46 @@ | |||||
package socketwrap | |||||
import ( | |||||
"code.gitea.io/gitea/models" | |||||
) | |||||
type ClientsManager struct { | |||||
Clients map[*Client]bool | |||||
Register chan *Client | |||||
Unregister chan *Client | |||||
} | |||||
func NewClientsManager() *ClientsManager { | |||||
return &ClientsManager{ | |||||
Register: make(chan *Client), | |||||
Unregister: make(chan *Client), | |||||
Clients: make(map[*Client]bool), | |||||
} | |||||
} | |||||
var LastTenActionsQueue = NewSyncQueue(10) | |||||
func (h *ClientsManager) Run() { | |||||
for { | |||||
select { | |||||
case client := <-h.Register: | |||||
h.Clients[client] = true | |||||
case client := <-h.Unregister: | |||||
if _, ok := h.Clients[client]; ok { | |||||
delete(h.Clients, client) | |||||
close(client.Send) | |||||
} | |||||
case message := <-models.ActionChan: | |||||
LastTenActionsQueue.Push(message) | |||||
for client := range h.Clients { | |||||
select { | |||||
case client.Send <- message: | |||||
default: | |||||
close(client.Send) | |||||
delete(h.Clients, client) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,34 @@ | |||||
package socketwrap | |||||
import ( | |||||
"container/list" | |||||
"sync" | |||||
) | |||||
type SyncQueue struct { | |||||
Queue *list.List | |||||
Mutex *sync.RWMutex | |||||
MaxSize int | |||||
} | |||||
func (q *SyncQueue) Push(value interface{}) { | |||||
q.Mutex.Lock() | |||||
{ | |||||
if q.Queue.Len() < q.MaxSize { | |||||
q.Queue.PushBack(value) | |||||
} else { | |||||
q.Queue.PushBack(value) | |||||
q.Queue.Remove(q.Queue.Front()) | |||||
} | |||||
} | |||||
q.Mutex.Unlock() | |||||
} | |||||
func NewSyncQueue(maxSize int) *SyncQueue { | |||||
return &SyncQueue{ | |||||
list.New(), | |||||
&sync.RWMutex{}, | |||||
maxSize, | |||||
} | |||||
} |
@@ -93,16 +93,6 @@ | |||||
display: none; | display: none; | ||||
} | } | ||||
.select2-container .select2-selection--single{ | |||||
height:38px !important; | |||||
} | |||||
.select2-container--default .select2-selection--single { | |||||
border : 1px solid rgba(34,36,38,.15) !important; | |||||
} | |||||
.select2-container--default .select2-selection--single .select2-selection__rendered{ | |||||
line-height: 38px !important; | |||||
} | |||||
</style> | </style> | ||||
<div id="mask"> | <div id="mask"> | ||||
@@ -258,7 +248,7 @@ | |||||
<button class="ui green button" > | <button class="ui green button" > | ||||
{{.i18n.Tr "repo.cloudbrain.new"}} | {{.i18n.Tr "repo.cloudbrain.new"}} | ||||
</button> | </button> | ||||
<a class="ui button" href="{{.RepoLink}}/debugjob??debugListType=CPU/GPU">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||||
<a class="ui button cancel" href="">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
@@ -268,12 +258,7 @@ | |||||
</div> | </div> | ||||
</div> | </div> | ||||
{{template "base/footer" .}} | {{template "base/footer" .}} | ||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" /> | |||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script> | |||||
<script> | <script> | ||||
// let url_href = window.location.pathname.split('create')[0] | |||||
// $(".ui.button").attr('href',url_href) | |||||
let form = document.getElementById('form_id'); | let form = document.getElementById('form_id'); | ||||
@@ -291,10 +276,6 @@ | |||||
$('#messageInfo p').text(str) | $('#messageInfo p').text(str) | ||||
return false | return false | ||||
} | } | ||||
// if(!value_image || !value_data){ | |||||
// console.log("------------------------") | |||||
// return false | |||||
// } | |||||
let min_value_task = value_task.toLowerCase() | let min_value_task = value_task.toLowerCase() | ||||
$("input[name='job_name']").attr("value",min_value_task) | $("input[name='job_name']").attr("value",min_value_task) | ||||
document.getElementById("mask").style.display = "block" | document.getElementById("mask").style.display = "block" | ||||
@@ -97,16 +97,21 @@ | |||||
<span>{{svg "octicon-code" 16}} {{.i18n.Tr "repo.code"}} <i class="dropdown icon"></i></span> | <span>{{svg "octicon-code" 16}} {{.i18n.Tr "repo.code"}} <i class="dropdown icon"></i></span> | ||||
</a> | </a> | ||||
<div class="dropdown-content"> | <div class="dropdown-content"> | ||||
{{if and (.Permission.CanRead $.UnitTypeReleases) (not .IsEmptyRepo) }} | |||||
<a style="border: none;" class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases"> | <a style="border: none;" class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases"> | ||||
{{svg "octicon-tag" 16}} {{.i18n.Tr "repo.releases"}} <span class="ui {{if not .NumReleases}}gray{{else}}blue{{end}} small label">{{.NumReleases}}</span> | {{svg "octicon-tag" 16}} {{.i18n.Tr "repo.releases"}} <span class="ui {{if not .NumReleases}}gray{{else}}blue{{end}} small label">{{.NumReleases}}</span> | ||||
</a> | </a> | ||||
{{end}} | |||||
{{if or (.Permission.CanRead $.UnitTypeWiki) (.Permission.CanRead $.UnitTypeExternalWiki)}} | |||||
<a style="border: none;" class="{{if .PageIsWiki}}active{{end}} item" href="{{.RepoLink}}/wiki" {{if (.Permission.CanRead $.UnitTypeExternalWiki)}} target="_blank" rel="noopener noreferrer" {{end}}> | <a style="border: none;" class="{{if .PageIsWiki}}active{{end}} item" href="{{.RepoLink}}/wiki" {{if (.Permission.CanRead $.UnitTypeExternalWiki)}} target="_blank" rel="noopener noreferrer" {{end}}> | ||||
{{svg "octicon-book" 16}} {{.i18n.Tr "repo.wiki"}} | {{svg "octicon-book" 16}} {{.i18n.Tr "repo.wiki"}} | ||||
</a> | </a> | ||||
{{end}} | |||||
{{if and (.Permission.CanReadAny $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases) (not .IsEmptyRepo)}} | |||||
<a style="border: none;" class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity"> | <a style="border: none;" class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity"> | ||||
{{svg "octicon-pulse" 16}} {{.i18n.Tr "repo.activity"}} | {{svg "octicon-pulse" 16}} {{.i18n.Tr "repo.activity"}} | ||||
</a> | </a> | ||||
{{end}} | |||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -92,7 +92,7 @@ | |||||
<button class="ui green button"> | <button class="ui green button"> | ||||
{{.i18n.Tr "repo.cloudbrain.new"}} | {{.i18n.Tr "repo.cloudbrain.new"}} | ||||
</button> | </button> | ||||
<a class="ui button" href="{{.RepoLink}}/debugjob??debugListType=CPU/GPU">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||||
<a class="ui button cancel" href="">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
@@ -102,10 +102,6 @@ | |||||
{{template "base/footer" .}} | {{template "base/footer" .}} | ||||
<script> | <script> | ||||
// 取消创建跳转 | |||||
let url_href = window.location.pathname.split('create')[0] | |||||
$(".ui.button").attr('href',url_href) | |||||
// 判断必填选项是否填写正确 | // 判断必填选项是否填写正确 | ||||
let form = document.getElementById('form_id'); | let form = document.getElementById('form_id'); | ||||
@@ -53,7 +53,7 @@ | |||||
<div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本;</a></div> | <div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本;</a></div> | ||||
{{end}} | {{end}} | ||||
<div class="bgtask-content-txt">数据集:云脑1提供 CPU / GPU 资源,云脑2提供 Ascend NPU 资源,调试使用的数据集也需要上传到对应的环境;</div> | <div class="bgtask-content-txt">数据集:云脑1提供 CPU / GPU 资源,云脑2提供 Ascend NPU 资源,调试使用的数据集也需要上传到对应的环境;</div> | ||||
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org/zeizei/OpenI_Learning">小白训练营课程。</a></div> | |||||
<div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
{{else}} | {{else}} | ||||
@@ -137,10 +137,10 @@ | |||||
</div> | </div> | ||||
<div class="three wide column text center padding0"> | <div class="three wide column text center padding0"> | ||||
<!-- 删除任务 --> | |||||
<!-- 停止任务 --> | |||||
<div class="ui compact buttons"> | <div class="ui compact buttons"> | ||||
{{$.CsrfTokenHtml}} | {{$.CsrfTokenHtml}} | ||||
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}} | |||||
{{if .CanDel}} | |||||
<a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})"> | <a style="padding: 0.5rem 1rem;" id="{{.VersionName}}-stop" class="ui basic {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{else}} blue {{end}}button" onclick="stopVersion({{.VersionName}},{{.JobID}})"> | ||||
{{$.i18n.Tr "repo.stop"}} | {{$.i18n.Tr "repo.stop"}} | ||||
</a> | </a> | ||||
@@ -151,15 +151,16 @@ | |||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
<!-- 删除任务 --> | |||||
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/del" method="post"> | <form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/del" method="post"> | ||||
{{$.CsrfTokenHtml}} | {{$.CsrfTokenHtml}} | ||||
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}} | |||||
{{if .CanDel}} | |||||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic blue button" onclick="assertDelete(this)" style="border-radius: .28571429rem;"> | <a style="padding: 0.5rem 1rem;margin-left:0.2rem" id="model-delete-{{.JobID}}" class="ui basic blue button" onclick="assertDelete(this)" style="border-radius: .28571429rem;"> | ||||
{{$.i18n.Tr "repo.delete"}} | {{$.i18n.Tr "repo.delete"}} | ||||
</a> | </a> | ||||
{{else}} | {{else}} | ||||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" onclick="assertDelete(this)" style="border-radius: .28571429rem;"> | |||||
{{$.i18n.Tr "repo.delete"}} | |||||
<a style="padding: 0.5rem 1rem;margin-left:0.2rem" class="ui basic button disabled" style="border-radius: .28571429rem;"> | |||||
{{$.i18n.Tr "repo.delete"}} | |||||
</a> | </a> | ||||
{{end}} | {{end}} | ||||
</form> | </form> | ||||
@@ -206,7 +207,6 @@ | |||||
{{template "base/footer" .}} | {{template "base/footer" .}} | ||||
<script> | <script> | ||||
console.log({{.Tasks}}) | console.log({{.Tasks}}) | ||||
// 调试和评分新开窗口 | // 调试和评分新开窗口 | ||||
function stop(obj) { | function stop(obj) { | ||||
@@ -165,7 +165,6 @@ td, th { | |||||
{{template "repo/header" .}} | {{template "repo/header" .}} | ||||
<div class="ui container"> | <div class="ui container"> | ||||
<h4 class="ui header" id="vertical-segment"> | <h4 class="ui header" id="vertical-segment"> | ||||
<!-- <a href="javascript:window.history.back();"><i class="arrow left icon"></i>返回</a> --> | |||||
<div class="ui breadcrumb"> | <div class="ui breadcrumb"> | ||||
<a class="section" href="{{.RepoLink}}/cloudbrain"> | <a class="section" href="{{.RepoLink}}/cloudbrain"> | ||||
{{.i18n.Tr "repo.cloudbrain"}} | {{.i18n.Tr "repo.cloudbrain"}} | ||||
@@ -187,22 +186,21 @@ td, th { | |||||
<span class="accordion-panel-title-content"> | <span class="accordion-panel-title-content"> | ||||
<span> | <span> | ||||
<div style="float: right;"> | <div style="float: right;"> | ||||
<!-- <a class="ti-action-menu-item {{if ne .Status "COMPLETED"}}disabled {{end}}">创建模型</a> --> | |||||
{{$.CsrfTokenHtml}} | {{$.CsrfTokenHtml}} | ||||
{{if $.canNewJob}} | |||||
{{if .CanModify}} | |||||
<a class="ti-action-menu-item" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> | <a class="ti-action-menu-item" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> | ||||
{{else}} | {{else}} | ||||
<a class="ti-action-menu-item disabled" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> | <a class="ti-action-menu-item disabled" href="{{$.RepoLink}}/modelarts/train-job/{{.JobID}}/create_version?version_name={{.VersionName}}">{{$.i18n.Tr "repo.modelarts.modify"}}</a> | ||||
{{end}} | {{end}} | ||||
{{$.CsrfTokenHtml}} | {{$.CsrfTokenHtml}} | ||||
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}} | |||||
{{if .CanDel}} | |||||
<a class="ti-action-menu-item {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{end}}" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a> | <a class="ti-action-menu-item {{if eq .Status "KILLED" "FAILED" "START_FAILED" "KILLING" "COMPLETED"}}disabled {{end}}" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a> | ||||
{{else}} | {{else}} | ||||
<a class="ti-action-menu-item disabled" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a> | <a class="ti-action-menu-item disabled" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a> | ||||
{{end}} | {{end}} | ||||
{{$.CsrfTokenHtml}} | {{$.CsrfTokenHtml}} | ||||
{{if $.Permission.CanWrite $.UnitTypeCloudBrain}} | |||||
{{if .CanDel}} | |||||
<a class="ti-action-menu-item" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a> | <a class="ti-action-menu-item" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a> | ||||
{{else}} | {{else}} | ||||
<a class="ti-action-menu-item disabled" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a> | <a class="ti-action-menu-item disabled" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a> | ||||
@@ -243,12 +241,11 @@ td, th { | |||||
<tbody class="ti-text-form"> | <tbody class="ti-text-form"> | ||||
<tr class="ti-no-ng-animate"> | <tr class="ti-no-ng-animate"> | ||||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | <td class="ti-no-ng-animate ti-text-form-label text-width80"> | ||||
{{$.i18n.Tr "repo.cloudbrain_task"}} | |||||
{{$.i18n.Tr "repo.cloudbrain_task"}} | |||||
</td> | </td> | ||||
<td class="ti-text-form-content"> | <td class="ti-text-form-content"> | ||||
<div class="text-span text-span-w"> | <div class="text-span text-span-w"> | ||||
{{.JobName}} | |||||
{{.JobName}} | |||||
</div> | </div> | ||||
</td> | </td> | ||||
</tr> | </tr> | ||||
@@ -259,7 +256,7 @@ td, th { | |||||
<td class="ti-text-form-content"> | <td class="ti-text-form-content"> | ||||
<div class="text-span text-span-w" id="{{.VersionName}}-status"> | <div class="text-span text-span-w" id="{{.VersionName}}-status"> | ||||
{{.Status}} | |||||
{{.Status}} | |||||
</div> | </div> | ||||
</td> | </td> | ||||
</tr> | </tr> | ||||
@@ -270,7 +267,7 @@ td, th { | |||||
<td class="ti-text-form-content"> | <td class="ti-text-form-content"> | ||||
<div class="text-span text-span-w"> | <div class="text-span text-span-w"> | ||||
{{.VersionName}} | |||||
{{.VersionName}} | |||||
</div> | </div> | ||||
</td> | </td> | ||||
</tr> | </tr> | ||||
@@ -287,7 +284,7 @@ td, th { | |||||
</tr> | </tr> | ||||
<tr class="ti-no-ng-animate"> | <tr class="ti-no-ng-animate"> | ||||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | <td class="ti-no-ng-animate ti-text-form-label text-width80"> | ||||
{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}} | |||||
{{$.i18n.Tr "repo.modelarts.train_job.dura_time"}} | |||||
</td> | </td> | ||||
<td class="ti-text-form-content"> | <td class="ti-text-form-content"> | ||||
@@ -309,9 +306,8 @@ td, th { | |||||
</tr> | </tr> | ||||
<tr class="ti-no-ng-animate"> | <tr class="ti-no-ng-animate"> | ||||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | <td class="ti-no-ng-animate ti-text-form-label text-width80"> | ||||
{{$.i18n.Tr "repo.modelarts.train_job.compute_node"}} | |||||
{{$.i18n.Tr "repo.modelarts.train_job.compute_node"}} | |||||
</td> | </td> | ||||
<td class="ti-text-form-content"> | <td class="ti-text-form-content"> | ||||
<div class="text-span text-span-w"> | <div class="text-span text-span-w"> | ||||
{{.WorkServerNumber}} | {{.WorkServerNumber}} | ||||
@@ -326,9 +322,8 @@ td, th { | |||||
<tbody class="ti-text-form"> | <tbody class="ti-text-form"> | ||||
<tr class="ti-no-ng-animate"> | <tr class="ti-no-ng-animate"> | ||||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | <td class="ti-no-ng-animate ti-text-form-label text-width80"> | ||||
{{$.i18n.Tr "repo.modelarts.train_job.AI_driver"}} | |||||
{{$.i18n.Tr "repo.modelarts.train_job.AI_driver"}} | |||||
</td> | </td> | ||||
<td class="ti-text-form-content"> | <td class="ti-text-form-content"> | ||||
<div class="text-span text-span-w"> | <div class="text-span text-span-w"> | ||||
{{.EngineName}} | {{.EngineName}} | ||||
@@ -348,12 +343,12 @@ td, th { | |||||
</tr> | </tr> | ||||
<tr class="ti-no-ng-animate"> | <tr class="ti-no-ng-animate"> | ||||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | <td class="ti-no-ng-animate ti-text-form-label text-width80"> | ||||
{{$.i18n.Tr "repo.modelarts.train_job.start_file"}} | |||||
{{$.i18n.Tr "repo.modelarts.train_job.start_file"}} | |||||
</td> | </td> | ||||
<td class="ti-text-form-content"> | <td class="ti-text-form-content"> | ||||
<div class="text-span text-span-w"> | <div class="text-span text-span-w"> | ||||
{{.BootFile}} | |||||
{{.BootFile}} | |||||
</div> | </div> | ||||
</td> | </td> | ||||
</tr> | </tr> | ||||
@@ -379,17 +374,7 @@ td, th { | |||||
</div> | </div> | ||||
</td> | </td> | ||||
</tr> | </tr> | ||||
<!-- <tr class="ti-no-ng-animate"> | |||||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||||
训练输出位置 | |||||
</td> | |||||
<td class="ti-text-form-content"> | |||||
<div class="text-span text-span-w"> | |||||
{{.TrainUrl}} | |||||
</div> | |||||
</td> | |||||
</tr> --> | |||||
</tr> | |||||
<tr class="ti-no-ng-animate"> | <tr class="ti-no-ng-animate"> | ||||
<td class="ti-no-ng-animate ti-text-form-label text-width80"> | <td class="ti-no-ng-animate ti-text-form-label text-width80"> | ||||
{{$.i18n.Tr "repo.modelarts.train_job.description"}} | {{$.i18n.Tr "repo.modelarts.train_job.description"}} | ||||
@@ -414,15 +399,7 @@ td, th { | |||||
<div class="ui message message{{.VersionName}}" style="display: none;"> | <div class="ui message message{{.VersionName}}" style="display: none;"> | ||||
<div id="header"></div> | <div id="header"></div> | ||||
</div> | </div> | ||||
<!-- <div class="ui top attached segment" style="background: #f0f0f0;"> | |||||
<div class="center aligned"> | |||||
<label>{{$.i18n.Tr "repo.modelarts.log"}}:</label> | |||||
</div> | |||||
</div> --> | |||||
<div class="ui attached log" onscroll="logScroll({{.VersionName}})" id="log{{.VersionName}}" style="height: 300px !important; overflow: auto;"> | <div class="ui attached log" onscroll="logScroll({{.VersionName}})" id="log{{.VersionName}}" style="height: 300px !important; overflow: auto;"> | ||||
<!-- <input type="hidden" class="version_name" name="version_name" value={{.VersionName}}> --> | |||||
<input type="hidden" name="end_line" value> | <input type="hidden" name="end_line" value> | ||||
<input type="hidden" name="start_line" value> | <input type="hidden" name="start_line" value> | ||||
<pre id="log_file{{.VersionName}}"></pre> | <pre id="log_file{{.VersionName}}"></pre> | ||||
@@ -474,9 +451,7 @@ td, th { | |||||
<script> | <script> | ||||
console.log({{.version_list_task}}) | console.log({{.version_list_task}}) | ||||
console.log({{.}}) | |||||
$('.menu .item').tab() | $('.menu .item').tab() | ||||
// $('.ui.style.accordion').accordion(); | |||||
$(document).ready(function(){ | $(document).ready(function(){ | ||||
$('.ui.accordion').accordion({selector:{trigger:'.icon'}}); | $('.ui.accordion').accordion({selector:{trigger:'.icon'}}); | ||||
@@ -503,8 +478,6 @@ td, th { | |||||
e.cancelBubble = true; //ie兼容 | e.cancelBubble = true; //ie兼容 | ||||
} | } | ||||
} | } | ||||
// let timeid = window.setInterval(refreshStatus(version_name), 30000); | |||||
// document.ready(refreshStatus(version_name)) | |||||
let timeid = window.setInterval(loadJobStatus, 30000); | let timeid = window.setInterval(loadJobStatus, 30000); | ||||
$(document).ready(loadJobStatus); | $(document).ready(loadJobStatus); | ||||
@@ -114,7 +114,7 @@ | |||||
</div> | </div> | ||||
<div class="inline field"> | <div class="inline field"> | ||||
<label>模型标签</label> | <label>模型标签</label> | ||||
<input style="width: 83%;margin-left: 7px;" name="Label" maxlength="255" placeholder='{{.i18n.Tr "repo.modelarts.train_job.label_place"}}'> | |||||
<input style="width: 83%;margin-left: 7px;" id="label" name="Label" maxlength="255" placeholder='{{.i18n.Tr "repo.modelarts.train_job.label_place"}}'> | |||||
</div> | </div> | ||||
<div class="inline field"> | <div class="inline field"> | ||||
<label for="description">模型描述</label> | <label for="description">模型描述</label> | ||||
@@ -123,7 +123,7 @@ | |||||
<div class="inline field" style="margin-left: 75px;"> | <div class="inline field" style="margin-left: 75px;"> | ||||
<button id="submitId" type="button" class="ui create_train_job green button" style="position: absolute;"> | <button id="submitId" type="button" class="ui create_train_job green button" style="position: absolute;"> | ||||
{{.i18n.Tr "repo.cloudbrain.new"}} | |||||
{{.i18n.Tr "repo.model.manage.sava_model"}} | |||||
</button> | </button> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
@@ -169,6 +169,7 @@ | |||||
$('#choice_model').dropdown('clear') | $('#choice_model').dropdown('clear') | ||||
$('#choice_version').dropdown('clear') | $('#choice_version').dropdown('clear') | ||||
$('.ui.dimmer').css({"background-color":""}) | $('.ui.dimmer').css({"background-color":""}) | ||||
$('.ui.error.message').text() | |||||
$('.ui.error.message').css('display','none') | $('.ui.error.message').css('display','none') | ||||
} | } | ||||
@@ -211,7 +212,6 @@ | |||||
} | } | ||||
$("#job-name").append(train_html) | $("#job-name").append(train_html) | ||||
$(".ui.dropdown.selection.search.width83").removeClass("loading") | $(".ui.dropdown.selection.search.width83").removeClass("loading") | ||||
$('#choice_model .default.text').text(data[0].JobName) | $('#choice_model .default.text').text(data[0].JobName) | ||||
$('#choice_model input[name="JobId"]').val(data[0].JobID) | $('#choice_model input[name="JobId"]').val(data[0].JobID) | ||||
loadTrainVersion() | loadTrainVersion() | ||||
@@ -227,10 +227,13 @@ | |||||
train_html += `<div class="item" data-value="${data[i].VersionName}">${data[i].VersionName}</div>` | train_html += `<div class="item" data-value="${data[i].VersionName}">${data[i].VersionName}</div>` | ||||
train_html += '</div>' | train_html += '</div>' | ||||
} | } | ||||
$("#job-version").append(train_html) | |||||
$(".ui.dropdown.selection.search.width70").removeClass("loading") | |||||
$('#choice_version .default.text').text(data[0].VersionName) | |||||
$('#choice_version input[name="VersionName"]').val(data[0].VersionName) | |||||
if(data.length){ | |||||
$("#job-version").append(train_html) | |||||
$(".ui.dropdown.selection.search.width70").removeClass("loading") | |||||
$('#choice_version .default.text').text(data[0].VersionName) | |||||
$('#choice_version input[name="VersionName"]').val(data[0].VersionName) | |||||
} | |||||
}) | }) | ||||
} | } | ||||
</script> | </script> | ||||
@@ -93,7 +93,7 @@ | |||||
<tr> | <tr> | ||||
<td class="ti-text-form-label text-width80">标签</td> | <td class="ti-text-form-label text-width80">标签</td> | ||||
<td class="ti-text-form-content"> | <td class="ti-text-form-content"> | ||||
<div id="Label"> | |||||
<div id="Label" style="overflow: hidden;width: 95%;"> | |||||
</div> | </div> | ||||
@@ -221,6 +221,7 @@ function tranSize(value){ | |||||
function editorFn(context){ | function editorFn(context){ | ||||
let id= context.dataset.id | let id= context.dataset.id | ||||
let text = context.dataset.desc | let text = context.dataset.desc | ||||
console.log(id,text) | |||||
$('#edit-td').replaceWith("<div id='edit-div' style='width:80%;display: inline-block;'><textarea id='textarea-value' value='' rows='3' maxlength='255' style='width:80%;' id='edit-text'>"+text+"</textarea><i class='check icon' style='color: #50d4ab;' onclick='editorSure(\"" + text + "\",\"" + id + "\")'></i><i class='times icon' style='color: #f66f6a;' onclick='editorCancel(\"" + text + "\",\"" + id + "\")'></i></div>"); | $('#edit-td').replaceWith("<div id='edit-div' style='width:80%;display: inline-block;'><textarea id='textarea-value' value='' rows='3' maxlength='255' style='width:80%;' id='edit-text'>"+text+"</textarea><i class='check icon' style='color: #50d4ab;' onclick='editorSure(\"" + text + "\",\"" + id + "\")'></i><i class='times icon' style='color: #f66f6a;' onclick='editorCancel(\"" + text + "\",\"" + id + "\")'></i></div>"); | ||||
} | } | ||||
@@ -254,10 +255,11 @@ function renderInfo(obj,accObj,id){ | |||||
if(obj[key]==='--'){ | if(obj[key]==='--'){ | ||||
$('#Label').text(obj[key]) | $('#Label').text(obj[key]) | ||||
}else{ | }else{ | ||||
let labelArray = obj[key].trim().split(' ') | |||||
let labelArray = obj[key].trim().replace(/ +/g,' ').split(' ') | |||||
let html='' | let html='' | ||||
for(let i=0;i<labelArray.length;i++){ | for(let i=0;i<labelArray.length;i++){ | ||||
html += `<a class="ui label">${labelArray[i]}</a>` | |||||
html += `<a class="ui label" title="${labelArray[i]}">${labelArray[i]}</a>` | |||||
} | } | ||||
$('#Label').append(html) | $('#Label').append(html) | ||||
} | } | ||||
@@ -0,0 +1,25 @@ | |||||
# Compiled Object files, Static and Dynamic libs (Shared Objects) | |||||
*.o | |||||
*.a | |||||
*.so | |||||
# Folders | |||||
_obj | |||||
_test | |||||
# Architecture specific extensions/prefixes | |||||
*.[568vq] | |||||
[568vq].out | |||||
*.cgo1.go | |||||
*.cgo2.c | |||||
_cgo_defun.c | |||||
_cgo_gotypes.go | |||||
_cgo_export.* | |||||
_testmain.go | |||||
*.exe | |||||
.idea/ | |||||
*.iml |
@@ -0,0 +1,19 @@ | |||||
language: go | |||||
sudo: false | |||||
matrix: | |||||
include: | |||||
- go: 1.7.x | |||||
- go: 1.8.x | |||||
- go: 1.9.x | |||||
- go: 1.10.x | |||||
- go: 1.11.x | |||||
- go: tip | |||||
allow_failures: | |||||
- go: tip | |||||
script: | |||||
- go get -t -v ./... | |||||
- diff -u <(echo -n) <(gofmt -d .) | |||||
- go vet $(go list ./... | grep -v /vendor/) | |||||
- go test -v -race ./... |
@@ -0,0 +1,9 @@ | |||||
# This is the official list of Gorilla WebSocket authors for copyright | |||||
# purposes. | |||||
# | |||||
# Please keep the list sorted. | |||||
Gary Burd <gary@beagledreams.com> | |||||
Google LLC (https://opensource.google.com/) | |||||
Joachim Bauch <mail@joachim-bauch.de> | |||||
@@ -0,0 +1,22 @@ | |||||
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. | |||||
Redistribution and use in source and binary forms, with or without | |||||
modification, are permitted provided that the following conditions are met: | |||||
Redistributions of source code must retain the above copyright notice, this | |||||
list of conditions and the following disclaimer. | |||||
Redistributions in binary form must reproduce the above copyright notice, | |||||
this list of conditions and the following disclaimer in the documentation | |||||
and/or other materials provided with the distribution. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@@ -0,0 +1,64 @@ | |||||
# Gorilla WebSocket | |||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the | |||||
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. | |||||
[](https://travis-ci.org/gorilla/websocket) | |||||
[](https://godoc.org/github.com/gorilla/websocket) | |||||
### Documentation | |||||
* [API Reference](http://godoc.org/github.com/gorilla/websocket) | |||||
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) | |||||
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) | |||||
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) | |||||
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) | |||||
### Status | |||||
The Gorilla WebSocket package provides a complete and tested implementation of | |||||
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The | |||||
package API is stable. | |||||
### Installation | |||||
go get github.com/gorilla/websocket | |||||
### Protocol Compliance | |||||
The Gorilla WebSocket package passes the server tests in the [Autobahn Test | |||||
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn | |||||
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). | |||||
### Gorilla WebSocket compared with other packages | |||||
<table> | |||||
<tr> | |||||
<th></th> | |||||
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th> | |||||
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th> | |||||
</tr> | |||||
<tr> | |||||
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr> | |||||
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr> | |||||
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr> | |||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr> | |||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr> | |||||
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr> | |||||
<tr><td colspan="3">Other Features</tr></td> | |||||
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr> | |||||
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr> | |||||
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr> | |||||
</table> | |||||
Notes: | |||||
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). | |||||
2. The application can get the type of a received data message by implementing | |||||
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal) | |||||
function. | |||||
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. | |||||
Read returns when the input buffer is full or a frame boundary is | |||||
encountered. Each call to Write sends a single frame message. The Gorilla | |||||
io.Reader and io.WriteCloser operate on a single WebSocket message. | |||||
@@ -0,0 +1,395 @@ | |||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package websocket | |||||
import ( | |||||
"bytes" | |||||
"context" | |||||
"crypto/tls" | |||||
"errors" | |||||
"io" | |||||
"io/ioutil" | |||||
"net" | |||||
"net/http" | |||||
"net/http/httptrace" | |||||
"net/url" | |||||
"strings" | |||||
"time" | |||||
) | |||||
// ErrBadHandshake is returned when the server response to opening handshake is | |||||
// invalid. | |||||
var ErrBadHandshake = errors.New("websocket: bad handshake") | |||||
var errInvalidCompression = errors.New("websocket: invalid compression negotiation") | |||||
// NewClient creates a new client connection using the given net connection. | |||||
// The URL u specifies the host and request URI. Use requestHeader to specify | |||||
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies | |||||
// (Cookie). Use the response.Header to get the selected subprotocol | |||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). | |||||
// | |||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a | |||||
// non-nil *http.Response so that callers can handle redirects, authentication, | |||||
// etc. | |||||
// | |||||
// Deprecated: Use Dialer instead. | |||||
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { | |||||
d := Dialer{ | |||||
ReadBufferSize: readBufSize, | |||||
WriteBufferSize: writeBufSize, | |||||
NetDial: func(net, addr string) (net.Conn, error) { | |||||
return netConn, nil | |||||
}, | |||||
} | |||||
return d.Dial(u.String(), requestHeader) | |||||
} | |||||
// A Dialer contains options for connecting to WebSocket server. | |||||
type Dialer struct { | |||||
// NetDial specifies the dial function for creating TCP connections. If | |||||
// NetDial is nil, net.Dial is used. | |||||
NetDial func(network, addr string) (net.Conn, error) | |||||
// NetDialContext specifies the dial function for creating TCP connections. If | |||||
// NetDialContext is nil, net.DialContext is used. | |||||
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error) | |||||
// Proxy specifies a function to return a proxy for a given | |||||
// Request. If the function returns a non-nil error, the | |||||
// request is aborted with the provided error. | |||||
// If Proxy is nil or returns a nil *URL, no proxy is used. | |||||
Proxy func(*http.Request) (*url.URL, error) | |||||
// TLSClientConfig specifies the TLS configuration to use with tls.Client. | |||||
// If nil, the default configuration is used. | |||||
TLSClientConfig *tls.Config | |||||
// HandshakeTimeout specifies the duration for the handshake to complete. | |||||
HandshakeTimeout time.Duration | |||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer | |||||
// size is zero, then a useful default size is used. The I/O buffer sizes | |||||
// do not limit the size of the messages that can be sent or received. | |||||
ReadBufferSize, WriteBufferSize int | |||||
// WriteBufferPool is a pool of buffers for write operations. If the value | |||||
// is not set, then write buffers are allocated to the connection for the | |||||
// lifetime of the connection. | |||||
// | |||||
// A pool is most useful when the application has a modest volume of writes | |||||
// across a large number of connections. | |||||
// | |||||
// Applications should use a single pool for each unique value of | |||||
// WriteBufferSize. | |||||
WriteBufferPool BufferPool | |||||
// Subprotocols specifies the client's requested subprotocols. | |||||
Subprotocols []string | |||||
// EnableCompression specifies if the client should attempt to negotiate | |||||
// per message compression (RFC 7692). Setting this value to true does not | |||||
// guarantee that compression will be supported. Currently only "no context | |||||
// takeover" modes are supported. | |||||
EnableCompression bool | |||||
// Jar specifies the cookie jar. | |||||
// If Jar is nil, cookies are not sent in requests and ignored | |||||
// in responses. | |||||
Jar http.CookieJar | |||||
} | |||||
// Dial creates a new client connection by calling DialContext with a background context. | |||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { | |||||
return d.DialContext(context.Background(), urlStr, requestHeader) | |||||
} | |||||
var errMalformedURL = errors.New("malformed ws or wss URL") | |||||
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { | |||||
hostPort = u.Host | |||||
hostNoPort = u.Host | |||||
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { | |||||
hostNoPort = hostNoPort[:i] | |||||
} else { | |||||
switch u.Scheme { | |||||
case "wss": | |||||
hostPort += ":443" | |||||
case "https": | |||||
hostPort += ":443" | |||||
default: | |||||
hostPort += ":80" | |||||
} | |||||
} | |||||
return hostPort, hostNoPort | |||||
} | |||||
// DefaultDialer is a dialer with all fields set to the default values. | |||||
var DefaultDialer = &Dialer{ | |||||
Proxy: http.ProxyFromEnvironment, | |||||
HandshakeTimeout: 45 * time.Second, | |||||
} | |||||
// nilDialer is dialer to use when receiver is nil. | |||||
var nilDialer = *DefaultDialer | |||||
// DialContext creates a new client connection. Use requestHeader to specify the | |||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). | |||||
// Use the response.Header to get the selected subprotocol | |||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). | |||||
// | |||||
// The context will be used in the request and in the Dialer | |||||
// | |||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a | |||||
// non-nil *http.Response so that callers can handle redirects, authentication, | |||||
// etcetera. The response body may not contain the entire response and does not | |||||
// need to be closed by the application. | |||||
func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { | |||||
if d == nil { | |||||
d = &nilDialer | |||||
} | |||||
challengeKey, err := generateChallengeKey() | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
u, err := url.Parse(urlStr) | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
switch u.Scheme { | |||||
case "ws": | |||||
u.Scheme = "http" | |||||
case "wss": | |||||
u.Scheme = "https" | |||||
default: | |||||
return nil, nil, errMalformedURL | |||||
} | |||||
if u.User != nil { | |||||
// User name and password are not allowed in websocket URIs. | |||||
return nil, nil, errMalformedURL | |||||
} | |||||
req := &http.Request{ | |||||
Method: "GET", | |||||
URL: u, | |||||
Proto: "HTTP/1.1", | |||||
ProtoMajor: 1, | |||||
ProtoMinor: 1, | |||||
Header: make(http.Header), | |||||
Host: u.Host, | |||||
} | |||||
req = req.WithContext(ctx) | |||||
// Set the cookies present in the cookie jar of the dialer | |||||
if d.Jar != nil { | |||||
for _, cookie := range d.Jar.Cookies(u) { | |||||
req.AddCookie(cookie) | |||||
} | |||||
} | |||||
// Set the request headers using the capitalization for names and values in | |||||
// RFC examples. Although the capitalization shouldn't matter, there are | |||||
// servers that depend on it. The Header.Set method is not used because the | |||||
// method canonicalizes the header names. | |||||
req.Header["Upgrade"] = []string{"websocket"} | |||||
req.Header["Connection"] = []string{"Upgrade"} | |||||
req.Header["Sec-WebSocket-Key"] = []string{challengeKey} | |||||
req.Header["Sec-WebSocket-Version"] = []string{"13"} | |||||
if len(d.Subprotocols) > 0 { | |||||
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")} | |||||
} | |||||
for k, vs := range requestHeader { | |||||
switch { | |||||
case k == "Host": | |||||
if len(vs) > 0 { | |||||
req.Host = vs[0] | |||||
} | |||||
case k == "Upgrade" || | |||||
k == "Connection" || | |||||
k == "Sec-Websocket-Key" || | |||||
k == "Sec-Websocket-Version" || | |||||
k == "Sec-Websocket-Extensions" || | |||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): | |||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) | |||||
case k == "Sec-Websocket-Protocol": | |||||
req.Header["Sec-WebSocket-Protocol"] = vs | |||||
default: | |||||
req.Header[k] = vs | |||||
} | |||||
} | |||||
if d.EnableCompression { | |||||
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"} | |||||
} | |||||
if d.HandshakeTimeout != 0 { | |||||
var cancel func() | |||||
ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout) | |||||
defer cancel() | |||||
} | |||||
// Get network dial function. | |||||
var netDial func(network, add string) (net.Conn, error) | |||||
if d.NetDialContext != nil { | |||||
netDial = func(network, addr string) (net.Conn, error) { | |||||
return d.NetDialContext(ctx, network, addr) | |||||
} | |||||
} else if d.NetDial != nil { | |||||
netDial = d.NetDial | |||||
} else { | |||||
netDialer := &net.Dialer{} | |||||
netDial = func(network, addr string) (net.Conn, error) { | |||||
return netDialer.DialContext(ctx, network, addr) | |||||
} | |||||
} | |||||
// If needed, wrap the dial function to set the connection deadline. | |||||
if deadline, ok := ctx.Deadline(); ok { | |||||
forwardDial := netDial | |||||
netDial = func(network, addr string) (net.Conn, error) { | |||||
c, err := forwardDial(network, addr) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
err = c.SetDeadline(deadline) | |||||
if err != nil { | |||||
c.Close() | |||||
return nil, err | |||||
} | |||||
return c, nil | |||||
} | |||||
} | |||||
// If needed, wrap the dial function to connect through a proxy. | |||||
if d.Proxy != nil { | |||||
proxyURL, err := d.Proxy(req) | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
if proxyURL != nil { | |||||
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial)) | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
netDial = dialer.Dial | |||||
} | |||||
} | |||||
hostPort, hostNoPort := hostPortNoPort(u) | |||||
trace := httptrace.ContextClientTrace(ctx) | |||||
if trace != nil && trace.GetConn != nil { | |||||
trace.GetConn(hostPort) | |||||
} | |||||
netConn, err := netDial("tcp", hostPort) | |||||
if trace != nil && trace.GotConn != nil { | |||||
trace.GotConn(httptrace.GotConnInfo{ | |||||
Conn: netConn, | |||||
}) | |||||
} | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
defer func() { | |||||
if netConn != nil { | |||||
netConn.Close() | |||||
} | |||||
}() | |||||
if u.Scheme == "https" { | |||||
cfg := cloneTLSConfig(d.TLSClientConfig) | |||||
if cfg.ServerName == "" { | |||||
cfg.ServerName = hostNoPort | |||||
} | |||||
tlsConn := tls.Client(netConn, cfg) | |||||
netConn = tlsConn | |||||
var err error | |||||
if trace != nil { | |||||
err = doHandshakeWithTrace(trace, tlsConn, cfg) | |||||
} else { | |||||
err = doHandshake(tlsConn, cfg) | |||||
} | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
} | |||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil) | |||||
if err := req.Write(netConn); err != nil { | |||||
return nil, nil, err | |||||
} | |||||
if trace != nil && trace.GotFirstResponseByte != nil { | |||||
if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 { | |||||
trace.GotFirstResponseByte() | |||||
} | |||||
} | |||||
resp, err := http.ReadResponse(conn.br, req) | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
if d.Jar != nil { | |||||
if rc := resp.Cookies(); len(rc) > 0 { | |||||
d.Jar.SetCookies(u, rc) | |||||
} | |||||
} | |||||
if resp.StatusCode != 101 || | |||||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || | |||||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || | |||||
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) { | |||||
// Before closing the network connection on return from this | |||||
// function, slurp up some of the response to aid application | |||||
// debugging. | |||||
buf := make([]byte, 1024) | |||||
n, _ := io.ReadFull(resp.Body, buf) | |||||
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) | |||||
return nil, resp, ErrBadHandshake | |||||
} | |||||
for _, ext := range parseExtensions(resp.Header) { | |||||
if ext[""] != "permessage-deflate" { | |||||
continue | |||||
} | |||||
_, snct := ext["server_no_context_takeover"] | |||||
_, cnct := ext["client_no_context_takeover"] | |||||
if !snct || !cnct { | |||||
return nil, resp, errInvalidCompression | |||||
} | |||||
conn.newCompressionWriter = compressNoContextTakeover | |||||
conn.newDecompressionReader = decompressNoContextTakeover | |||||
break | |||||
} | |||||
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) | |||||
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") | |||||
netConn.SetDeadline(time.Time{}) | |||||
netConn = nil // to avoid close in defer. | |||||
return conn, resp, nil | |||||
} | |||||
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error { | |||||
if err := tlsConn.Handshake(); err != nil { | |||||
return err | |||||
} | |||||
if !cfg.InsecureSkipVerify { | |||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
return nil | |||||
} |
@@ -0,0 +1,16 @@ | |||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// +build go1.8 | |||||
package websocket | |||||
import "crypto/tls" | |||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config { | |||||
if cfg == nil { | |||||
return &tls.Config{} | |||||
} | |||||
return cfg.Clone() | |||||
} |
@@ -0,0 +1,38 @@ | |||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// +build !go1.8 | |||||
package websocket | |||||
import "crypto/tls" | |||||
// cloneTLSConfig clones all public fields except the fields | |||||
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the | |||||
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a | |||||
// config in active use. | |||||
func cloneTLSConfig(cfg *tls.Config) *tls.Config { | |||||
if cfg == nil { | |||||
return &tls.Config{} | |||||
} | |||||
return &tls.Config{ | |||||
Rand: cfg.Rand, | |||||
Time: cfg.Time, | |||||
Certificates: cfg.Certificates, | |||||
NameToCertificate: cfg.NameToCertificate, | |||||
GetCertificate: cfg.GetCertificate, | |||||
RootCAs: cfg.RootCAs, | |||||
NextProtos: cfg.NextProtos, | |||||
ServerName: cfg.ServerName, | |||||
ClientAuth: cfg.ClientAuth, | |||||
ClientCAs: cfg.ClientCAs, | |||||
InsecureSkipVerify: cfg.InsecureSkipVerify, | |||||
CipherSuites: cfg.CipherSuites, | |||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites, | |||||
ClientSessionCache: cfg.ClientSessionCache, | |||||
MinVersion: cfg.MinVersion, | |||||
MaxVersion: cfg.MaxVersion, | |||||
CurvePreferences: cfg.CurvePreferences, | |||||
} | |||||
} |
@@ -0,0 +1,148 @@ | |||||
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package websocket | |||||
import ( | |||||
"compress/flate" | |||||
"errors" | |||||
"io" | |||||
"strings" | |||||
"sync" | |||||
) | |||||
const ( | |||||
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6 | |||||
maxCompressionLevel = flate.BestCompression | |||||
defaultCompressionLevel = 1 | |||||
) | |||||
var ( | |||||
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool | |||||
flateReaderPool = sync.Pool{New: func() interface{} { | |||||
return flate.NewReader(nil) | |||||
}} | |||||
) | |||||
func decompressNoContextTakeover(r io.Reader) io.ReadCloser { | |||||
const tail = | |||||
// Add four bytes as specified in RFC | |||||
"\x00\x00\xff\xff" + | |||||
// Add final block to squelch unexpected EOF error from flate reader. | |||||
"\x01\x00\x00\xff\xff" | |||||
fr, _ := flateReaderPool.Get().(io.ReadCloser) | |||||
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) | |||||
return &flateReadWrapper{fr} | |||||
} | |||||
func isValidCompressionLevel(level int) bool { | |||||
return minCompressionLevel <= level && level <= maxCompressionLevel | |||||
} | |||||
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser { | |||||
p := &flateWriterPools[level-minCompressionLevel] | |||||
tw := &truncWriter{w: w} | |||||
fw, _ := p.Get().(*flate.Writer) | |||||
if fw == nil { | |||||
fw, _ = flate.NewWriter(tw, level) | |||||
} else { | |||||
fw.Reset(tw) | |||||
} | |||||
return &flateWriteWrapper{fw: fw, tw: tw, p: p} | |||||
} | |||||
// truncWriter is an io.Writer that writes all but the last four bytes of the | |||||
// stream to another io.Writer. | |||||
type truncWriter struct { | |||||
w io.WriteCloser | |||||
n int | |||||
p [4]byte | |||||
} | |||||
func (w *truncWriter) Write(p []byte) (int, error) { | |||||
n := 0 | |||||
// fill buffer first for simplicity. | |||||
if w.n < len(w.p) { | |||||
n = copy(w.p[w.n:], p) | |||||
p = p[n:] | |||||
w.n += n | |||||
if len(p) == 0 { | |||||
return n, nil | |||||
} | |||||
} | |||||
m := len(p) | |||||
if m > len(w.p) { | |||||
m = len(w.p) | |||||
} | |||||
if nn, err := w.w.Write(w.p[:m]); err != nil { | |||||
return n + nn, err | |||||
} | |||||
copy(w.p[:], w.p[m:]) | |||||
copy(w.p[len(w.p)-m:], p[len(p)-m:]) | |||||
nn, err := w.w.Write(p[:len(p)-m]) | |||||
return n + nn, err | |||||
} | |||||
type flateWriteWrapper struct { | |||||
fw *flate.Writer | |||||
tw *truncWriter | |||||
p *sync.Pool | |||||
} | |||||
func (w *flateWriteWrapper) Write(p []byte) (int, error) { | |||||
if w.fw == nil { | |||||
return 0, errWriteClosed | |||||
} | |||||
return w.fw.Write(p) | |||||
} | |||||
func (w *flateWriteWrapper) Close() error { | |||||
if w.fw == nil { | |||||
return errWriteClosed | |||||
} | |||||
err1 := w.fw.Flush() | |||||
w.p.Put(w.fw) | |||||
w.fw = nil | |||||
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { | |||||
return errors.New("websocket: internal error, unexpected bytes at end of flate stream") | |||||
} | |||||
err2 := w.tw.w.Close() | |||||
if err1 != nil { | |||||
return err1 | |||||
} | |||||
return err2 | |||||
} | |||||
type flateReadWrapper struct { | |||||
fr io.ReadCloser | |||||
} | |||||
func (r *flateReadWrapper) Read(p []byte) (int, error) { | |||||
if r.fr == nil { | |||||
return 0, io.ErrClosedPipe | |||||
} | |||||
n, err := r.fr.Read(p) | |||||
if err == io.EOF { | |||||
// Preemptively place the reader back in the pool. This helps with | |||||
// scenarios where the application does not call NextReader() soon after | |||||
// this final read. | |||||
r.Close() | |||||
} | |||||
return n, err | |||||
} | |||||
func (r *flateReadWrapper) Close() error { | |||||
if r.fr == nil { | |||||
return io.ErrClosedPipe | |||||
} | |||||
err := r.fr.Close() | |||||
flateReaderPool.Put(r.fr) | |||||
r.fr = nil | |||||
return err | |||||
} |
@@ -0,0 +1,15 @@ | |||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// +build go1.8 | |||||
package websocket | |||||
import "net" | |||||
func (c *Conn) writeBufs(bufs ...[]byte) error { | |||||
b := net.Buffers(bufs) | |||||
_, err := b.WriteTo(c.conn) | |||||
return err | |||||
} |
@@ -0,0 +1,18 @@ | |||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// +build !go1.8 | |||||
package websocket | |||||
func (c *Conn) writeBufs(bufs ...[]byte) error { | |||||
for _, buf := range bufs { | |||||
if len(buf) > 0 { | |||||
if _, err := c.conn.Write(buf); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
} | |||||
return nil | |||||
} |
@@ -0,0 +1,180 @@ | |||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// Package websocket implements the WebSocket protocol defined in RFC 6455. | |||||
// | |||||
// Overview | |||||
// | |||||
// The Conn type represents a WebSocket connection. A server application calls | |||||
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn: | |||||
// | |||||
// var upgrader = websocket.Upgrader{ | |||||
// ReadBufferSize: 1024, | |||||
// WriteBufferSize: 1024, | |||||
// } | |||||
// | |||||
// func handler(w http.ResponseWriter, r *http.Request) { | |||||
// conn, err := upgrader.Upgrade(w, r, nil) | |||||
// if err != nil { | |||||
// log.Println(err) | |||||
// return | |||||
// } | |||||
// ... Use conn to send and receive messages. | |||||
// } | |||||
// | |||||
// Call the connection's WriteMessage and ReadMessage methods to send and | |||||
// receive messages as a slice of bytes. This snippet of code shows how to echo | |||||
// messages using these methods: | |||||
// | |||||
// for { | |||||
// messageType, p, err := conn.ReadMessage() | |||||
// if err != nil { | |||||
// log.Println(err) | |||||
// return | |||||
// } | |||||
// if err := conn.WriteMessage(messageType, p); err != nil { | |||||
// log.Println(err) | |||||
// return | |||||
// } | |||||
// } | |||||
// | |||||
// In above snippet of code, p is a []byte and messageType is an int with value | |||||
// websocket.BinaryMessage or websocket.TextMessage. | |||||
// | |||||
// An application can also send and receive messages using the io.WriteCloser | |||||
// and io.Reader interfaces. To send a message, call the connection NextWriter | |||||
// method to get an io.WriteCloser, write the message to the writer and close | |||||
// the writer when done. To receive a message, call the connection NextReader | |||||
// method to get an io.Reader and read until io.EOF is returned. This snippet | |||||
// shows how to echo messages using the NextWriter and NextReader methods: | |||||
// | |||||
// for { | |||||
// messageType, r, err := conn.NextReader() | |||||
// if err != nil { | |||||
// return | |||||
// } | |||||
// w, err := conn.NextWriter(messageType) | |||||
// if err != nil { | |||||
// return err | |||||
// } | |||||
// if _, err := io.Copy(w, r); err != nil { | |||||
// return err | |||||
// } | |||||
// if err := w.Close(); err != nil { | |||||
// return err | |||||
// } | |||||
// } | |||||
// | |||||
// Data Messages | |||||
// | |||||
// The WebSocket protocol distinguishes between text and binary data messages. | |||||
// Text messages are interpreted as UTF-8 encoded text. The interpretation of | |||||
// binary messages is left to the application. | |||||
// | |||||
// This package uses the TextMessage and BinaryMessage integer constants to | |||||
// identify the two data message types. The ReadMessage and NextReader methods | |||||
// return the type of the received message. The messageType argument to the | |||||
// WriteMessage and NextWriter methods specifies the type of a sent message. | |||||
// | |||||
// It is the application's responsibility to ensure that text messages are | |||||
// valid UTF-8 encoded text. | |||||
// | |||||
// Control Messages | |||||
// | |||||
// The WebSocket protocol defines three types of control messages: close, ping | |||||
// and pong. Call the connection WriteControl, WriteMessage or NextWriter | |||||
// methods to send a control message to the peer. | |||||
// | |||||
// Connections handle received close messages by calling the handler function | |||||
// set with the SetCloseHandler method and by returning a *CloseError from the | |||||
// NextReader, ReadMessage or the message Read method. The default close | |||||
// handler sends a close message to the peer. | |||||
// | |||||
// Connections handle received ping messages by calling the handler function | |||||
// set with the SetPingHandler method. The default ping handler sends a pong | |||||
// message to the peer. | |||||
// | |||||
// Connections handle received pong messages by calling the handler function | |||||
// set with the SetPongHandler method. The default pong handler does nothing. | |||||
// If an application sends ping messages, then the application should set a | |||||
// pong handler to receive the corresponding pong. | |||||
// | |||||
// The control message handler functions are called from the NextReader, | |||||
// ReadMessage and message reader Read methods. The default close and ping | |||||
// handlers can block these methods for a short time when the handler writes to | |||||
// the connection. | |||||
// | |||||
// The application must read the connection to process close, ping and pong | |||||
// messages sent from the peer. If the application is not otherwise interested | |||||
// in messages from the peer, then the application should start a goroutine to | |||||
// read and discard messages from the peer. A simple example is: | |||||
// | |||||
// func readLoop(c *websocket.Conn) { | |||||
// for { | |||||
// if _, _, err := c.NextReader(); err != nil { | |||||
// c.Close() | |||||
// break | |||||
// } | |||||
// } | |||||
// } | |||||
// | |||||
// Concurrency | |||||
// | |||||
// Connections support one concurrent reader and one concurrent writer. | |||||
// | |||||
// Applications are responsible for ensuring that no more than one goroutine | |||||
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, | |||||
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and | |||||
// that no more than one goroutine calls the read methods (NextReader, | |||||
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) | |||||
// concurrently. | |||||
// | |||||
// The Close and WriteControl methods can be called concurrently with all other | |||||
// methods. | |||||
// | |||||
// Origin Considerations | |||||
// | |||||
// Web browsers allow Javascript applications to open a WebSocket connection to | |||||
// any host. It's up to the server to enforce an origin policy using the Origin | |||||
// request header sent by the browser. | |||||
// | |||||
// The Upgrader calls the function specified in the CheckOrigin field to check | |||||
// the origin. If the CheckOrigin function returns false, then the Upgrade | |||||
// method fails the WebSocket handshake with HTTP status 403. | |||||
// | |||||
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail | |||||
// the handshake if the Origin request header is present and the Origin host is | |||||
// not equal to the Host request header. | |||||
// | |||||
// The deprecated package-level Upgrade function does not perform origin | |||||
// checking. The application is responsible for checking the Origin header | |||||
// before calling the Upgrade function. | |||||
// | |||||
// Compression EXPERIMENTAL | |||||
// | |||||
// Per message compression extensions (RFC 7692) are experimentally supported | |||||
// by this package in a limited capacity. Setting the EnableCompression option | |||||
// to true in Dialer or Upgrader will attempt to negotiate per message deflate | |||||
// support. | |||||
// | |||||
// var upgrader = websocket.Upgrader{ | |||||
// EnableCompression: true, | |||||
// } | |||||
// | |||||
// If compression was successfully negotiated with the connection's peer, any | |||||
// message received in compressed form will be automatically decompressed. | |||||
// All Read methods will return uncompressed bytes. | |||||
// | |||||
// Per message compression of messages written to a connection can be enabled | |||||
// or disabled by calling the corresponding Conn method: | |||||
// | |||||
// conn.EnableWriteCompression(false) | |||||
// | |||||
// Currently this package does not support compression with "context takeover". | |||||
// This means that messages must be compressed and decompressed in isolation, | |||||
// without retaining sliding window or dictionary state across messages. For | |||||
// more details refer to RFC 7692. | |||||
// | |||||
// Use of compression is experimental and may result in decreased performance. | |||||
package websocket |
@@ -0,0 +1,60 @@ | |||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package websocket | |||||
import ( | |||||
"encoding/json" | |||||
"io" | |||||
) | |||||
// WriteJSON writes the JSON encoding of v as a message. | |||||
// | |||||
// Deprecated: Use c.WriteJSON instead. | |||||
func WriteJSON(c *Conn, v interface{}) error { | |||||
return c.WriteJSON(v) | |||||
} | |||||
// WriteJSON writes the JSON encoding of v as a message. | |||||
// | |||||
// See the documentation for encoding/json Marshal for details about the | |||||
// conversion of Go values to JSON. | |||||
func (c *Conn) WriteJSON(v interface{}) error { | |||||
w, err := c.NextWriter(TextMessage) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
err1 := json.NewEncoder(w).Encode(v) | |||||
err2 := w.Close() | |||||
if err1 != nil { | |||||
return err1 | |||||
} | |||||
return err2 | |||||
} | |||||
// ReadJSON reads the next JSON-encoded message from the connection and stores | |||||
// it in the value pointed to by v. | |||||
// | |||||
// Deprecated: Use c.ReadJSON instead. | |||||
func ReadJSON(c *Conn, v interface{}) error { | |||||
return c.ReadJSON(v) | |||||
} | |||||
// ReadJSON reads the next JSON-encoded message from the connection and stores | |||||
// it in the value pointed to by v. | |||||
// | |||||
// See the documentation for the encoding/json Unmarshal function for details | |||||
// about the conversion of JSON to a Go value. | |||||
func (c *Conn) ReadJSON(v interface{}) error { | |||||
_, r, err := c.NextReader() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
err = json.NewDecoder(r).Decode(v) | |||||
if err == io.EOF { | |||||
// One value is expected in the message. | |||||
err = io.ErrUnexpectedEOF | |||||
} | |||||
return err | |||||
} |
@@ -0,0 +1,54 @@ | |||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of | |||||
// this source code is governed by a BSD-style license that can be found in the | |||||
// LICENSE file. | |||||
// +build !appengine | |||||
package websocket | |||||
import "unsafe" | |||||
const wordSize = int(unsafe.Sizeof(uintptr(0))) | |||||
func maskBytes(key [4]byte, pos int, b []byte) int { | |||||
// Mask one byte at a time for small buffers. | |||||
if len(b) < 2*wordSize { | |||||
for i := range b { | |||||
b[i] ^= key[pos&3] | |||||
pos++ | |||||
} | |||||
return pos & 3 | |||||
} | |||||
// Mask one byte at a time to word boundary. | |||||
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 { | |||||
n = wordSize - n | |||||
for i := range b[:n] { | |||||
b[i] ^= key[pos&3] | |||||
pos++ | |||||
} | |||||
b = b[n:] | |||||
} | |||||
// Create aligned word size key. | |||||
var k [wordSize]byte | |||||
for i := range k { | |||||
k[i] = key[(pos+i)&3] | |||||
} | |||||
kw := *(*uintptr)(unsafe.Pointer(&k)) | |||||
// Mask one word at a time. | |||||
n := (len(b) / wordSize) * wordSize | |||||
for i := 0; i < n; i += wordSize { | |||||
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw | |||||
} | |||||
// Mask one byte at a time for remaining bytes. | |||||
b = b[n:] | |||||
for i := range b { | |||||
b[i] ^= key[pos&3] | |||||
pos++ | |||||
} | |||||
return pos & 3 | |||||
} |
@@ -0,0 +1,15 @@ | |||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of | |||||
// this source code is governed by a BSD-style license that can be found in the | |||||
// LICENSE file. | |||||
// +build appengine | |||||
package websocket | |||||
func maskBytes(key [4]byte, pos int, b []byte) int { | |||||
for i := range b { | |||||
b[i] ^= key[pos&3] | |||||
pos++ | |||||
} | |||||
return pos & 3 | |||||
} |
@@ -0,0 +1,102 @@ | |||||
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package websocket | |||||
import ( | |||||
"bytes" | |||||
"net" | |||||
"sync" | |||||
"time" | |||||
) | |||||
// PreparedMessage caches on the wire representations of a message payload. | |||||
// Use PreparedMessage to efficiently send a message payload to multiple | |||||
// connections. PreparedMessage is especially useful when compression is used | |||||
// because the CPU and memory expensive compression operation can be executed | |||||
// once for a given set of compression options. | |||||
type PreparedMessage struct { | |||||
messageType int | |||||
data []byte | |||||
mu sync.Mutex | |||||
frames map[prepareKey]*preparedFrame | |||||
} | |||||
// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage. | |||||
type prepareKey struct { | |||||
isServer bool | |||||
compress bool | |||||
compressionLevel int | |||||
} | |||||
// preparedFrame contains data in wire representation. | |||||
type preparedFrame struct { | |||||
once sync.Once | |||||
data []byte | |||||
} | |||||
// NewPreparedMessage returns an initialized PreparedMessage. You can then send | |||||
// it to connection using WritePreparedMessage method. Valid wire | |||||
// representation will be calculated lazily only once for a set of current | |||||
// connection options. | |||||
func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) { | |||||
pm := &PreparedMessage{ | |||||
messageType: messageType, | |||||
frames: make(map[prepareKey]*preparedFrame), | |||||
data: data, | |||||
} | |||||
// Prepare a plain server frame. | |||||
_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false}) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
// To protect against caller modifying the data argument, remember the data | |||||
// copied to the plain server frame. | |||||
pm.data = frameData[len(frameData)-len(data):] | |||||
return pm, nil | |||||
} | |||||
func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) { | |||||
pm.mu.Lock() | |||||
frame, ok := pm.frames[key] | |||||
if !ok { | |||||
frame = &preparedFrame{} | |||||
pm.frames[key] = frame | |||||
} | |||||
pm.mu.Unlock() | |||||
var err error | |||||
frame.once.Do(func() { | |||||
// Prepare a frame using a 'fake' connection. | |||||
// TODO: Refactor code in conn.go to allow more direct construction of | |||||
// the frame. | |||||
mu := make(chan bool, 1) | |||||
mu <- true | |||||
var nc prepareConn | |||||
c := &Conn{ | |||||
conn: &nc, | |||||
mu: mu, | |||||
isServer: key.isServer, | |||||
compressionLevel: key.compressionLevel, | |||||
enableWriteCompression: true, | |||||
writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize), | |||||
} | |||||
if key.compress { | |||||
c.newCompressionWriter = compressNoContextTakeover | |||||
} | |||||
err = c.WriteMessage(pm.messageType, pm.data) | |||||
frame.data = nc.buf.Bytes() | |||||
}) | |||||
return pm.messageType, frame.data, err | |||||
} | |||||
type prepareConn struct { | |||||
buf bytes.Buffer | |||||
net.Conn | |||||
} | |||||
func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) } | |||||
func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil } |
@@ -0,0 +1,77 @@ | |||||
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package websocket | |||||
import ( | |||||
"bufio" | |||||
"encoding/base64" | |||||
"errors" | |||||
"net" | |||||
"net/http" | |||||
"net/url" | |||||
"strings" | |||||
) | |||||
type netDialerFunc func(network, addr string) (net.Conn, error) | |||||
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) { | |||||
return fn(network, addr) | |||||
} | |||||
func init() { | |||||
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) { | |||||
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil | |||||
}) | |||||
} | |||||
type httpProxyDialer struct { | |||||
proxyURL *url.URL | |||||
fowardDial func(network, addr string) (net.Conn, error) | |||||
} | |||||
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) { | |||||
hostPort, _ := hostPortNoPort(hpd.proxyURL) | |||||
conn, err := hpd.fowardDial(network, hostPort) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
connectHeader := make(http.Header) | |||||
if user := hpd.proxyURL.User; user != nil { | |||||
proxyUser := user.Username() | |||||
if proxyPassword, passwordSet := user.Password(); passwordSet { | |||||
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword)) | |||||
connectHeader.Set("Proxy-Authorization", "Basic "+credential) | |||||
} | |||||
} | |||||
connectReq := &http.Request{ | |||||
Method: "CONNECT", | |||||
URL: &url.URL{Opaque: addr}, | |||||
Host: addr, | |||||
Header: connectHeader, | |||||
} | |||||
if err := connectReq.Write(conn); err != nil { | |||||
conn.Close() | |||||
return nil, err | |||||
} | |||||
// Read response. It's OK to use and discard buffered reader here becaue | |||||
// the remote server does not speak until spoken to. | |||||
br := bufio.NewReader(conn) | |||||
resp, err := http.ReadResponse(br, connectReq) | |||||
if err != nil { | |||||
conn.Close() | |||||
return nil, err | |||||
} | |||||
if resp.StatusCode != 200 { | |||||
conn.Close() | |||||
f := strings.SplitN(resp.Status, " ", 2) | |||||
return nil, errors.New(f[1]) | |||||
} | |||||
return conn, nil | |||||
} |
@@ -0,0 +1,363 @@ | |||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package websocket | |||||
import ( | |||||
"bufio" | |||||
"errors" | |||||
"io" | |||||
"net/http" | |||||
"net/url" | |||||
"strings" | |||||
"time" | |||||
) | |||||
// HandshakeError describes an error with the handshake from the peer. | |||||
type HandshakeError struct { | |||||
message string | |||||
} | |||||
func (e HandshakeError) Error() string { return e.message } | |||||
// Upgrader specifies parameters for upgrading an HTTP connection to a | |||||
// WebSocket connection. | |||||
type Upgrader struct { | |||||
// HandshakeTimeout specifies the duration for the handshake to complete. | |||||
HandshakeTimeout time.Duration | |||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer | |||||
// size is zero, then buffers allocated by the HTTP server are used. The | |||||
// I/O buffer sizes do not limit the size of the messages that can be sent | |||||
// or received. | |||||
ReadBufferSize, WriteBufferSize int | |||||
// WriteBufferPool is a pool of buffers for write operations. If the value | |||||
// is not set, then write buffers are allocated to the connection for the | |||||
// lifetime of the connection. | |||||
// | |||||
// A pool is most useful when the application has a modest volume of writes | |||||
// across a large number of connections. | |||||
// | |||||
// Applications should use a single pool for each unique value of | |||||
// WriteBufferSize. | |||||
WriteBufferPool BufferPool | |||||
// Subprotocols specifies the server's supported protocols in order of | |||||
// preference. If this field is not nil, then the Upgrade method negotiates a | |||||
// subprotocol by selecting the first match in this list with a protocol | |||||
// requested by the client. If there's no match, then no protocol is | |||||
// negotiated (the Sec-Websocket-Protocol header is not included in the | |||||
// handshake response). | |||||
Subprotocols []string | |||||
// Error specifies the function for generating HTTP error responses. If Error | |||||
// is nil, then http.Error is used to generate the HTTP response. | |||||
Error func(w http.ResponseWriter, r *http.Request, status int, reason error) | |||||
// CheckOrigin returns true if the request Origin header is acceptable. If | |||||
// CheckOrigin is nil, then a safe default is used: return false if the | |||||
// Origin request header is present and the origin host is not equal to | |||||
// request Host header. | |||||
// | |||||
// A CheckOrigin function should carefully validate the request origin to | |||||
// prevent cross-site request forgery. | |||||
CheckOrigin func(r *http.Request) bool | |||||
// EnableCompression specify if the server should attempt to negotiate per | |||||
// message compression (RFC 7692). Setting this value to true does not | |||||
// guarantee that compression will be supported. Currently only "no context | |||||
// takeover" modes are supported. | |||||
EnableCompression bool | |||||
} | |||||
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { | |||||
err := HandshakeError{reason} | |||||
if u.Error != nil { | |||||
u.Error(w, r, status, err) | |||||
} else { | |||||
w.Header().Set("Sec-Websocket-Version", "13") | |||||
http.Error(w, http.StatusText(status), status) | |||||
} | |||||
return nil, err | |||||
} | |||||
// checkSameOrigin returns true if the origin is not set or is equal to the request host. | |||||
func checkSameOrigin(r *http.Request) bool { | |||||
origin := r.Header["Origin"] | |||||
if len(origin) == 0 { | |||||
return true | |||||
} | |||||
u, err := url.Parse(origin[0]) | |||||
if err != nil { | |||||
return false | |||||
} | |||||
return equalASCIIFold(u.Host, r.Host) | |||||
} | |||||
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { | |||||
if u.Subprotocols != nil { | |||||
clientProtocols := Subprotocols(r) | |||||
for _, serverProtocol := range u.Subprotocols { | |||||
for _, clientProtocol := range clientProtocols { | |||||
if clientProtocol == serverProtocol { | |||||
return clientProtocol | |||||
} | |||||
} | |||||
} | |||||
} else if responseHeader != nil { | |||||
return responseHeader.Get("Sec-Websocket-Protocol") | |||||
} | |||||
return "" | |||||
} | |||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol. | |||||
// | |||||
// The responseHeader is included in the response to the client's upgrade | |||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the | |||||
// application negotiated subprotocol (Sec-WebSocket-Protocol). | |||||
// | |||||
// If the upgrade fails, then Upgrade replies to the client with an HTTP error | |||||
// response. | |||||
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { | |||||
const badHandshake = "websocket: the client is not using the websocket protocol: " | |||||
if !tokenListContainsValue(r.Header, "Connection", "upgrade") { | |||||
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header") | |||||
} | |||||
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { | |||||
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header") | |||||
} | |||||
if r.Method != "GET" { | |||||
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET") | |||||
} | |||||
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { | |||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") | |||||
} | |||||
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { | |||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported") | |||||
} | |||||
checkOrigin := u.CheckOrigin | |||||
if checkOrigin == nil { | |||||
checkOrigin = checkSameOrigin | |||||
} | |||||
if !checkOrigin(r) { | |||||
return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin") | |||||
} | |||||
challengeKey := r.Header.Get("Sec-Websocket-Key") | |||||
if challengeKey == "" { | |||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank") | |||||
} | |||||
subprotocol := u.selectSubprotocol(r, responseHeader) | |||||
// Negotiate PMCE | |||||
var compress bool | |||||
if u.EnableCompression { | |||||
for _, ext := range parseExtensions(r.Header) { | |||||
if ext[""] != "permessage-deflate" { | |||||
continue | |||||
} | |||||
compress = true | |||||
break | |||||
} | |||||
} | |||||
h, ok := w.(http.Hijacker) | |||||
if !ok { | |||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") | |||||
} | |||||
var brw *bufio.ReadWriter | |||||
netConn, brw, err := h.Hijack() | |||||
if err != nil { | |||||
return u.returnError(w, r, http.StatusInternalServerError, err.Error()) | |||||
} | |||||
if brw.Reader.Buffered() > 0 { | |||||
netConn.Close() | |||||
return nil, errors.New("websocket: client sent data before handshake is complete") | |||||
} | |||||
var br *bufio.Reader | |||||
if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 { | |||||
// Reuse hijacked buffered reader as connection reader. | |||||
br = brw.Reader | |||||
} | |||||
buf := bufioWriterBuffer(netConn, brw.Writer) | |||||
var writeBuf []byte | |||||
if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 { | |||||
// Reuse hijacked write buffer as connection buffer. | |||||
writeBuf = buf | |||||
} | |||||
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf) | |||||
c.subprotocol = subprotocol | |||||
if compress { | |||||
c.newCompressionWriter = compressNoContextTakeover | |||||
c.newDecompressionReader = decompressNoContextTakeover | |||||
} | |||||
// Use larger of hijacked buffer and connection write buffer for header. | |||||
p := buf | |||||
if len(c.writeBuf) > len(p) { | |||||
p = c.writeBuf | |||||
} | |||||
p = p[:0] | |||||
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) | |||||
p = append(p, computeAcceptKey(challengeKey)...) | |||||
p = append(p, "\r\n"...) | |||||
if c.subprotocol != "" { | |||||
p = append(p, "Sec-WebSocket-Protocol: "...) | |||||
p = append(p, c.subprotocol...) | |||||
p = append(p, "\r\n"...) | |||||
} | |||||
if compress { | |||||
p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) | |||||
} | |||||
for k, vs := range responseHeader { | |||||
if k == "Sec-Websocket-Protocol" { | |||||
continue | |||||
} | |||||
for _, v := range vs { | |||||
p = append(p, k...) | |||||
p = append(p, ": "...) | |||||
for i := 0; i < len(v); i++ { | |||||
b := v[i] | |||||
if b <= 31 { | |||||
// prevent response splitting. | |||||
b = ' ' | |||||
} | |||||
p = append(p, b) | |||||
} | |||||
p = append(p, "\r\n"...) | |||||
} | |||||
} | |||||
p = append(p, "\r\n"...) | |||||
// Clear deadlines set by HTTP server. | |||||
netConn.SetDeadline(time.Time{}) | |||||
if u.HandshakeTimeout > 0 { | |||||
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) | |||||
} | |||||
if _, err = netConn.Write(p); err != nil { | |||||
netConn.Close() | |||||
return nil, err | |||||
} | |||||
if u.HandshakeTimeout > 0 { | |||||
netConn.SetWriteDeadline(time.Time{}) | |||||
} | |||||
return c, nil | |||||
} | |||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol. | |||||
// | |||||
// Deprecated: Use websocket.Upgrader instead. | |||||
// | |||||
// Upgrade does not perform origin checking. The application is responsible for | |||||
// checking the Origin header before calling Upgrade. An example implementation | |||||
// of the same origin policy check is: | |||||
// | |||||
// if req.Header.Get("Origin") != "http://"+req.Host { | |||||
// http.Error(w, "Origin not allowed", http.StatusForbidden) | |||||
// return | |||||
// } | |||||
// | |||||
// If the endpoint supports subprotocols, then the application is responsible | |||||
// for negotiating the protocol used on the connection. Use the Subprotocols() | |||||
// function to get the subprotocols requested by the client. Use the | |||||
// Sec-Websocket-Protocol response header to specify the subprotocol selected | |||||
// by the application. | |||||
// | |||||
// The responseHeader is included in the response to the client's upgrade | |||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the | |||||
// negotiated subprotocol (Sec-Websocket-Protocol). | |||||
// | |||||
// The connection buffers IO to the underlying network connection. The | |||||
// readBufSize and writeBufSize parameters specify the size of the buffers to | |||||
// use. Messages can be larger than the buffers. | |||||
// | |||||
// If the request is not a valid WebSocket handshake, then Upgrade returns an | |||||
// error of type HandshakeError. Applications should handle this error by | |||||
// replying to the client with an HTTP error response. | |||||
func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { | |||||
u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} | |||||
u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { | |||||
// don't return errors to maintain backwards compatibility | |||||
} | |||||
u.CheckOrigin = func(r *http.Request) bool { | |||||
// allow all connections by default | |||||
return true | |||||
} | |||||
return u.Upgrade(w, r, responseHeader) | |||||
} | |||||
// Subprotocols returns the subprotocols requested by the client in the | |||||
// Sec-Websocket-Protocol header. | |||||
func Subprotocols(r *http.Request) []string { | |||||
h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) | |||||
if h == "" { | |||||
return nil | |||||
} | |||||
protocols := strings.Split(h, ",") | |||||
for i := range protocols { | |||||
protocols[i] = strings.TrimSpace(protocols[i]) | |||||
} | |||||
return protocols | |||||
} | |||||
// IsWebSocketUpgrade returns true if the client requested upgrade to the | |||||
// WebSocket protocol. | |||||
func IsWebSocketUpgrade(r *http.Request) bool { | |||||
return tokenListContainsValue(r.Header, "Connection", "upgrade") && | |||||
tokenListContainsValue(r.Header, "Upgrade", "websocket") | |||||
} | |||||
// bufioReaderSize size returns the size of a bufio.Reader. | |||||
func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int { | |||||
// This code assumes that peek on a reset reader returns | |||||
// bufio.Reader.buf[:0]. | |||||
// TODO: Use bufio.Reader.Size() after Go 1.10 | |||||
br.Reset(originalReader) | |||||
if p, err := br.Peek(0); err == nil { | |||||
return cap(p) | |||||
} | |||||
return 0 | |||||
} | |||||
// writeHook is an io.Writer that records the last slice passed to it vio | |||||
// io.Writer.Write. | |||||
type writeHook struct { | |||||
p []byte | |||||
} | |||||
func (wh *writeHook) Write(p []byte) (int, error) { | |||||
wh.p = p | |||||
return len(p), nil | |||||
} | |||||
// bufioWriterBuffer grabs the buffer from a bufio.Writer. | |||||
func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte { | |||||
// This code assumes that bufio.Writer.buf[:1] is passed to the | |||||
// bufio.Writer's underlying writer. | |||||
var wh writeHook | |||||
bw.Reset(&wh) | |||||
bw.WriteByte(0) | |||||
bw.Flush() | |||||
bw.Reset(originalWriter) | |||||
return wh.p[:cap(wh.p)] | |||||
} |
@@ -0,0 +1,19 @@ | |||||
// +build go1.8 | |||||
package websocket | |||||
import ( | |||||
"crypto/tls" | |||||
"net/http/httptrace" | |||||
) | |||||
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error { | |||||
if trace.TLSHandshakeStart != nil { | |||||
trace.TLSHandshakeStart() | |||||
} | |||||
err := doHandshake(tlsConn, cfg) | |||||
if trace.TLSHandshakeDone != nil { | |||||
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err) | |||||
} | |||||
return err | |||||
} |
@@ -0,0 +1,12 @@ | |||||
// +build !go1.8 | |||||
package websocket | |||||
import ( | |||||
"crypto/tls" | |||||
"net/http/httptrace" | |||||
) | |||||
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error { | |||||
return doHandshake(tlsConn, cfg) | |||||
} |
@@ -0,0 +1,237 @@ | |||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package websocket | |||||
import ( | |||||
"crypto/rand" | |||||
"crypto/sha1" | |||||
"encoding/base64" | |||||
"io" | |||||
"net/http" | |||||
"strings" | |||||
"unicode/utf8" | |||||
) | |||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") | |||||
func computeAcceptKey(challengeKey string) string { | |||||
h := sha1.New() | |||||
h.Write([]byte(challengeKey)) | |||||
h.Write(keyGUID) | |||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)) | |||||
} | |||||
func generateChallengeKey() (string, error) { | |||||
p := make([]byte, 16) | |||||
if _, err := io.ReadFull(rand.Reader, p); err != nil { | |||||
return "", err | |||||
} | |||||
return base64.StdEncoding.EncodeToString(p), nil | |||||
} | |||||
// Octet types from RFC 2616. | |||||
var octetTypes [256]byte | |||||
const ( | |||||
isTokenOctet = 1 << iota | |||||
isSpaceOctet | |||||
) | |||||
func init() { | |||||
// From RFC 2616 | |||||
// | |||||
// OCTET = <any 8-bit sequence of data> | |||||
// CHAR = <any US-ASCII character (octets 0 - 127)> | |||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> | |||||
// CR = <US-ASCII CR, carriage return (13)> | |||||
// LF = <US-ASCII LF, linefeed (10)> | |||||
// SP = <US-ASCII SP, space (32)> | |||||
// HT = <US-ASCII HT, horizontal-tab (9)> | |||||
// <"> = <US-ASCII double-quote mark (34)> | |||||
// CRLF = CR LF | |||||
// LWS = [CRLF] 1*( SP | HT ) | |||||
// TEXT = <any OCTET except CTLs, but including LWS> | |||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | |||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT | |||||
// token = 1*<any CHAR except CTLs or separators> | |||||
// qdtext = <any TEXT except <">> | |||||
for c := 0; c < 256; c++ { | |||||
var t byte | |||||
isCtl := c <= 31 || c == 127 | |||||
isChar := 0 <= c && c <= 127 | |||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 | |||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { | |||||
t |= isSpaceOctet | |||||
} | |||||
if isChar && !isCtl && !isSeparator { | |||||
t |= isTokenOctet | |||||
} | |||||
octetTypes[c] = t | |||||
} | |||||
} | |||||
func skipSpace(s string) (rest string) { | |||||
i := 0 | |||||
for ; i < len(s); i++ { | |||||
if octetTypes[s[i]]&isSpaceOctet == 0 { | |||||
break | |||||
} | |||||
} | |||||
return s[i:] | |||||
} | |||||
func nextToken(s string) (token, rest string) { | |||||
i := 0 | |||||
for ; i < len(s); i++ { | |||||
if octetTypes[s[i]]&isTokenOctet == 0 { | |||||
break | |||||
} | |||||
} | |||||
return s[:i], s[i:] | |||||
} | |||||
func nextTokenOrQuoted(s string) (value string, rest string) { | |||||
if !strings.HasPrefix(s, "\"") { | |||||
return nextToken(s) | |||||
} | |||||
s = s[1:] | |||||
for i := 0; i < len(s); i++ { | |||||
switch s[i] { | |||||
case '"': | |||||
return s[:i], s[i+1:] | |||||
case '\\': | |||||
p := make([]byte, len(s)-1) | |||||
j := copy(p, s[:i]) | |||||
escape := true | |||||
for i = i + 1; i < len(s); i++ { | |||||
b := s[i] | |||||
switch { | |||||
case escape: | |||||
escape = false | |||||
p[j] = b | |||||
j++ | |||||
case b == '\\': | |||||
escape = true | |||||
case b == '"': | |||||
return string(p[:j]), s[i+1:] | |||||
default: | |||||
p[j] = b | |||||
j++ | |||||
} | |||||
} | |||||
return "", "" | |||||
} | |||||
} | |||||
return "", "" | |||||
} | |||||
// equalASCIIFold returns true if s is equal to t with ASCII case folding. | |||||
func equalASCIIFold(s, t string) bool { | |||||
for s != "" && t != "" { | |||||
sr, size := utf8.DecodeRuneInString(s) | |||||
s = s[size:] | |||||
tr, size := utf8.DecodeRuneInString(t) | |||||
t = t[size:] | |||||
if sr == tr { | |||||
continue | |||||
} | |||||
if 'A' <= sr && sr <= 'Z' { | |||||
sr = sr + 'a' - 'A' | |||||
} | |||||
if 'A' <= tr && tr <= 'Z' { | |||||
tr = tr + 'a' - 'A' | |||||
} | |||||
if sr != tr { | |||||
return false | |||||
} | |||||
} | |||||
return s == t | |||||
} | |||||
// tokenListContainsValue returns true if the 1#token header with the given | |||||
// name contains a token equal to value with ASCII case folding. | |||||
func tokenListContainsValue(header http.Header, name string, value string) bool { | |||||
headers: | |||||
for _, s := range header[name] { | |||||
for { | |||||
var t string | |||||
t, s = nextToken(skipSpace(s)) | |||||
if t == "" { | |||||
continue headers | |||||
} | |||||
s = skipSpace(s) | |||||
if s != "" && s[0] != ',' { | |||||
continue headers | |||||
} | |||||
if equalASCIIFold(t, value) { | |||||
return true | |||||
} | |||||
if s == "" { | |||||
continue headers | |||||
} | |||||
s = s[1:] | |||||
} | |||||
} | |||||
return false | |||||
} | |||||
// parseExtensions parses WebSocket extensions from a header. | |||||
func parseExtensions(header http.Header) []map[string]string { | |||||
// From RFC 6455: | |||||
// | |||||
// Sec-WebSocket-Extensions = extension-list | |||||
// extension-list = 1#extension | |||||
// extension = extension-token *( ";" extension-param ) | |||||
// extension-token = registered-token | |||||
// registered-token = token | |||||
// extension-param = token [ "=" (token | quoted-string) ] | |||||
// ;When using the quoted-string syntax variant, the value | |||||
// ;after quoted-string unescaping MUST conform to the | |||||
// ;'token' ABNF. | |||||
var result []map[string]string | |||||
headers: | |||||
for _, s := range header["Sec-Websocket-Extensions"] { | |||||
for { | |||||
var t string | |||||
t, s = nextToken(skipSpace(s)) | |||||
if t == "" { | |||||
continue headers | |||||
} | |||||
ext := map[string]string{"": t} | |||||
for { | |||||
s = skipSpace(s) | |||||
if !strings.HasPrefix(s, ";") { | |||||
break | |||||
} | |||||
var k string | |||||
k, s = nextToken(skipSpace(s[1:])) | |||||
if k == "" { | |||||
continue headers | |||||
} | |||||
s = skipSpace(s) | |||||
var v string | |||||
if strings.HasPrefix(s, "=") { | |||||
v, s = nextTokenOrQuoted(skipSpace(s[1:])) | |||||
s = skipSpace(s) | |||||
} | |||||
if s != "" && s[0] != ',' && s[0] != ';' { | |||||
continue headers | |||||
} | |||||
ext[k] = v | |||||
} | |||||
if s != "" && s[0] != ',' { | |||||
continue headers | |||||
} | |||||
result = append(result, ext) | |||||
if s == "" { | |||||
continue headers | |||||
} | |||||
s = s[1:] | |||||
} | |||||
} | |||||
return result | |||||
} |
@@ -0,0 +1,473 @@ | |||||
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT. | |||||
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy | |||||
// Package proxy provides support for a variety of protocols to proxy network | |||||
// data. | |||||
// | |||||
package websocket | |||||
import ( | |||||
"errors" | |||||
"io" | |||||
"net" | |||||
"net/url" | |||||
"os" | |||||
"strconv" | |||||
"strings" | |||||
"sync" | |||||
) | |||||
type proxy_direct struct{} | |||||
// Direct is a direct proxy: one that makes network connections directly. | |||||
var proxy_Direct = proxy_direct{} | |||||
func (proxy_direct) Dial(network, addr string) (net.Conn, error) { | |||||
return net.Dial(network, addr) | |||||
} | |||||
// A PerHost directs connections to a default Dialer unless the host name | |||||
// requested matches one of a number of exceptions. | |||||
type proxy_PerHost struct { | |||||
def, bypass proxy_Dialer | |||||
bypassNetworks []*net.IPNet | |||||
bypassIPs []net.IP | |||||
bypassZones []string | |||||
bypassHosts []string | |||||
} | |||||
// NewPerHost returns a PerHost Dialer that directs connections to either | |||||
// defaultDialer or bypass, depending on whether the connection matches one of | |||||
// the configured rules. | |||||
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost { | |||||
return &proxy_PerHost{ | |||||
def: defaultDialer, | |||||
bypass: bypass, | |||||
} | |||||
} | |||||
// Dial connects to the address addr on the given network through either | |||||
// defaultDialer or bypass. | |||||
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) { | |||||
host, _, err := net.SplitHostPort(addr) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return p.dialerForRequest(host).Dial(network, addr) | |||||
} | |||||
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer { | |||||
if ip := net.ParseIP(host); ip != nil { | |||||
for _, net := range p.bypassNetworks { | |||||
if net.Contains(ip) { | |||||
return p.bypass | |||||
} | |||||
} | |||||
for _, bypassIP := range p.bypassIPs { | |||||
if bypassIP.Equal(ip) { | |||||
return p.bypass | |||||
} | |||||
} | |||||
return p.def | |||||
} | |||||
for _, zone := range p.bypassZones { | |||||
if strings.HasSuffix(host, zone) { | |||||
return p.bypass | |||||
} | |||||
if host == zone[1:] { | |||||
// For a zone ".example.com", we match "example.com" | |||||
// too. | |||||
return p.bypass | |||||
} | |||||
} | |||||
for _, bypassHost := range p.bypassHosts { | |||||
if bypassHost == host { | |||||
return p.bypass | |||||
} | |||||
} | |||||
return p.def | |||||
} | |||||
// AddFromString parses a string that contains comma-separated values | |||||
// specifying hosts that should use the bypass proxy. Each value is either an | |||||
// IP address, a CIDR range, a zone (*.example.com) or a host name | |||||
// (localhost). A best effort is made to parse the string and errors are | |||||
// ignored. | |||||
func (p *proxy_PerHost) AddFromString(s string) { | |||||
hosts := strings.Split(s, ",") | |||||
for _, host := range hosts { | |||||
host = strings.TrimSpace(host) | |||||
if len(host) == 0 { | |||||
continue | |||||
} | |||||
if strings.Contains(host, "/") { | |||||
// We assume that it's a CIDR address like 127.0.0.0/8 | |||||
if _, net, err := net.ParseCIDR(host); err == nil { | |||||
p.AddNetwork(net) | |||||
} | |||||
continue | |||||
} | |||||
if ip := net.ParseIP(host); ip != nil { | |||||
p.AddIP(ip) | |||||
continue | |||||
} | |||||
if strings.HasPrefix(host, "*.") { | |||||
p.AddZone(host[1:]) | |||||
continue | |||||
} | |||||
p.AddHost(host) | |||||
} | |||||
} | |||||
// AddIP specifies an IP address that will use the bypass proxy. Note that | |||||
// this will only take effect if a literal IP address is dialed. A connection | |||||
// to a named host will never match an IP. | |||||
func (p *proxy_PerHost) AddIP(ip net.IP) { | |||||
p.bypassIPs = append(p.bypassIPs, ip) | |||||
} | |||||
// AddNetwork specifies an IP range that will use the bypass proxy. Note that | |||||
// this will only take effect if a literal IP address is dialed. A connection | |||||
// to a named host will never match. | |||||
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) { | |||||
p.bypassNetworks = append(p.bypassNetworks, net) | |||||
} | |||||
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of | |||||
// "example.com" matches "example.com" and all of its subdomains. | |||||
func (p *proxy_PerHost) AddZone(zone string) { | |||||
if strings.HasSuffix(zone, ".") { | |||||
zone = zone[:len(zone)-1] | |||||
} | |||||
if !strings.HasPrefix(zone, ".") { | |||||
zone = "." + zone | |||||
} | |||||
p.bypassZones = append(p.bypassZones, zone) | |||||
} | |||||
// AddHost specifies a host name that will use the bypass proxy. | |||||
func (p *proxy_PerHost) AddHost(host string) { | |||||
if strings.HasSuffix(host, ".") { | |||||
host = host[:len(host)-1] | |||||
} | |||||
p.bypassHosts = append(p.bypassHosts, host) | |||||
} | |||||
// A Dialer is a means to establish a connection. | |||||
type proxy_Dialer interface { | |||||
// Dial connects to the given address via the proxy. | |||||
Dial(network, addr string) (c net.Conn, err error) | |||||
} | |||||
// Auth contains authentication parameters that specific Dialers may require. | |||||
type proxy_Auth struct { | |||||
User, Password string | |||||
} | |||||
// FromEnvironment returns the dialer specified by the proxy related variables in | |||||
// the environment. | |||||
func proxy_FromEnvironment() proxy_Dialer { | |||||
allProxy := proxy_allProxyEnv.Get() | |||||
if len(allProxy) == 0 { | |||||
return proxy_Direct | |||||
} | |||||
proxyURL, err := url.Parse(allProxy) | |||||
if err != nil { | |||||
return proxy_Direct | |||||
} | |||||
proxy, err := proxy_FromURL(proxyURL, proxy_Direct) | |||||
if err != nil { | |||||
return proxy_Direct | |||||
} | |||||
noProxy := proxy_noProxyEnv.Get() | |||||
if len(noProxy) == 0 { | |||||
return proxy | |||||
} | |||||
perHost := proxy_NewPerHost(proxy, proxy_Direct) | |||||
perHost.AddFromString(noProxy) | |||||
return perHost | |||||
} | |||||
// proxySchemes is a map from URL schemes to a function that creates a Dialer | |||||
// from a URL with such a scheme. | |||||
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error) | |||||
// RegisterDialerType takes a URL scheme and a function to generate Dialers from | |||||
// a URL with that scheme and a forwarding Dialer. Registered schemes are used | |||||
// by FromURL. | |||||
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) { | |||||
if proxy_proxySchemes == nil { | |||||
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) | |||||
} | |||||
proxy_proxySchemes[scheme] = f | |||||
} | |||||
// FromURL returns a Dialer given a URL specification and an underlying | |||||
// Dialer for it to make network requests. | |||||
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) { | |||||
var auth *proxy_Auth | |||||
if u.User != nil { | |||||
auth = new(proxy_Auth) | |||||
auth.User = u.User.Username() | |||||
if p, ok := u.User.Password(); ok { | |||||
auth.Password = p | |||||
} | |||||
} | |||||
switch u.Scheme { | |||||
case "socks5": | |||||
return proxy_SOCKS5("tcp", u.Host, auth, forward) | |||||
} | |||||
// If the scheme doesn't match any of the built-in schemes, see if it | |||||
// was registered by another package. | |||||
if proxy_proxySchemes != nil { | |||||
if f, ok := proxy_proxySchemes[u.Scheme]; ok { | |||||
return f(u, forward) | |||||
} | |||||
} | |||||
return nil, errors.New("proxy: unknown scheme: " + u.Scheme) | |||||
} | |||||
var ( | |||||
proxy_allProxyEnv = &proxy_envOnce{ | |||||
names: []string{"ALL_PROXY", "all_proxy"}, | |||||
} | |||||
proxy_noProxyEnv = &proxy_envOnce{ | |||||
names: []string{"NO_PROXY", "no_proxy"}, | |||||
} | |||||
) | |||||
// envOnce looks up an environment variable (optionally by multiple | |||||
// names) once. It mitigates expensive lookups on some platforms | |||||
// (e.g. Windows). | |||||
// (Borrowed from net/http/transport.go) | |||||
type proxy_envOnce struct { | |||||
names []string | |||||
once sync.Once | |||||
val string | |||||
} | |||||
func (e *proxy_envOnce) Get() string { | |||||
e.once.Do(e.init) | |||||
return e.val | |||||
} | |||||
func (e *proxy_envOnce) init() { | |||||
for _, n := range e.names { | |||||
e.val = os.Getenv(n) | |||||
if e.val != "" { | |||||
return | |||||
} | |||||
} | |||||
} | |||||
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address | |||||
// with an optional username and password. See RFC 1928 and RFC 1929. | |||||
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) { | |||||
s := &proxy_socks5{ | |||||
network: network, | |||||
addr: addr, | |||||
forward: forward, | |||||
} | |||||
if auth != nil { | |||||
s.user = auth.User | |||||
s.password = auth.Password | |||||
} | |||||
return s, nil | |||||
} | |||||
type proxy_socks5 struct { | |||||
user, password string | |||||
network, addr string | |||||
forward proxy_Dialer | |||||
} | |||||
const proxy_socks5Version = 5 | |||||
const ( | |||||
proxy_socks5AuthNone = 0 | |||||
proxy_socks5AuthPassword = 2 | |||||
) | |||||
const proxy_socks5Connect = 1 | |||||
const ( | |||||
proxy_socks5IP4 = 1 | |||||
proxy_socks5Domain = 3 | |||||
proxy_socks5IP6 = 4 | |||||
) | |||||
var proxy_socks5Errors = []string{ | |||||
"", | |||||
"general failure", | |||||
"connection forbidden", | |||||
"network unreachable", | |||||
"host unreachable", | |||||
"connection refused", | |||||
"TTL expired", | |||||
"command not supported", | |||||
"address type not supported", | |||||
} | |||||
// Dial connects to the address addr on the given network via the SOCKS5 proxy. | |||||
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) { | |||||
switch network { | |||||
case "tcp", "tcp6", "tcp4": | |||||
default: | |||||
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network) | |||||
} | |||||
conn, err := s.forward.Dial(s.network, s.addr) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if err := s.connect(conn, addr); err != nil { | |||||
conn.Close() | |||||
return nil, err | |||||
} | |||||
return conn, nil | |||||
} | |||||
// connect takes an existing connection to a socks5 proxy server, | |||||
// and commands the server to extend that connection to target, | |||||
// which must be a canonical address with a host and port. | |||||
func (s *proxy_socks5) connect(conn net.Conn, target string) error { | |||||
host, portStr, err := net.SplitHostPort(target) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
port, err := strconv.Atoi(portStr) | |||||
if err != nil { | |||||
return errors.New("proxy: failed to parse port number: " + portStr) | |||||
} | |||||
if port < 1 || port > 0xffff { | |||||
return errors.New("proxy: port number out of range: " + portStr) | |||||
} | |||||
// the size here is just an estimate | |||||
buf := make([]byte, 0, 6+len(host)) | |||||
buf = append(buf, proxy_socks5Version) | |||||
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { | |||||
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword) | |||||
} else { | |||||
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone) | |||||
} | |||||
if _, err := conn.Write(buf); err != nil { | |||||
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) | |||||
} | |||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil { | |||||
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | |||||
} | |||||
if buf[0] != 5 { | |||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) | |||||
} | |||||
if buf[1] == 0xff { | |||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") | |||||
} | |||||
// See RFC 1929 | |||||
if buf[1] == proxy_socks5AuthPassword { | |||||
buf = buf[:0] | |||||
buf = append(buf, 1 /* password protocol version */) | |||||
buf = append(buf, uint8(len(s.user))) | |||||
buf = append(buf, s.user...) | |||||
buf = append(buf, uint8(len(s.password))) | |||||
buf = append(buf, s.password...) | |||||
if _, err := conn.Write(buf); err != nil { | |||||
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) | |||||
} | |||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil { | |||||
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | |||||
} | |||||
if buf[1] != 0 { | |||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") | |||||
} | |||||
} | |||||
buf = buf[:0] | |||||
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */) | |||||
if ip := net.ParseIP(host); ip != nil { | |||||
if ip4 := ip.To4(); ip4 != nil { | |||||
buf = append(buf, proxy_socks5IP4) | |||||
ip = ip4 | |||||
} else { | |||||
buf = append(buf, proxy_socks5IP6) | |||||
} | |||||
buf = append(buf, ip...) | |||||
} else { | |||||
if len(host) > 255 { | |||||
return errors.New("proxy: destination host name too long: " + host) | |||||
} | |||||
buf = append(buf, proxy_socks5Domain) | |||||
buf = append(buf, byte(len(host))) | |||||
buf = append(buf, host...) | |||||
} | |||||
buf = append(buf, byte(port>>8), byte(port)) | |||||
if _, err := conn.Write(buf); err != nil { | |||||
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) | |||||
} | |||||
if _, err := io.ReadFull(conn, buf[:4]); err != nil { | |||||
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | |||||
} | |||||
failure := "unknown error" | |||||
if int(buf[1]) < len(proxy_socks5Errors) { | |||||
failure = proxy_socks5Errors[buf[1]] | |||||
} | |||||
if len(failure) > 0 { | |||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) | |||||
} | |||||
bytesToDiscard := 0 | |||||
switch buf[3] { | |||||
case proxy_socks5IP4: | |||||
bytesToDiscard = net.IPv4len | |||||
case proxy_socks5IP6: | |||||
bytesToDiscard = net.IPv6len | |||||
case proxy_socks5Domain: | |||||
_, err := io.ReadFull(conn, buf[:1]) | |||||
if err != nil { | |||||
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | |||||
} | |||||
bytesToDiscard = int(buf[0]) | |||||
default: | |||||
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) | |||||
} | |||||
if cap(buf) < bytesToDiscard { | |||||
buf = make([]byte, bytesToDiscard) | |||||
} else { | |||||
buf = buf[:bytesToDiscard] | |||||
} | |||||
if _, err := io.ReadFull(conn, buf); err != nil { | |||||
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | |||||
} | |||||
// Also need to discard the port number | |||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil { | |||||
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) | |||||
} | |||||
return nil | |||||
} |
@@ -472,6 +472,9 @@ github.com/gorilla/mux | |||||
github.com/gorilla/securecookie | github.com/gorilla/securecookie | ||||
# github.com/gorilla/sessions v1.2.0 | # github.com/gorilla/sessions v1.2.0 | ||||
github.com/gorilla/sessions | github.com/gorilla/sessions | ||||
# github.com/gorilla/websocket v1.4.0 | |||||
## explicit | |||||
github.com/gorilla/websocket | |||||
# github.com/hashicorp/go-cleanhttp v0.5.1 | # github.com/hashicorp/go-cleanhttp v0.5.1 | ||||
github.com/hashicorp/go-cleanhttp | github.com/hashicorp/go-cleanhttp | ||||
# github.com/hashicorp/go-retryablehttp v0.6.6 | # github.com/hashicorp/go-retryablehttp v0.6.6 | ||||
@@ -95,8 +95,8 @@ | |||||
min-width="6.75%" | min-width="6.75%" | ||||
> | > | ||||
<template slot-scope="scope"> | <template slot-scope="scope"> | ||||
<a :href="'/'+scope.row.UserName" :title="scope.row.UserName"> | |||||
<img class="ui avatar image" :src="scope.row.UserRelAvatarLink"> | |||||
<a :href="!scope.row.UserName? '#':'/'+scope.row.UserName" :title="scope.row.UserName||defaultAvatarName"> | |||||
<img class="ui avatar image" :src="scope.row.UserRelAvatarLink||defaultAvatar"> | |||||
</a> | </a> | ||||
</template> | </template> | ||||
</el-table-column> | </el-table-column> | ||||
@@ -104,7 +104,7 @@ | |||||
<el-table-column label="操作" min-width="18%" align="center"> | <el-table-column label="操作" min-width="18%" align="center"> | ||||
<template slot-scope="scope"> | <template slot-scope="scope"> | ||||
<div class="space-around"> | <div class="space-around"> | ||||
<a :style="{visibility:!scope.row.Children ? 'visible':'hidden'}" :class="{'disabled':!scope.row.IsCanOper}" @click="showcreateVue(scope.row.Name,scope.row.Version)">创建新版本</a> | |||||
<a :style="{visibility:!scope.row.Children ? 'visible':'hidden'}" :class="{'disabled':!scope.row.IsCanOper}" @click="showcreateVue(scope.row.Name,scope.row.Version,scope.row.Label)">创建新版本</a> | |||||
<a :href="loadhref+scope.row.ID" :class="{'disabled':!scope.row.IsCanOper}">下载</a> | <a :href="loadhref+scope.row.ID" :class="{'disabled':!scope.row.IsCanOper}">下载</a> | ||||
<a :class="{'disabled':!scope.row.IsCanOper}" @click="deleteModel(scope.row.ID,scope.row.cName)">删除</a> | <a :class="{'disabled':!scope.row.IsCanOper}" @click="deleteModel(scope.row.ID,scope.row.cName)">删除</a> | ||||
</div> | </div> | ||||
@@ -141,6 +141,7 @@ export default { | |||||
}, | }, | ||||
data() { | data() { | ||||
return { | return { | ||||
currentPage:1, | currentPage:1, | ||||
pageSize:10, | pageSize:10, | ||||
totalNum:0, | totalNum:0, | ||||
@@ -149,29 +150,37 @@ export default { | |||||
url:'', | url:'', | ||||
isLoading:true, | isLoading:true, | ||||
loadNodeMap:new Map(), | loadNodeMap:new Map(), | ||||
submitId:{} | |||||
submitId:{}, | |||||
defaultAvatar:'/user/avatar/Ghost/-1', | |||||
defaultAvatarName:'Ghost', | |||||
data:'' | |||||
}; | }; | ||||
}, | }, | ||||
methods: { | methods: { | ||||
load(tree, treeNode, resolve) { | load(tree, treeNode, resolve) { | ||||
this.loadNodeMap.set(tree.cName, {tree,treeNode,resolve}) | |||||
this.$axios.get(this.url+'show_model_child_api',{params:{ | |||||
name:tree.cName | |||||
}}).then((res)=>{ | |||||
let TrainTaskInfo | |||||
let tableData | |||||
tableData= res.data | |||||
for(let i=0;i<tableData.length;i++){ | |||||
TrainTaskInfo = JSON.parse(tableData[i].TrainTaskInfo) | |||||
tableData[i].EngineName = TrainTaskInfo.EngineName.split('-')[0] | |||||
tableData[i].ComputeResource = TrainTaskInfo.ComputeResource | |||||
tableData[i].cName=tableData[i].Name | |||||
tableData[i].Name='' | |||||
tableData[i].VersionCount = '' | |||||
tableData[i].Children = true | |||||
} | |||||
resolve(tableData) | |||||
}) | |||||
try{ | |||||
this.loadNodeMap.set(tree.cName, {tree,treeNode,resolve}) | |||||
this.$axios.get(this.url+'show_model_child_api',{params:{ | |||||
name:tree.cName | |||||
}}).then((res)=>{ | |||||
let TrainTaskInfo | |||||
let tableData | |||||
tableData= res.data | |||||
for(let i=0;i<tableData.length;i++){ | |||||
TrainTaskInfo = JSON.parse(tableData[i].TrainTaskInfo) | |||||
tableData[i].EngineName = TrainTaskInfo.EngineName.split('-')[0] | |||||
tableData[i].ComputeResource = TrainTaskInfo.ComputeResource | |||||
tableData[i].cName=tableData[i].Name | |||||
tableData[i].Name='' | |||||
tableData[i].VersionCount = '' | |||||
tableData[i].Children = true | |||||
} | |||||
resolve(tableData||[]) | |||||
}) | |||||
} | |||||
catch(e){ | |||||
this.loading = false; | |||||
} | |||||
}, | }, | ||||
tableHeaderStyle({row,column,rowIndex,columnIndex}){ | tableHeaderStyle({row,column,rowIndex,columnIndex}){ | ||||
if(rowIndex===0){ | if(rowIndex===0){ | ||||
@@ -186,26 +195,32 @@ export default { | |||||
this.params.page = val | this.params.page = val | ||||
this.getModelList() | this.getModelList() | ||||
}, | }, | ||||
showcreateVue(name,version){ | |||||
showcreateVue(name,version,label){ | |||||
$('.ui.modal.second') | $('.ui.modal.second') | ||||
.modal({ | .modal({ | ||||
centered: false, | centered: false, | ||||
onShow:function(){ | onShow:function(){ | ||||
$('#model_header').text("创建模型新版本") | $('#model_header').text("创建模型新版本") | ||||
$('input[name="Name"]').addClass('model_disabled') | $('input[name="Name"]').addClass('model_disabled') | ||||
$('input[name="Name"]').attr('readonly','readonly') | |||||
$('input[name="Version"]').addClass('model_disabled') | $('input[name="Version"]').addClass('model_disabled') | ||||
$('.ui.dimmer').css({"background-color":"rgb(136, 136, 136,0.7)"}) | $('.ui.dimmer').css({"background-color":"rgb(136, 136, 136,0.7)"}) | ||||
$("#job-name").empty() | $("#job-name").empty() | ||||
$('#name').val(name) | $('#name').val(name) | ||||
$('#label').val(label) | |||||
let version_string = versionAdd(version) | let version_string = versionAdd(version) | ||||
$('#version').val(version_string) | $('#version').val(version_string) | ||||
loadTrainList() | loadTrainList() | ||||
}, | }, | ||||
onHide:function(){ | onHide:function(){ | ||||
document.getElementById("formId").reset(); | document.getElementById("formId").reset(); | ||||
$('input[name="Name"]').removeClass('model_disabled') | |||||
$('input[name="Name"]').removeAttr('readonly') | |||||
$('#choice_model').dropdown('clear') | $('#choice_model').dropdown('clear') | ||||
$('#choice_version').dropdown('clear') | $('#choice_version').dropdown('clear') | ||||
$('.ui.dimmer').css({"background-color":""}) | $('.ui.dimmer').css({"background-color":""}) | ||||
$('.ui.error.message').text() | |||||
$('.ui.error.message').css('display','none') | |||||
} | } | ||||
}) | }) | ||||
.modal('show') | .modal('show') | ||||
@@ -240,23 +255,22 @@ export default { | |||||
$("#verionname").removeClass("error") | $("#verionname").removeClass("error") | ||||
} | } | ||||
return true | return true | ||||
}, | }, | ||||
submit(){ | submit(){ | ||||
let context = this | let context = this | ||||
let flag= this.check() | let flag= this.check() | ||||
if(flag){ | if(flag){ | ||||
let data = $("#formId").serialize() | |||||
let cName = $("input[name='Name']").val() | let cName = $("input[name='Name']").val() | ||||
let row = {cName:cName} | |||||
let version = $("input[name='Version']").val() | |||||
let data = $("#formId").serialize() | |||||
$("#mask").css({"display":"block","z-index":"9999"}) | $("#mask").css({"display":"block","z-index":"9999"}) | ||||
$.ajax({ | $.ajax({ | ||||
url:url_href, | url:url_href, | ||||
type:'POST', | type:'POST', | ||||
data:data, | data:data, | ||||
success:function(res){ | success:function(res){ | ||||
// context.loadrefresh1(row) | |||||
context.getModelList() | context.getModelList() | ||||
context.loadrefresh(row) | |||||
$('.ui.modal.second').modal('hide') | $('.ui.modal.second').modal('hide') | ||||
}, | }, | ||||
error: function(xhr){ | error: function(xhr){ | ||||
@@ -274,20 +288,32 @@ export default { | |||||
} | } | ||||
}, | }, | ||||
loadrefresh(row){ | loadrefresh(row){ | ||||
const store = this.$refs.table.store | |||||
if(!this.loadNodeMap.get(row.cName)){ | if(!this.loadNodeMap.get(row.cName)){ | ||||
return | |||||
const parent = store.states.data | |||||
const index = parent.findIndex(child => child.ID == row.ID) | |||||
parent.splice(index, 1) | |||||
}else{ | }else{ | ||||
let {tree,treeNode,resolve} = this.loadNodeMap.get(row.cName) | let {tree,treeNode,resolve} = this.loadNodeMap.get(row.cName) | ||||
this.$set( | |||||
this.$refs.table.store.states.lazyTreeNodeMap, | |||||
tree.ID, | |||||
[]) | |||||
this.load(tree,treeNode,resolve) | |||||
const keys = Object.keys(store.states.lazyTreeNodeMap); | |||||
if(keys.includes(row.ID)){ | |||||
this.getModelList() | |||||
}else{ | |||||
let parentRow = store.states.data.find(child => child.cName == row.cName); | |||||
let childrenIndex = store.states.lazyTreeNodeMap[parentRow.ID].findIndex(child => child.ID == row.ID) | |||||
parentRow.VersionCount = parentRow.VersionCount-1 | |||||
const parent = store.states.lazyTreeNodeMap[parentRow.ID] | |||||
if(parent.length===1){ | |||||
this.getModelList() | |||||
}else{ | |||||
parent.splice(childrenIndex, 1); | |||||
} | |||||
} | |||||
} | } | ||||
}, | }, | ||||
deleteModel(id,name){ | deleteModel(id,name){ | ||||
let row={cName:name} | |||||
let row={cName:name,ID:id} | |||||
let _this = this | let _this = this | ||||
let flag=1 | let flag=1 | ||||
$('.ui.basic.modal.first') | $('.ui.basic.modal.first') | ||||
@@ -300,8 +326,8 @@ export default { | |||||
params:{ | params:{ | ||||
ID:id | ID:id | ||||
}}).then((res)=>{ | }}).then((res)=>{ | ||||
_this.getModelList() | |||||
_this.loadrefresh(row) | _this.loadrefresh(row) | ||||
// _this.getModelList() | |||||
}) | }) | ||||
flag = true | flag = true | ||||
}, | }, | ||||
@@ -315,24 +341,29 @@ export default { | |||||
}) | }) | ||||
.modal('show') | .modal('show') | ||||
}, | }, | ||||
getModelList(){ | getModelList(){ | ||||
this.$axios.get(location.href+'_api',{ | |||||
params:this.params | |||||
}).then((res)=>{ | |||||
$(".ui.grid").removeAttr("style") | |||||
$("#loadContainer").removeClass("loader") | |||||
let TrainTaskInfo | |||||
this.tableData = res.data.data | |||||
for(let i=0;i<this.tableData.length;i++){ | |||||
TrainTaskInfo = JSON.parse(this.tableData[i].TrainTaskInfo) | |||||
this.tableData[i].cName=this.tableData[i].Name | |||||
this.tableData[i].EngineName = TrainTaskInfo.EngineName.split('-')[0] | |||||
this.tableData[i].ComputeResource = TrainTaskInfo.ComputeResource | |||||
this.tableData[i].hasChildren = res.data.data[i].VersionCount===1 ? false : true | |||||
} | |||||
this.totalNum = res.data.count | |||||
}) | |||||
try { | |||||
this.$refs.table.store.states.lazyTreeNodeMap = {} | |||||
this.$axios.get(location.href+'_api',{ | |||||
params:this.params | |||||
}).then((res)=>{ | |||||
$(".ui.grid").removeAttr("style") | |||||
$("#loadContainer").removeClass("loader") | |||||
let TrainTaskInfo | |||||
this.tableData = res.data.data | |||||
for(let i=0;i<this.tableData.length;i++){ | |||||
TrainTaskInfo = JSON.parse(this.tableData[i].TrainTaskInfo) | |||||
this.tableData[i].cName=this.tableData[i].Name | |||||
this.tableData[i].EngineName = TrainTaskInfo.EngineName.split('-')[0] | |||||
this.tableData[i].ComputeResource = TrainTaskInfo.ComputeResource | |||||
this.tableData[i].hasChildren = res.data.data[i].VersionCount===1 ? false : true | |||||
} | |||||
this.totalNum = res.data.count | |||||
}) | |||||
}catch (e) { | |||||
console.log(e) | |||||
} | |||||
}, | }, | ||||
}, | }, | ||||
@@ -369,8 +400,6 @@ export default { | |||||
return size+unitArr[index]; | return size+unitArr[index]; | ||||
} | } | ||||
} | } | ||||
}, | }, | ||||
mounted() { | mounted() { | ||||
this.submitId = document.getElementById("submitId") | this.submitId = document.getElementById("submitId") | ||||
@@ -458,7 +487,7 @@ export default { | |||||
margin-right: 3px; | margin-right: 3px; | ||||
font-size: 12px; | font-size: 12px; | ||||
} | } | ||||
/deep/ .el-table_1_column_1.is-left .cell {padding-right: 0px !important;} | |||||
/deep/ .el-table_1_column_1.is-left .cell {padding-right: 0px !important;white-space: nowrap;} | |||||
/deep/ .el-table__expand-icon .el-icon-arrow-right{ | /deep/ .el-table__expand-icon .el-icon-arrow-right{ | ||||
font-family: element-icons!important; | font-family: element-icons!important; | ||||
speak: none; | speak: none; | ||||
@@ -3745,6 +3745,7 @@ function initFilterBranchTagDropdown(selector) { | |||||
}); | }); | ||||
}); | }); | ||||
$data.remove(); | $data.remove(); | ||||
console.log("-this",this) | |||||
new Vue({ | new Vue({ | ||||
delimiters: ['${', '}'], | delimiters: ['${', '}'], | ||||
el: this, | el: this, | ||||
@@ -4133,4 +4134,6 @@ $('.question.circle.icon').hover(function(){ | |||||
}); | }); | ||||
//云脑详情页面跳转回上一个页面 | //云脑详情页面跳转回上一个页面 | ||||
$(".section.backTodeBug").attr("href",localStorage.getItem('all')) | |||||
$(".section.backTodeBug").attr("href",localStorage.getItem('all')) | |||||
//新建调试取消跳转 | |||||
$(".ui.button.cancel").attr("href",localStorage.getItem('all')) |