| @@ -56,6 +56,7 @@ require ( | |||
| github.com/gomodule/redigo v2.0.0+incompatible | |||
| github.com/google/go-github/v24 v24.0.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/huandu/xstrings v1.3.0 | |||
| 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.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= | |||
| 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/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= | |||
| @@ -31,7 +31,7 @@ type AiModelManage struct { | |||
| CodeCommitID string `xorm:"NULL"` | |||
| UserId int64 `xorm:"NOT NULL"` | |||
| UserName string | |||
| UserRelAvatarLink string `xorm:"NULL"` | |||
| UserRelAvatarLink string | |||
| TrainTaskInfo string `xorm:"text NULL"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"updated"` | |||
| @@ -135,7 +135,7 @@ func QueryModelByName(name string, repoId int64) []*AiModelManage { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| sess.Select("*").Table("ai_model_manage"). | |||
| Where("name='" + name + "' and repo_id=" + fmt.Sprint(repoId)).OrderBy("version desc") | |||
| Where("name='" + name + "' and repo_id=" + fmt.Sprint(repoId)).OrderBy("created_unix desc") | |||
| aiModelManageList := make([]*AiModelManage, 0) | |||
| sess.Find(&aiModelManageList) | |||
| return aiModelManageList | |||
| @@ -962,13 +962,23 @@ func QueryModelTrainJobList(repoId int64) ([]*CloudbrainInfo, int, error) { | |||
| cond = cond.And( | |||
| builder.Eq{"Status": "COMPLETED"}, | |||
| ) | |||
| sess.OrderBy("job_id DESC") | |||
| cloudbrains := make([]*CloudbrainInfo, 0) | |||
| if err := sess.Distinct("job_id,job_name").Table(&Cloudbrain{}).Where(cond). | |||
| if err := sess.Select("job_id,job_name").Table(&Cloudbrain{}).Where(cond).OrderBy("created_unix DESC"). | |||
| Find(&cloudbrains); err != nil { | |||
| return nil, 0, fmt.Errorf("Find: %v", err) | |||
| } | |||
| return cloudbrains, int(len(cloudbrains)), nil | |||
| keys := make(map[string]string) | |||
| uniqueElements := make([]*CloudbrainInfo, 0) | |||
| for _, entry := range cloudbrains { | |||
| if _, value := keys[entry.JobID]; !value { | |||
| keys[entry.JobID] = entry.JobName | |||
| uniqueElements = append(uniqueElements, entry) | |||
| } | |||
| } | |||
| return uniqueElements, int(len(uniqueElements)), nil | |||
| } | |||
| func CloudbrainsVersionList(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int, error) { | |||
| @@ -1108,10 +1118,7 @@ func UpdateJob(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 | |||
| } | |||
| @@ -1397,6 +1397,8 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats, | |||
| if opts.MilestoneID > 0 { | |||
| 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 { | |||
| @@ -353,7 +353,7 @@ func GetMilestonesByRepoID(repoID int64, state api.StateType, listOptions ListOp | |||
| } | |||
| 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. | |||
| @@ -649,49 +649,41 @@ func (repo *Repository) GetAssignees() (_ []*User, err error) { | |||
| 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 >= ? AND user_id NOT IN ( ?, ?)) ORDER BY name", | |||
| repo.ID, AccessModeRead, | |||
| 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 >= ? AND user_id NOT IN ( ?, ?) ) ORDER BY name " | |||
| if err = e. | |||
| SQL(SQLCmd, | |||
| repo.ID, AccessModeWrite, 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) { | |||
| if err = repo.getOwner(e); err != nil { | |||
| 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 { | |||
| 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 | |||
| @@ -2478,6 +2470,12 @@ func GetBlockChainUnSuccessRepos() ([]*Repository, error) { | |||
| 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() { | |||
| sess := x.NewSession() | |||
| defer sess.Close() | |||
| @@ -24,6 +24,8 @@ const ( | |||
| RepoWatchModeAuto // 3 | |||
| ) | |||
| var ActionChan = make(chan *Action, 200) | |||
| // Watch is connection request for receiving repository notification. | |||
| type Watch struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| @@ -277,9 +279,17 @@ func notifyWatchers(e Engine, actions ...*Action) error { | |||
| // NotifyWatchers creates batch of actions for every watcher. | |||
| func NotifyWatchers(actions ...*Action) error { | |||
| producer(actions...) | |||
| return notifyWatchers(x, actions...) | |||
| } | |||
| func producer(actions ...*Action) { | |||
| for _, action := range actions { | |||
| ActionChan <- action | |||
| } | |||
| } | |||
| // NotifyWatchersActions creates batch of actions for every watcher. | |||
| func NotifyWatchersActions(acts []*Action) error { | |||
| sess := x.NewSession() | |||
| @@ -271,6 +271,15 @@ func AllCommitsCount(repoPath string) (int64, error) { | |||
| return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) | |||
| } | |||
| func HEADCommitsCount(repoPath string) (int64, error) { | |||
| stdout, err := NewCommand("rev-list", "HEAD", "--count").RunInDir(repoPath) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) | |||
| } | |||
| func commitsCount(repoPath, revision, relpath string) (int64, error) { | |||
| cmd := NewCommand("rev-list", "--count") | |||
| cmd.AddArguments(revision) | |||
| @@ -52,6 +52,10 @@ func (repo *Repository) GetAllCommitsCount() (int64, error) { | |||
| return AllCommitsCount(repo.Path) | |||
| } | |||
| func (repo *Repository) GetHeadCommitsCount() (int64, error) { | |||
| return HEADCommitsCount(repo.Path) | |||
| } | |||
| func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, error) { | |||
| l := list.New() | |||
| if len(logs) == 0 { | |||
| @@ -441,8 +445,12 @@ type Contributor struct { | |||
| 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) | |||
| if err != nil { | |||
| return nil, err | |||
| @@ -458,9 +466,9 @@ func GetContributors(repoPath string) ([]Contributor, error){ | |||
| } | |||
| number := oneCount[0:strings.Index(oneCount, "\t")] | |||
| 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, " ") | |||
| email := oneCount[strings.Index(oneCount, "<")+1:strings.Index(oneCount, ">")] | |||
| email := oneCount[strings.Index(oneCount, "<")+1 : strings.Index(oneCount, ">")] | |||
| contributorsInfo[i] = Contributor{ | |||
| commitCnt, committer, email, | |||
| } | |||
| @@ -126,7 +126,7 @@ func GetUserKPIStats(repoPath string) (map[string]*UserKPIStats, error) { | |||
| func SetRepoKPIStats(repoPath string, fromTime time.Time, stats *RepoKPIStats, newContributers map[string]struct{}) error { | |||
| since := fromTime.Format(time.RFC3339) | |||
| args := []string{"log", "--numstat", "--no-merges", "--branches=*", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)} | |||
| args := []string{"log", "--numstat", "--no-merges", "HEAD", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)} | |||
| stdout, err := NewCommand(args...).RunInDirBytes(repoPath) | |||
| if err != nil { | |||
| @@ -212,7 +212,7 @@ func SetRepoKPIStats(repoPath string, fromTime time.Time, stats *RepoKPIStats, n | |||
| func GetContributorsDetail(repoPath string, fromTime time.Time) ([]Contributor, error) { | |||
| since := fromTime.Format(time.RFC3339) | |||
| cmd := NewCommand("shortlog", "-sne", "--all", fmt.Sprintf("--since='%s'", since)) | |||
| cmd := NewCommand("shortlog", "-sne", "HEAD", fmt.Sprintf("--since='%s'", since)) | |||
| stdout, err := cmd.RunInDir(repoPath) | |||
| if err != nil { | |||
| return nil, err | |||
| @@ -778,8 +778,6 @@ datasets = Datasets | |||
| datasets.desc = Enable Dataset | |||
| cloudbrain_helper=Use GPU/NPU resources to open notebooks, model training tasks, etc. | |||
| model_manager = Model | |||
| debug=Debug | |||
| stop=Stop | |||
| delete=Delete | |||
| @@ -897,7 +895,7 @@ model.manage.Accuracy = Accuracy | |||
| model.manage.F1 = F1 | |||
| model.manage.Precision = Precision | |||
| model.manage.Recall = Recall | |||
| model.manage.sava_model = Sava Model | |||
| template.items = Template Items | |||
| template.git_content = Git Content (Default Branch) | |||
| @@ -907,6 +907,7 @@ model.manage.Accuracy = 准确率 | |||
| model.manage.F1 = F1值 | |||
| model.manage.Precision = 精确率 | |||
| model.manage.Recall = 召回率 | |||
| model.manage.sava_model = 保存模型 | |||
| template.items=模板选项 | |||
| 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() | |||
| } | |||
| @@ -539,7 +539,7 @@ func updateRepoCommitCnt(ctx *macaron.Context, repo *models.Repository) error { | |||
| } | |||
| defer gitRepo.Close() | |||
| count, err := gitRepo.GetAllCommitsCount() | |||
| count, err := gitRepo.GetHeadCommitsCount() | |||
| if err != nil { | |||
| log.Error("GetAllCommitsCount failed:%v", err.Error(), ctx.Data["MsgID"]) | |||
| return err | |||
| @@ -71,25 +71,24 @@ func saveModelByParameters(jobId string, versionName string, name string, versio | |||
| aiTaskJson, _ := json.Marshal(aiTask) | |||
| model := &models.AiModelManage{ | |||
| ID: id, | |||
| Version: version, | |||
| VersionCount: len(aimodels) + 1, | |||
| Label: label, | |||
| Name: name, | |||
| Description: description, | |||
| New: MODEL_LATEST, | |||
| Type: cloudType, | |||
| Path: modelPath, | |||
| Size: modelSize, | |||
| AttachmentId: aiTask.Uuid, | |||
| RepoId: aiTask.RepoID, | |||
| UserId: ctx.User.ID, | |||
| UserRelAvatarLink: ctx.User.RelAvatarLink(), | |||
| CodeBranch: aiTask.BranchName, | |||
| CodeCommitID: aiTask.CommitID, | |||
| Engine: aiTask.EngineID, | |||
| TrainTaskInfo: string(aiTaskJson), | |||
| Accuracy: string(accuracyJson), | |||
| ID: id, | |||
| Version: version, | |||
| VersionCount: len(aimodels) + 1, | |||
| Label: label, | |||
| Name: name, | |||
| Description: description, | |||
| New: MODEL_LATEST, | |||
| Type: cloudType, | |||
| Path: modelPath, | |||
| Size: modelSize, | |||
| AttachmentId: aiTask.Uuid, | |||
| RepoId: aiTask.RepoID, | |||
| UserId: ctx.User.ID, | |||
| CodeBranch: aiTask.BranchName, | |||
| CodeCommitID: aiTask.CommitID, | |||
| Engine: aiTask.EngineID, | |||
| TrainTaskInfo: string(aiTaskJson), | |||
| Accuracy: string(accuracyJson), | |||
| } | |||
| err = models.SaveModelToDb(model) | |||
| @@ -392,10 +391,24 @@ func ShowSingleModel(ctx *context.Context) { | |||
| log.Info("Show single ModelInfo start.name=" + name) | |||
| models := models.QueryModelByName(name, ctx.Repo.Repository.ID) | |||
| userIds := make([]int64, len(models)) | |||
| for i, model := range models { | |||
| model.IsCanOper = isOper(ctx, model.UserId) | |||
| userIds[i] = model.UserId | |||
| } | |||
| userNameMap := queryUserName(userIds) | |||
| for _, model := range models { | |||
| value := userNameMap[model.UserId] | |||
| if value != nil { | |||
| model.UserName = value.Name | |||
| model.UserRelAvatarLink = value.RelAvatarLink() | |||
| } | |||
| } | |||
| ctx.JSON(http.StatusOK, models) | |||
| } | |||
| func queryUserName(intSlice []int64) map[int64]string { | |||
| func queryUserName(intSlice []int64) map[int64]*models.User { | |||
| keys := make(map[int64]string) | |||
| uniqueElements := []int64{} | |||
| for _, entry := range intSlice { | |||
| @@ -404,13 +417,15 @@ func queryUserName(intSlice []int64) map[int64]string { | |||
| uniqueElements = append(uniqueElements, entry) | |||
| } | |||
| } | |||
| userNames, err := models.GetUserNamesByIDs(uniqueElements) | |||
| result := make(map[int64]*models.User) | |||
| userLists, err := models.GetUsersByIDs(uniqueElements) | |||
| if err == nil { | |||
| for i, userName := range userNames { | |||
| keys[uniqueElements[i]] = userName | |||
| for _, user := range userLists { | |||
| result[user.ID] = user | |||
| } | |||
| } | |||
| return keys | |||
| return result | |||
| } | |||
| func ShowOneVersionOtherModel(ctx *context.Context) { | |||
| @@ -420,15 +435,17 @@ func ShowOneVersionOtherModel(ctx *context.Context) { | |||
| userIds := make([]int64, len(aimodels)) | |||
| for i, model := range aimodels { | |||
| log.Info("model=" + model.Name) | |||
| log.Info("model.UserId=" + fmt.Sprint(model.UserId)) | |||
| model.IsCanOper = isOper(ctx, model.UserId) | |||
| userIds[i] = model.UserId | |||
| } | |||
| userNameMap := queryUserName(userIds) | |||
| for _, model := range aimodels { | |||
| model.UserName = userNameMap[model.UserId] | |||
| value := userNameMap[model.UserId] | |||
| if value != nil { | |||
| model.UserName = value.Name | |||
| model.UserRelAvatarLink = value.RelAvatarLink() | |||
| } | |||
| } | |||
| if len(aimodels) > 0 { | |||
| @@ -496,15 +513,18 @@ func ShowModelPageInfo(ctx *context.Context) { | |||
| userIds := make([]int64, len(modelResult)) | |||
| for i, model := range modelResult { | |||
| log.Info("model=" + model.Name) | |||
| log.Info("model.UserId=" + fmt.Sprint(model.UserId)) | |||
| model.IsCanOper = isOper(ctx, model.UserId) | |||
| userIds[i] = model.UserId | |||
| } | |||
| userNameMap := queryUserName(userIds) | |||
| for _, model := range modelResult { | |||
| model.UserName = userNameMap[model.UserId] | |||
| value := userNameMap[model.UserId] | |||
| if value != nil { | |||
| model.UserName = value.Name | |||
| model.UserRelAvatarLink = value.RelAvatarLink() | |||
| } | |||
| } | |||
| mapInterface := make(map[string]interface{}) | |||
| @@ -72,7 +72,7 @@ func HandleBlockChainInitNotify(ctx *context.Context) { | |||
| repo.BlockChainStatus = models.RepoBlockChainSuccess | |||
| 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"]) | |||
| ctx.JSON(200, map[string]string{ | |||
| "code": "-1", | |||
| @@ -193,6 +193,8 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB | |||
| var mileIDs []int64 | |||
| if milestoneID > 0 { | |||
| mileIDs = []int64{milestoneID} | |||
| } else if milestoneID == -1 { //only search no milestone | |||
| mileIDs = []int64{0} | |||
| } | |||
| var issues []*models.Issue | |||
| @@ -355,7 +357,8 @@ func Issues(ctx *context.Context) { | |||
| var err error | |||
| // 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 { | |||
| ctx.ServerError("GetAllRepoMilestones", err) | |||
| return | |||
| @@ -92,6 +92,7 @@ func DebugJobIndex(ctx *context.Context) { | |||
| ctx.Data["PageIsCloudBrain"] = true | |||
| ctx.Data["Tasks"] = ciTasks | |||
| ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx) | |||
| ctx.Data["RepoIsEmpty"] = repo.IsEmpty | |||
| ctx.HTML(200, tplDebugJobIndex) | |||
| } | |||
| @@ -331,6 +332,7 @@ func TrainJobIndex(ctx *context.Context) { | |||
| ctx.Data["PageIsCloudBrain"] = true | |||
| ctx.Data["Tasks"] = tasks | |||
| ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx) | |||
| ctx.Data["RepoIsEmpty"] = repo.IsEmpty | |||
| ctx.HTML(200, tplModelArtsTrainJobIndex) | |||
| } | |||
| @@ -1228,7 +1230,7 @@ func TrainJobShow(ctx *context.Context) { | |||
| ctx.Data["canNewJob"] = canNewJob | |||
| //将运行参数转化为epoch_size = 3, device_target = Ascend的格式 | |||
| for i, _ := range VersionListTasks { | |||
| for i, task := range VersionListTasks { | |||
| var parameters models.Parameters | |||
| @@ -1249,6 +1251,9 @@ func TrainJobShow(ctx *context.Context) { | |||
| } else { | |||
| 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) | |||
| @@ -605,7 +605,7 @@ func getContributorInfo(contributorInfos []*ContributorInfo, email string) *Cont | |||
| func Home(ctx *context.Context) { | |||
| if len(ctx.Repo.Units) > 0 { | |||
| //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 { | |||
| startTime := time.Now() | |||
| var contributorInfos []*ContributorInfo | |||
| @@ -924,7 +924,9 @@ func ContributorsAPI(ctx *context.Context) { | |||
| count := 0 | |||
| errorCode := 0 | |||
| 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 | |||
| if err == nil && contributors != nil { | |||
| contributorInfoHash := make(map[string]*ContributorInfo) | |||
| @@ -315,6 +315,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| }) | |||
| m.Get("/", routers.Home) | |||
| m.Get("/dashboard", routers.Dashboard) | |||
| go routers.SocketManager.Run() | |||
| m.Get("/action/notification", routers.ActionNotification) | |||
| m.Get("/recommend/org", routers.RecommendOrgFromPromote) | |||
| m.Get("/recommend/repo", routers.RecommendRepoFromPromote) | |||
| 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; | |||
| } | |||
| .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> | |||
| <div id="mask"> | |||
| @@ -258,7 +248,7 @@ | |||
| <button class="ui green button" > | |||
| {{.i18n.Tr "repo.cloudbrain.new"}} | |||
| </button> | |||
| <a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||
| <a class="ui button cancel" href="">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| @@ -268,12 +258,7 @@ | |||
| </div> | |||
| </div> | |||
| {{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> | |||
| let url_href = window.location.pathname.split('create')[0] | |||
| $(".ui.button").attr('href',url_href) | |||
| let form = document.getElementById('form_id'); | |||
| @@ -291,10 +276,6 @@ | |||
| $('#messageInfo p').text(str) | |||
| return false | |||
| } | |||
| // if(!value_image || !value_data){ | |||
| // console.log("------------------------") | |||
| // return false | |||
| // } | |||
| let min_value_task = value_task.toLowerCase() | |||
| $("input[name='job_name']").attr("value",min_value_task) | |||
| document.getElementById("mask").style.display = "block" | |||
| @@ -11,7 +11,7 @@ | |||
| {{.i18n.Tr "repo.cloudbrain"}} | |||
| </a> | |||
| <div class="divider"> / </div> | |||
| <a class="section" href="{{.RepoLink}}/debugjob?debugListType=CPU/GPU"> | |||
| <a class="section backTodeBug" href="{{.RepoLink}}/debugjob?debugListType=CPU/GPU"> | |||
| {{$.i18n.Tr "repo.modelarts.notebook"}} | |||
| </a> | |||
| <div class="divider"> / </div> | |||
| @@ -98,3 +98,4 @@ | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| @@ -239,10 +239,12 @@ | |||
| <div class="ui icon header bgtask-header-pic"></div> | |||
| <div class="bgtask-content-header">未创建过调试任务</div> | |||
| <div class="bgtask-content"> | |||
| {{if $.RepoIsEmpty}} | |||
| <div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本</a>;</div> | |||
| {{end}} | |||
| <div class="bgtask-content-txt">运行时长:最长不超过4个小时,超过4个小时将自动停止;</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">数据集:云脑1提供 CPU / GPU 资源,云脑2提供 Ascend NPU 资源,调试使用的数据集也需要上传到对应的环境;</div> | |||
| <div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div> | |||
| </div> | |||
| </div> | |||
| {{else}} | |||
| @@ -464,6 +466,7 @@ | |||
| let url={{.RepoLink}} | |||
| let getParam=location.search.split('?debugListType=').pop() | |||
| let dropdownValue = getParam==='all'||getParam==='' ? '全部' : getParam | |||
| localStorage.setItem('all',location.href) | |||
| function stop(obj) { | |||
| if (obj.style.color != "rgb(204, 204, 204)") { | |||
| obj.target = '_blank' | |||
| @@ -618,4 +621,5 @@ | |||
| } | |||
| }) | |||
| } | |||
| </script> | |||
| @@ -97,16 +97,21 @@ | |||
| <span>{{svg "octicon-code" 16}} {{.i18n.Tr "repo.code"}} <i class="dropdown icon"></i></span> | |||
| </a> | |||
| <div class="dropdown-content"> | |||
| {{if and (.Permission.CanRead $.UnitTypeReleases) (not .IsEmptyRepo) }} | |||
| <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> | |||
| </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}}> | |||
| {{svg "octicon-book" 16}} {{.i18n.Tr "repo.wiki"}} | |||
| </a> | |||
| {{end}} | |||
| {{if and (.Permission.CanReadAny $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases) (not .IsEmptyRepo)}} | |||
| <a style="border: none;" class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity"> | |||
| {{svg "octicon-pulse" 16}} {{.i18n.Tr "repo.activity"}} | |||
| </a> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| @@ -92,7 +92,7 @@ | |||
| <button class="ui green button"> | |||
| {{.i18n.Tr "repo.cloudbrain.new"}} | |||
| </button> | |||
| <a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||
| <a class="ui button cancel" href="">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| @@ -102,10 +102,6 @@ | |||
| {{template "base/footer" .}} | |||
| <script> | |||
| // 取消创建跳转 | |||
| let url_href = window.location.pathname.split('create')[0] | |||
| $(".ui.button").attr('href',url_href) | |||
| // 判断必填选项是否填写正确 | |||
| let form = document.getElementById('form_id'); | |||
| @@ -11,7 +11,7 @@ | |||
| {{.i18n.Tr "repo.cloudbrain"}} | |||
| </a> | |||
| <div class="divider"> / </div> | |||
| <a class="section" href="{{.RepoLink}}/debugjob?debugListType=NPU"> | |||
| <a class="section backTodeBug" href="{{.RepoLink}}/debugjob?debugListType=NPU"> | |||
| {{$.i18n.Tr "repo.modelarts.notebook"}} | |||
| </a> | |||
| <div class="divider"> / </div> | |||
| @@ -49,9 +49,11 @@ | |||
| <div class="ui icon header bgtask-header-pic"></div> | |||
| <div class="bgtask-content-header">未创建过训练任务</div> | |||
| <div class="bgtask-content"> | |||
| <div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本</a>;</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> | |||
| {{if $.RepoIsEmpty}} | |||
| <div class="bgtask-content-txt">代码版本:您还没有初始化代码仓库,请先<a href="{{.RepoLink}}">创建代码版本;</a></div> | |||
| {{end}} | |||
| <div class="bgtask-content-txt">数据集:云脑1提供 CPU / GPU 资源,云脑2提供 Ascend NPU 资源,调试使用的数据集也需要上传到对应的环境;</div> | |||
| <div class="bgtask-content-txt">使用说明:可以参考启智AI协作平台<a href="https://git.openi.org.cn/zeizei/OpenI_Learning">小白训练营课程。</a></div> | |||
| </div> | |||
| </div> | |||
| {{else}} | |||
| @@ -135,10 +137,10 @@ | |||
| </div> | |||
| <div class="three wide column text center padding0"> | |||
| <!-- 删除任务 --> | |||
| <!-- 停止任务 --> | |||
| <div class="ui compact buttons"> | |||
| {{$.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}})"> | |||
| {{$.i18n.Tr "repo.stop"}} | |||
| </a> | |||
| @@ -149,15 +151,16 @@ | |||
| {{end}} | |||
| </div> | |||
| <!-- 删除任务 --> | |||
| <form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{$.Link}}/{{.JobID}}/del" method="post"> | |||
| {{$.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;"> | |||
| {{$.i18n.Tr "repo.delete"}} | |||
| </a> | |||
| {{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> | |||
| {{end}} | |||
| </form> | |||
| @@ -204,7 +207,6 @@ | |||
| {{template "base/footer" .}} | |||
| <script> | |||
| console.log({{.Tasks}}) | |||
| // 调试和评分新开窗口 | |||
| function stop(obj) { | |||
| @@ -147,6 +147,7 @@ | |||
| <span> | |||
| <i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i> | |||
| </span> | |||
| <a href="https://git.openi.org.cn/OpenIOSSG/MINIST_Example" target="_blank">查看样例</a> | |||
| </div> | |||
| <div class="required unite min_title inline field"> | |||
| <label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label> | |||
| @@ -159,6 +160,7 @@ | |||
| <option name="attachment" value="{{.UUID}}">{{.Attachment.Name}}</option> | |||
| {{end}} | |||
| </select> | |||
| <span class="tooltips">数据集位置存储在环境变量data_url中,训练输出路径存储在环境变量train_url中。</span> | |||
| </div> | |||
| <div class="inline unite min_title field"> | |||
| @@ -267,7 +269,6 @@ | |||
| sever_num.val(parseInt(sever_num.val())+1) | |||
| } | |||
| }) | |||
| // 参数增加、删除、修改、保存 | |||
| function Add_parameter(i){ | |||
| value = '<div class="two fields width85" id= "para'+ i +'">' + | |||
| @@ -165,7 +165,6 @@ td, th { | |||
| {{template "repo/header" .}} | |||
| <div class="ui container"> | |||
| <h4 class="ui header" id="vertical-segment"> | |||
| <!-- <a href="javascript:window.history.back();"><i class="arrow left icon"></i>返回</a> --> | |||
| <div class="ui breadcrumb"> | |||
| <a class="section" href="{{.RepoLink}}/cloudbrain"> | |||
| {{.i18n.Tr "repo.cloudbrain"}} | |||
| @@ -187,22 +186,21 @@ td, th { | |||
| <span class="accordion-panel-title-content"> | |||
| <span> | |||
| <div style="float: right;"> | |||
| <!-- <a class="ti-action-menu-item {{if ne .Status "COMPLETED"}}disabled {{end}}">创建模型</a> --> | |||
| {{$.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> | |||
| {{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> | |||
| {{end}} | |||
| {{$.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> | |||
| {{else}} | |||
| <a class="ti-action-menu-item disabled" id="{{.VersionName}}-stop" onclick="stopVersion({{.VersionName}})">{{$.i18n.Tr "repo.stop"}}</a> | |||
| {{end}} | |||
| {{$.CsrfTokenHtml}} | |||
| {{if $.Permission.CanWrite $.UnitTypeCloudBrain}} | |||
| {{if .CanDel}} | |||
| <a class="ti-action-menu-item" onclick="deleteVersion({{.VersionName}})" style="color: #FF4D4F;">{{$.i18n.Tr "repo.delete"}}</a> | |||
| {{else}} | |||
| <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"> | |||
| <tr class="ti-no-ng-animate"> | |||
| <td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
| {{$.i18n.Tr "repo.cloudbrain_task"}} | |||
| {{$.i18n.Tr "repo.cloudbrain_task"}} | |||
| </td> | |||
| <td class="ti-text-form-content"> | |||
| <div class="text-span text-span-w"> | |||
| {{.JobName}} | |||
| {{.JobName}} | |||
| </div> | |||
| </td> | |||
| </tr> | |||
| @@ -259,7 +256,7 @@ td, th { | |||
| <td class="ti-text-form-content"> | |||
| <div class="text-span text-span-w" id="{{.VersionName}}-status"> | |||
| {{.Status}} | |||
| {{.Status}} | |||
| </div> | |||
| </td> | |||
| </tr> | |||
| @@ -270,7 +267,7 @@ td, th { | |||
| <td class="ti-text-form-content"> | |||
| <div class="text-span text-span-w"> | |||
| {{.VersionName}} | |||
| {{.VersionName}} | |||
| </div> | |||
| </td> | |||
| </tr> | |||
| @@ -287,7 +284,7 @@ td, th { | |||
| </tr> | |||
| <tr class="ti-no-ng-animate"> | |||
| <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 class="ti-text-form-content"> | |||
| @@ -309,9 +306,8 @@ td, th { | |||
| </tr> | |||
| <tr class="ti-no-ng-animate"> | |||
| <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 class="ti-text-form-content"> | |||
| <div class="text-span text-span-w"> | |||
| {{.WorkServerNumber}} | |||
| @@ -326,9 +322,8 @@ td, th { | |||
| <tbody class="ti-text-form"> | |||
| <tr class="ti-no-ng-animate"> | |||
| <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 class="ti-text-form-content"> | |||
| <div class="text-span text-span-w"> | |||
| {{.EngineName}} | |||
| @@ -348,12 +343,12 @@ td, th { | |||
| </tr> | |||
| <tr class="ti-no-ng-animate"> | |||
| <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 class="ti-text-form-content"> | |||
| <div class="text-span text-span-w"> | |||
| {{.BootFile}} | |||
| {{.BootFile}} | |||
| </div> | |||
| </td> | |||
| </tr> | |||
| @@ -379,17 +374,7 @@ td, th { | |||
| </div> | |||
| </td> | |||
| </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"> | |||
| <td class="ti-no-ng-animate ti-text-form-label text-width80"> | |||
| {{$.i18n.Tr "repo.modelarts.train_job.description"}} | |||
| @@ -414,15 +399,7 @@ td, th { | |||
| <div class="ui message message{{.VersionName}}" style="display: none;"> | |||
| <div id="header"></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;"> | |||
| <!-- <input type="hidden" class="version_name" name="version_name" value={{.VersionName}}> --> | |||
| <input type="hidden" name="end_line" value> | |||
| <input type="hidden" name="start_line" value> | |||
| <pre id="log_file{{.VersionName}}"></pre> | |||
| @@ -474,9 +451,7 @@ td, th { | |||
| <script> | |||
| console.log({{.version_list_task}}) | |||
| console.log({{.}}) | |||
| $('.menu .item').tab() | |||
| // $('.ui.style.accordion').accordion(); | |||
| $(document).ready(function(){ | |||
| $('.ui.accordion').accordion({selector:{trigger:'.icon'}}); | |||
| @@ -503,8 +478,6 @@ td, th { | |||
| e.cancelBubble = true; //ie兼容 | |||
| } | |||
| } | |||
| // let timeid = window.setInterval(refreshStatus(version_name), 30000); | |||
| // document.ready(refreshStatus(version_name)) | |||
| let timeid = window.setInterval(loadJobStatus, 30000); | |||
| $(document).ready(loadJobStatus); | |||
| @@ -159,6 +159,7 @@ | |||
| <span> | |||
| <i class="question circle icon link" data-content={{.i18n.Tr "repo.modelarts.train_job.boot_file_helper"}} data-position="right center" data-variation="mini"></i> | |||
| </span> | |||
| <a href="https://git.openi.org.cn/OpenIOSSG/MINIST_Example" target="_blank">查看样例</a> | |||
| </div> | |||
| <div class="required unite min_title inline field"> | |||
| <label style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.dataset"}}</label> | |||
| @@ -173,6 +174,7 @@ | |||
| {{end}} | |||
| {{end}} | |||
| </select> | |||
| <span class="tooltips">数据集位置存储在环境变量data_url中,训练输出路径存储在环境变量train_url中。</span> | |||
| </div> | |||
| <div class="inline unite min_title field"> | |||
| @@ -114,7 +114,7 @@ | |||
| </div> | |||
| <div class="inline field"> | |||
| <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 class="inline field"> | |||
| <label for="description">模型描述</label> | |||
| @@ -123,7 +123,7 @@ | |||
| <div class="inline field" style="margin-left: 75px;"> | |||
| <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> | |||
| </div> | |||
| </form> | |||
| @@ -162,12 +162,14 @@ | |||
| $("#job-name").empty() | |||
| createModelName() | |||
| loadTrainList() | |||
| }, | |||
| onHide:function(){ | |||
| document.getElementById("formId").reset(); | |||
| $('#choice_model').dropdown('clear') | |||
| $('#choice_version').dropdown('clear') | |||
| $('.ui.dimmer').css({"background-color":""}) | |||
| $('.ui.error.message').text() | |||
| $('.ui.error.message').css('display','none') | |||
| } | |||
| @@ -179,6 +181,7 @@ | |||
| $('#choice_model').dropdown({ | |||
| onChange:function(value){ | |||
| $(".ui.dropdown.selection.search.width70").addClass("loading") | |||
| $('#choice_version').dropdown('clear') | |||
| $("#job-version").empty() | |||
| loadTrainVersion(value) | |||
| } | |||
| @@ -208,19 +211,29 @@ | |||
| train_html += '</div>' | |||
| } | |||
| $("#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 input[name="JobId"]').val(data[0].JobID) | |||
| loadTrainVersion() | |||
| }) | |||
| } | |||
| function loadTrainVersion(value){ | |||
| $.get(`${repolink}/modelmanage/query_train_job_version?JobID=${value}`, (data) => { | |||
| let JobID = !value ?$('#choice_model input[name="JobId"]').val(): value | |||
| $.get(`${repolink}/modelmanage/query_train_job_version?JobID=${JobID}`, (data) => { | |||
| const n_length = data.length | |||
| let train_html='' | |||
| for (let i=0;i<n_length;i++){ | |||
| train_html += `<div class="item" data-value="${data[i].VersionName}">${data[i].VersionName}</div>` | |||
| train_html += '</div>' | |||
| } | |||
| $("#job-version").append(train_html) | |||
| $(".ui.dropdown.selection.search.width70").removeClass("loading") | |||
| 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> | |||
| @@ -93,7 +93,7 @@ | |||
| <tr> | |||
| <td class="ti-text-form-label text-width80">标签</td> | |||
| <td class="ti-text-form-content"> | |||
| <div id="Label"> | |||
| <div id="Label" style="overflow: hidden;width: 95%;"> | |||
| </div> | |||
| @@ -162,6 +162,9 @@ function loadInfo(){ | |||
| $.get(`${url}show_model_info_api?name=${ID}`,(data)=>{ | |||
| let html = '' | |||
| for (let i=0;i<data.length;i++){ | |||
| if(!data[i].IsCanOper){ | |||
| $("#edit-pencil").css("display","none") | |||
| } | |||
| html += `<option value="${data[i].Version}">${data[i].Version}</option>` | |||
| } | |||
| $('#dropdown').append(html) | |||
| @@ -218,7 +221,8 @@ function tranSize(value){ | |||
| function editorFn(context){ | |||
| let id= context.dataset.id | |||
| let text = context.dataset.desc | |||
| $('#edit-td').replaceWith("<div id='edit-div' style='width:80%;display: inline-block;'><textarea id='textarea-value' rows='3' maxlength='255' style='width:80%;' id='edit-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>"); | |||
| 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>"); | |||
| } | |||
| function editorCancel(text,id){ | |||
| @@ -247,12 +251,18 @@ function renderInfo(obj,accObj,id){ | |||
| $('#edit-pencil').attr("data-desc",obj[key]) | |||
| } | |||
| else if(key==="Label"){ | |||
| let labelArray = obj[key].split(' ') | |||
| let html='' | |||
| for(let i=0;i<labelArray.length;i++){ | |||
| html += `<a class="ui label">${labelArray[i]}</a>` | |||
| $('#Label').empty() | |||
| if(obj[key]==='--'){ | |||
| $('#Label').text(obj[key]) | |||
| }else{ | |||
| let labelArray = obj[key].trim().replace(/ +/g,' ').split(' ') | |||
| let html='' | |||
| for(let i=0;i<labelArray.length;i++){ | |||
| html += `<a class="ui label" title="${labelArray[i]}">${labelArray[i]}</a>` | |||
| } | |||
| $('#Label').append(html) | |||
| } | |||
| $('#Label').append(html) | |||
| } | |||
| else{ | |||
| $(`#${key}`).text(obj[key]) | |||
| @@ -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/sessions v1.2.0 | |||
| 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 | |||
| # github.com/hashicorp/go-retryablehttp v0.6.6 | |||
| @@ -1,7 +1,7 @@ | |||
| <template> | |||
| <div> | |||
| <div class="ui container" id="header"> | |||
| <el-row style="margin-top:15px;" v-loading.fullscreen.lock="fullscreenLoading"> | |||
| <el-row style="margin-top:15px;"> | |||
| <el-table | |||
| ref="table" | |||
| :data="tableData" | |||
| @@ -9,7 +9,7 @@ | |||
| row-key="ID" | |||
| lazy | |||
| :load="load" | |||
| :tree-props="{children: 'children', hasChildren: 'hasChildren'}" | |||
| :tree-props="{children: 'Children', hasChildren: 'hasChildren'}" | |||
| :header-cell-style="tableHeaderStyle" | |||
| > | |||
| <el-table-column | |||
| @@ -95,8 +95,8 @@ | |||
| min-width="6.75%" | |||
| > | |||
| <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> | |||
| </template> | |||
| </el-table-column> | |||
| @@ -104,7 +104,7 @@ | |||
| <el-table-column label="操作" min-width="18%" align="center"> | |||
| <template slot-scope="scope"> | |||
| <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 :class="{'disabled':!scope.row.IsCanOper}" @click="deleteModel(scope.row.ID,scope.row.cName)">删除</a> | |||
| </div> | |||
| @@ -141,38 +141,46 @@ export default { | |||
| }, | |||
| data() { | |||
| return { | |||
| currentPage:1, | |||
| pageSize:10, | |||
| totalNum:0, | |||
| params:{page:0,pageSize:10}, | |||
| tableData: [], | |||
| fullscreenLoading: false, | |||
| url:'', | |||
| isLoading:true, | |||
| loadNodeMap:new Map(), | |||
| submitId:{} | |||
| submitId:{}, | |||
| defaultAvatar:'/user/avatar/Ghost/-1', | |||
| defaultAvatarName:'Ghost', | |||
| data:'' | |||
| }; | |||
| }, | |||
| methods: { | |||
| load(tree, treeNode, resolve) { | |||
| this.loadNodeMap.set(tree.cName, tree.ID) | |||
| 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}){ | |||
| if(rowIndex===0){ | |||
| @@ -187,26 +195,32 @@ export default { | |||
| this.params.page = val | |||
| this.getModelList() | |||
| }, | |||
| showcreateVue(name,version){ | |||
| showcreateVue(name,version,label){ | |||
| $('.ui.modal.second') | |||
| .modal({ | |||
| centered: false, | |||
| onShow:function(){ | |||
| $('#model_header').text("创建模型新版本") | |||
| $('input[name="Name"]').addClass('model_disabled') | |||
| $('input[name="Name"]').attr('readonly','readonly') | |||
| $('input[name="Version"]').addClass('model_disabled') | |||
| $('.ui.dimmer').css({"background-color":"rgb(136, 136, 136,0.7)"}) | |||
| $("#job-name").empty() | |||
| $('#name').val(name) | |||
| $('#label').val(label) | |||
| let version_string = versionAdd(version) | |||
| $('#version').val(version_string) | |||
| loadTrainList() | |||
| }, | |||
| onHide:function(){ | |||
| document.getElementById("formId").reset(); | |||
| $('input[name="Name"]').removeClass('model_disabled') | |||
| $('input[name="Name"]').removeAttr('readonly') | |||
| $('#choice_model').dropdown('clear') | |||
| $('#choice_version').dropdown('clear') | |||
| $('.ui.dimmer').css({"background-color":""}) | |||
| $('.ui.error.message').text() | |||
| $('.ui.error.message').css('display','none') | |||
| } | |||
| }) | |||
| .modal('show') | |||
| @@ -241,13 +255,13 @@ export default { | |||
| $("#verionname").removeClass("error") | |||
| } | |||
| return true | |||
| }, | |||
| submit(){ | |||
| let context = this | |||
| let flag= this.check() | |||
| console.log(flag,context) | |||
| if(flag){ | |||
| let cName = $("input[name='Name']").val() | |||
| let version = $("input[name='Version']").val() | |||
| let data = $("#formId").serialize() | |||
| $("#mask").css({"display":"block","z-index":"9999"}) | |||
| $.ajax({ | |||
| @@ -255,6 +269,7 @@ export default { | |||
| type:'POST', | |||
| data:data, | |||
| success:function(res){ | |||
| // context.loadrefresh1(row) | |||
| context.getModelList() | |||
| $('.ui.modal.second').modal('hide') | |||
| }, | |||
| @@ -272,9 +287,33 @@ export default { | |||
| return false | |||
| } | |||
| }, | |||
| loadrefresh(row){ | |||
| const store = this.$refs.table.store | |||
| if(!this.loadNodeMap.get(row.cName)){ | |||
| const parent = store.states.data | |||
| const index = parent.findIndex(child => child.ID == row.ID) | |||
| parent.splice(index, 1) | |||
| }else{ | |||
| let {tree,treeNode,resolve} = this.loadNodeMap.get(row.cName) | |||
| 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){ | |||
| let tree={cName:name} | |||
| let row={cName:name,ID:id} | |||
| let _this = this | |||
| let flag=1 | |||
| $('.ui.basic.modal.first') | |||
| @@ -287,8 +326,8 @@ export default { | |||
| params:{ | |||
| ID:id | |||
| }}).then((res)=>{ | |||
| _this.getModelList() | |||
| _this.loadrefresh(tree) | |||
| _this.loadrefresh(row) | |||
| // _this.getModelList() | |||
| }) | |||
| flag = true | |||
| }, | |||
| @@ -302,46 +341,29 @@ export default { | |||
| }) | |||
| .modal('show') | |||
| }, | |||
| loadrefresh(tree){ | |||
| 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 | |||
| } | |||
| let id = this.loadNodeMap.get(tree.cName) | |||
| this.$set(this.$refs.table.store.states.lazyTreeNodeMap, id, tableData) | |||
| }) | |||
| }, | |||
| getModelList(){ | |||
| this.fullscreenLoading = false | |||
| 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) | |||
| } | |||
| 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 | |||
| this.fullscreenLoading = false | |||
| }) | |||
| }, | |||
| }, | |||
| @@ -378,8 +400,6 @@ export default { | |||
| return size+unitArr[index]; | |||
| } | |||
| } | |||
| }, | |||
| mounted() { | |||
| this.submitId = document.getElementById("submitId") | |||
| @@ -467,7 +487,7 @@ export default { | |||
| margin-right: 3px; | |||
| 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{ | |||
| font-family: element-icons!important; | |||
| speak: none; | |||
| @@ -3745,6 +3745,7 @@ function initFilterBranchTagDropdown(selector) { | |||
| }); | |||
| }); | |||
| $data.remove(); | |||
| console.log("-this",this) | |||
| new Vue({ | |||
| delimiters: ['${', '}'], | |||
| el: this, | |||
| @@ -4130,4 +4131,9 @@ function initDropDown() { | |||
| $('.question.circle.icon').hover(function(){ | |||
| $(this).popup('show') | |||
| $('.ui.popup.mini.top.center').css({"border-color":'rgba(50, 145, 248, 100)',"color":"rgba(3, 102, 214, 100)","border-radius":"5px","border-shadow":"none"}) | |||
| }); | |||
| }); | |||
| //云脑详情页面跳转回上一个页面 | |||
| $(".section.backTodeBug").attr("href",localStorage.getItem('all')) | |||
| //新建调试取消跳转 | |||
| $(".ui.button.cancel").attr("href",localStorage.getItem('all')) | |||
| @@ -549,8 +549,8 @@ display: block; | |||
| cursor: pointer; | |||
| pointer-events: none; | |||
| } | |||
| .letf2{ | |||
| margin-left: -2px; | |||
| .left2{ | |||
| margin-left: -2px !important; | |||
| } | |||
| .width70{ | |||
| width: 70% !important; | |||
| @@ -578,3 +578,10 @@ display: block; | |||
| .mglf{ | |||
| margin-left:0.5em !important; | |||
| } | |||
| .tooltips{ | |||
| display: inline-block; | |||
| margin-left: 5.5rem; | |||
| font-size: 12px; | |||
| margin-top: 0.7rem; | |||
| color: #888888; | |||
| } | |||