├── program ├── util │ ├── map.go │ ├── array.go │ └── directory.go ├── game │ ├── player │ │ ├── bind.go │ │ ├── handler.go │ │ ├── sender.go │ │ └── player.go │ ├── types.go │ ├── games │ │ └── factory.go │ ├── bind.go │ ├── room.go │ ├── iplayer.go │ ├── igame.go │ └── msg │ │ └── msg.go ├── connection │ └── websocket.go └── model │ └── User.go ├── vendor └── github.com │ ├── BurntSushi │ └── toml │ │ ├── session.vim │ │ ├── .gitignore │ │ ├── COMPATIBLE │ │ ├── .travis.yml │ │ ├── Makefile │ │ ├── COPYING │ │ ├── cmd │ │ ├── tomlv │ │ │ └── COPYING │ │ ├── toml-test-decoder │ │ │ └── COPYING │ │ └── toml-test-encoder │ │ │ └── COPYING │ │ ├── encoding_types_1.1.go │ │ ├── encoding_types.go │ │ ├── doc.go │ │ ├── type_check.go │ │ ├── decode_meta.go │ │ ├── README.md │ │ ├── type_fields.go │ │ ├── decode.go │ │ ├── encode.go │ │ └── parse.go │ └── gorilla │ └── websocket │ ├── AUTHORS │ ├── trace_17.go │ ├── .gitignore │ ├── .travis.yml │ ├── mask_safe.go │ ├── conn_write.go │ ├── client_clone.go │ ├── trace.go │ ├── conn_write_legacy.go │ ├── LICENSE │ ├── mask.go │ ├── client_clone_legacy.go │ ├── json.go │ ├── proxy.go │ ├── prepared.go │ ├── compression.go │ ├── README.md │ ├── util.go │ ├── doc.go │ ├── client.go │ ├── server.go │ └── x_net_proxy.go ├── config ├── config.toml └── config.go ├── .gitignore ├── views ├── static │ ├── js │ │ ├── util.js │ │ └── home.js │ └── css │ │ └── index.css └── pages │ └── index.html ├── README.md ├── Gopkg.toml ├── main.go └── Gopkg.lock /program/util/map.go: -------------------------------------------------------------------------------- 1 | package util 2 | -------------------------------------------------------------------------------- /program/util/array.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/session.vim: -------------------------------------------------------------------------------- 1 | au BufWritePost *.go silent!make tags > /dev/null 2>&1 2 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/.gitignore: -------------------------------------------------------------------------------- 1 | TAGS 2 | tags 3 | .*.swp 4 | tomlcheck/tomlcheck 5 | toml.test 6 | -------------------------------------------------------------------------------- /config/config.toml: -------------------------------------------------------------------------------- 1 | [Database] 2 | host="127.0.0.1" 3 | port=3306 4 | dbname="chess_game" 5 | username="root" 6 | password="root" 7 | 8 | 9 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/COMPATIBLE: -------------------------------------------------------------------------------- 1 | Compatible with TOML version 2 | [v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md) 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | .idea 14 | vendor -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Gorilla WebSocket authors for copyright 2 | # purposes. 3 | # 4 | # Please keep the list sorted. 5 | 6 | Gary Burd 7 | Google LLC (https://opensource.google.com/) 8 | Joachim Bauch 9 | 10 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - 1.2 5 | - 1.3 6 | - 1.4 7 | - 1.5 8 | - 1.6 9 | - tip 10 | install: 11 | - go install ./... 12 | - go get github.com/BurntSushi/toml-test 13 | script: 14 | - export PATH="$PATH:$HOME/gopath/bin" 15 | - make test 16 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/trace_17.go: -------------------------------------------------------------------------------- 1 | // +build !go1.8 2 | 3 | package websocket 4 | 5 | import ( 6 | "crypto/tls" 7 | "net/http/httptrace" 8 | ) 9 | 10 | func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error { 11 | return doHandshake(tlsConn, cfg) 12 | } 13 | -------------------------------------------------------------------------------- /views/static/js/util.js: -------------------------------------------------------------------------------- 1 | String.format = function () { 2 | if (arguments.length == 0) 3 | return null; 4 | 5 | var str = arguments[0]; 6 | for (var i = 1; i < arguments.length; i++) { 7 | var re = new RegExp('\\{' + (i - 1) + '\\}', 'gm'); 8 | str = str.replace(re, arguments[i]); 9 | } 10 | 11 | return str; 12 | } -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | go install ./... 3 | 4 | test: install 5 | go test -v 6 | toml-test toml-test-decoder 7 | toml-test -encoder toml-test-encoder 8 | 9 | fmt: 10 | gofmt -w *.go */*.go 11 | colcheck *.go */*.go 12 | 13 | tags: 14 | find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS 15 | 16 | push: 17 | git push origin master 18 | git push github master 19 | 20 | -------------------------------------------------------------------------------- /program/util/directory.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | ) 7 | 8 | var OsType = runtime.GOOS 9 | 10 | func GetConfigFilePath() string{ 11 | if OsType == "windows"{ 12 | return getProjectPath()+"\\config\\config.toml" 13 | }else{ 14 | return getProjectPath()+"/config/config.toml" 15 | } 16 | } 17 | 18 | func getProjectPath() string{ 19 | path,_ := os.Getwd() 20 | return path 21 | } 22 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | .idea/ 25 | *.iml 26 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - go: 1.7.x 7 | - go: 1.8.x 8 | - go: 1.9.x 9 | - go: 1.10.x 10 | - go: 1.11.x 11 | - go: tip 12 | allow_failures: 13 | - go: tip 14 | 15 | script: 16 | - go get -t -v ./... 17 | - diff -u <(echo -n) <(gofmt -d .) 18 | - go vet $(go list ./... | grep -v /vendor/) 19 | - go test -v -race ./... 20 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/mask_safe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of 2 | // this source code is governed by a BSD-style license that can be found in the 3 | // LICENSE file. 4 | 5 | // +build appengine 6 | 7 | package websocket 8 | 9 | func maskBytes(key [4]byte, pos int, b []byte) int { 10 | for i := range b { 11 | b[i] ^= key[pos&3] 12 | pos++ 13 | } 14 | return pos & 3 15 | } 16 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/conn_write.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.8 6 | 7 | package websocket 8 | 9 | import "net" 10 | 11 | func (c *Conn) writeBufs(bufs ...[]byte) error { 12 | b := net.Buffers(bufs) 13 | _, err := b.WriteTo(c.conn) 14 | return err 15 | } 16 | -------------------------------------------------------------------------------- /program/game/player/bind.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | type PlayerUserDic map[int]*Player 4 | 5 | var playerUserDic PlayerUserDic 6 | 7 | func init(){ 8 | playerUserDic = make(PlayerUserDic) 9 | } 10 | 11 | func GetPlayer(userID int) *Player{ 12 | p,ok := playerUserDic[userID] 13 | if ok { 14 | return p 15 | }else{ 16 | return nil 17 | } 18 | } 19 | 20 | func SetPlayer(userId int,p *Player){ 21 | playerUserDic[userId] = p 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/client_clone.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.8 6 | 7 | package websocket 8 | 9 | import "crypto/tls" 10 | 11 | func cloneTLSConfig(cfg *tls.Config) *tls.Config { 12 | if cfg == nil { 13 | return &tls.Config{} 14 | } 15 | return cfg.Clone() 16 | } 17 | -------------------------------------------------------------------------------- /program/game/types.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import "github.com/wqtapp/pokergame" 4 | 5 | func IsDoudizhuTypeBiger(type1 int,type2 int) bool{ 6 | if type1 == pokergame.LANDLORD_SET_TYPE_JOKER_BOMB && type2 != pokergame.LANDLORD_SET_TYPE_JOKER_BOMB { 7 | return true 8 | } 9 | 10 | if type1 == pokergame.LANDLORD_SET_TYPE_COMMON_BOMB && type2 != pokergame.LANDLORD_SET_TYPE_JOKER_BOMB && type2 != pokergame.LANDLORD_SET_TYPE_COMMON_BOMB { 11 | return true 12 | } 13 | 14 | return false 15 | } -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/trace.go: -------------------------------------------------------------------------------- 1 | // +build go1.8 2 | 3 | package websocket 4 | 5 | import ( 6 | "crypto/tls" 7 | "net/http/httptrace" 8 | ) 9 | 10 | func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error { 11 | if trace.TLSHandshakeStart != nil { 12 | trace.TLSHandshakeStart() 13 | } 14 | err := doHandshake(tlsConn, cfg) 15 | if trace.TLSHandshakeDone != nil { 16 | trace.TLSHandshakeDone(tlsConn.ConnectionState(), err) 17 | } 18 | return err 19 | } 20 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/conn_write_legacy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !go1.8 6 | 7 | package websocket 8 | 9 | func (c *Conn) writeBufs(bufs ...[]byte) error { 10 | for _, buf := range bufs { 11 | if len(buf) > 0 { 12 | if _, err := c.conn.Write(buf); err != nil { 13 | return err 14 | } 15 | } 16 | } 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/BurntSushi/toml" 5 | "fmt" 6 | "landlord/program/util" 7 | ) 8 | 9 | //订制配置文件解析载体 10 | type Config struct{ 11 | Database *Database 12 | } 13 | 14 | type Database struct { 15 | Host string 16 | Port int 17 | DbName string 18 | Username string 19 | Password string 20 | } 21 | 22 | var Con *Config=new (Config) 23 | 24 | func init(){ 25 | //读取配置文件 26 | _, err := toml.DecodeFile(util.GetConfigFilePath(),Con) 27 | if err!=nil{ 28 | fmt.Println(err) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /program/game/games/factory.go: -------------------------------------------------------------------------------- 1 | package games 2 | 3 | import ( 4 | "landlord/program/game/games/doudizhu" 5 | "landlord/program/game" 6 | ) 7 | 8 | /** 9 | *该包用于解决game和doudizhu包循环依赖问题 10 | */ 11 | func NewGame(gameID int,baseScore int) game.IGame { 12 | switch gameID { 13 | case game.GAME_TYPE_OF_DOUDOZHU: 14 | return doudizhu.GetDoudizhu(baseScore) 15 | case game.GAME_TYPE_OF_SHENGJI: 16 | return nil 17 | case game.GAME_TYPE_OF_BAOHUANG: 18 | return nil 19 | case game.GAME_TYPE_OF_ZHAJINHUA: 20 | return nil 21 | default: 22 | return nil 23 | } 24 | } -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/encoding_types_1.1.go: -------------------------------------------------------------------------------- 1 | // +build !go1.2 2 | 3 | package toml 4 | 5 | // These interfaces were introduced in Go 1.2, so we add them manually when 6 | // compiling for Go 1.1. 7 | 8 | // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here 9 | // so that Go 1.1 can be supported. 10 | type TextMarshaler interface { 11 | MarshalText() (text []byte, err error) 12 | } 13 | 14 | // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined 15 | // here so that Go 1.1 can be supported. 16 | type TextUnmarshaler interface { 17 | UnmarshalText(text []byte) error 18 | } 19 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/encoding_types.go: -------------------------------------------------------------------------------- 1 | // +build go1.2 2 | 3 | package toml 4 | 5 | // In order to support Go 1.1, we define our own TextMarshaler and 6 | // TextUnmarshaler types. For Go 1.2+, we just alias them with the 7 | // standard library interfaces. 8 | 9 | import ( 10 | "encoding" 11 | ) 12 | 13 | // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here 14 | // so that Go 1.1 can be supported. 15 | type TextMarshaler encoding.TextMarshaler 16 | 17 | // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined 18 | // here so that Go 1.1 can be supported. 19 | type TextUnmarshaler encoding.TextUnmarshaler 20 | -------------------------------------------------------------------------------- /program/connection/websocket.go: -------------------------------------------------------------------------------- 1 | package connection 2 | 3 | import ( 4 | "sync" 5 | "github.com/gorilla/websocket" 6 | ) 7 | 8 | type WebSocketConnection struct { 9 | sync.RWMutex 10 | Conn *websocket.Conn 11 | } 12 | 13 | func NewWebSocketConnection(conn *websocket.Conn) *WebSocketConnection{ 14 | return &WebSocketConnection{ 15 | Conn:conn, 16 | } 17 | } 18 | 19 | func (conn *WebSocketConnection)SendMsg(msg []byte){ 20 | conn.Lock() 21 | defer conn.Unlock() 22 | conn.Conn.WriteMessage(websocket.TextMessage,msg) 23 | } 24 | 25 | func (conn *WebSocketConnection)SendMsgWithType(msgType int,msg []byte){ 26 | conn.Lock() 27 | defer conn.Unlock() 28 | conn.Conn.WriteMessage(msgType,msg) 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # landlord 2 | 3 | #### 项目介绍 4 | GO 实现的斗地主游戏,游戏代码规划兼容多中棋牌游戏,暂时只是先斗地主游戏 5 | 6 | #### 软件架构 7 | 使用go语言构建 8 | 包管理工具使用dep 9 | 10 | 11 | #### 安装教程 12 | 13 | 1. git clone https://www.github.com/wqtapp/landlord 14 | 2. go run main.go 15 | 3. 浏览器开三个窗口localhost:8888,模拟游戏 16 | 17 | #### 使用说明 18 | 19 | 1. 只使用默认生成的玩家,从第一个登陆玩家开始,三个一组进行游戏,没有用户登录系统和计算系统 20 | 2. 叫地主采用叫地主和抢地主模式,不是叫3分模式 21 | 3. 无人叫地主将重新发牌 22 | 4. 支持叫地主倒计时和出牌倒计时 23 | 5. 支持出炸弹自动翻倍 24 | 6. 支持倒计时结束自动出牌,优先不拆拍原则 25 | 7. 支持复杂牌型比如333444555KKK,3333444455667788等所有复杂但合理的牌型 26 | 27 | #### 注意事项 28 | 29 | 1.用goland调试的话,请直接打开项目目录,不要打开项目的上级目录,会导致路由不到html页面而报404 page not found 30 | 31 | #### 参与贡献 32 | 33 | 1. Fork 本项目 34 | 2. 新建 Feat_xxx 分支 35 | 3. 提交代码 36 | 4. 新建 Pull Request 37 | -------------------------------------------------------------------------------- /program/model/User.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type User struct { 4 | Id int `gorm:"column:id;primary_key;auto_increment;"` 5 | NickName string `gorm:"column:nickname;unique;type:varchar(100);index:name;not null"` 6 | Avatar string `gorm:"column:avatar;type:varchar(255);not null"` 7 | password string `gorm:"column:password;type:varchar(255);not null"` 8 | Phone string `gorm:"column:phone;type:char(11);unique;index:phone;not null"` 9 | RegisterTime string `gorm:"column:register_time;type:datetime;not null"` 10 | Level int `gorm:"column:level;type:smallint;not null;default:1"` 11 | Score int `gorm:"column:score;not null;default:1"` 12 | GameCard int `gorm:column:game_card;not null;default:10` 13 | } 14 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [prune] 29 | go-tests = true 30 | unused-packages = true 31 | -------------------------------------------------------------------------------- /program/game/player/handler.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import ( 4 | "fmt" 5 | "runtime/debug" 6 | "github.com/gorilla/websocket" 7 | "sync" 8 | "github.com/sirupsen/logrus" 9 | "strconv" 10 | ) 11 | 12 | func HandlerUserMsg(wg *sync.WaitGroup,con *websocket.Conn,currPlayer *Player) { 13 | defer wg.Done() 14 | defer func(){ 15 | if p := recover();p != nil{ 16 | fmt.Printf("panic recover! p: %v", p) 17 | debug.PrintStack() 18 | } 19 | }() 20 | for{ 21 | msgType,msg,err := con.ReadMessage() 22 | if err == nil{ 23 | switch msgType { 24 | case websocket.TextMessage: 25 | //同桌用户交流,包含对话流程和出牌流程 26 | currPlayer.ResolveMsg(msg) 27 | case websocket.CloseMessage: 28 | logrus.Info("玩家:"+strconv.Itoa(currPlayer.GetPlayerUser().Id)+"断开链接") 29 | break 30 | //离开桌子流程,后续包含断线保持,自动出牌 31 | default: 32 | 33 | } 34 | }else{ 35 | break 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /program/game/player/sender.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import ( 4 | "fmt" 5 | "landlord/program/game/msg" 6 | "log" 7 | ) 8 | 9 | func SendPlayerCards(curPlayer *Player){ 10 | json,err := msg.NewSendCardMsg(curPlayer.PokerCards) 11 | if err != nil{ 12 | fmt.Println(err.Error()) 13 | return 14 | } 15 | curPlayer.SendMsg(json) 16 | } 17 | 18 | func SendMsgToPlayer(p *Player,msgType int,hints string){ 19 | 20 | var newMsg []byte 21 | var err error 22 | switch msgType { 23 | case msg.MSG_TYPE_OF_CALL_SCORE: 24 | newMsg,err = msg.NewCallScoreMsg() 25 | case msg.MSG_TYPE_OF_CALL_SCORE_TIME_OUT: 26 | newMsg,err = msg.NewCallScoreTimeOutMsg() 27 | case msg.MSG_TYPE_OF_PLAY_CARD: 28 | newMsg,err = msg.NewPlayCardMsg() 29 | case msg.MSG_TYPE_OF_PLAY_ERROR: 30 | newMsg,err = msg.NewPlayCardsErrorMsg(hints) 31 | case msg.MSG_TYPE_OF_PLAY_CARD_SUCCESS: 32 | newMsg,err = msg.NewPlayCardSuccessMsg() 33 | case msg.MSG_TYPE_OF_LOGIN: 34 | newMsg,err = msg.NewLoginMsg(p.User.Id,"登陆成功") 35 | default: 36 | return 37 | } 38 | 39 | if err == nil{ 40 | p.SendMsg(newMsg) 41 | }else{ 42 | log.Fatal(err.Error()) 43 | } 44 | } -------------------------------------------------------------------------------- /program/game/bind.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "sync" 5 | "errors" 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | type PlayerGameDic struct { 10 | sync.RWMutex 11 | Dic map[IPlayer]IGame 12 | } 13 | 14 | var dic PlayerGameDic 15 | 16 | func init(){ 17 | dic = PlayerGameDic{ 18 | Dic: make(map[IPlayer]IGame), 19 | } 20 | } 21 | 22 | func BindPlayerGame(p IPlayer,game IGame){ 23 | dic.Lock() 24 | defer dic.Unlock() 25 | _,ok := dic.Dic[p] 26 | if ok { 27 | logrus.Error("该玩家已绑定游戏,绑定失败") 28 | }else{ 29 | dic.Dic[p] = game 30 | } 31 | } 32 | 33 | func UnbindPlayerGame(p IPlayer,game IGame){ 34 | dic.Lock() 35 | defer dic.Unlock() 36 | currGame,ok := dic.Dic[p] 37 | if ok { 38 | if currGame == game{ 39 | delete(dic.Dic,p) 40 | }else{ 41 | logrus.Error("玩家已绑定游戏,不是当前给定的游戏,解绑失败") 42 | } 43 | }else{ 44 | logrus.Error("玩家未绑定游戏,解绑失败") 45 | } 46 | } 47 | 48 | func GetPlayerGame(p IPlayer) (IGame,error){ 49 | dic.RLock() 50 | defer dic.RUnlock() 51 | game,ok := dic.Dic[p] 52 | if ok { 53 | return game,nil 54 | }else{ 55 | logrus.Error("该player没有关联的game") 56 | return nil,errors.New("该player没有关联的game") 57 | } 58 | } -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package toml provides facilities for decoding and encoding TOML configuration 3 | files via reflection. There is also support for delaying decoding with 4 | the Primitive type, and querying the set of keys in a TOML document with the 5 | MetaData type. 6 | 7 | The specification implemented: https://github.com/toml-lang/toml 8 | 9 | The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify 10 | whether a file is a valid TOML document. It can also be used to print the 11 | type of each key in a TOML document. 12 | 13 | Testing 14 | 15 | There are two important types of tests used for this package. The first is 16 | contained inside '*_test.go' files and uses the standard Go unit testing 17 | framework. These tests are primarily devoted to holistically testing the 18 | decoder and encoder. 19 | 20 | The second type of testing is used to verify the implementation's adherence 21 | to the TOML specification. These tests have been factored into their own 22 | project: https://github.com/BurntSushi/toml-test 23 | 24 | The reason the tests are in a separate project is so that they can be used by 25 | any implementation of TOML. Namely, it is language agnostic. 26 | */ 27 | package toml 28 | -------------------------------------------------------------------------------- /program/game/room.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "sync" 5 | "errors" 6 | "github.com/sirupsen/logrus" 7 | "strconv" 8 | ) 9 | 10 | type GameList struct { 11 | list map[int]IGame 12 | sync.RWMutex 13 | } 14 | 15 | type Room map[int]*GameList //分游戏类型,每种游戏单独切片 16 | 17 | var room Room = nil 18 | 19 | func init(){ 20 | room = Room{} 21 | } 22 | //获得全局room单例对象 23 | func GetRoom() Room { 24 | return room 25 | } 26 | //将game加入房间,并返回game的id 27 | func (r Room)AddGame(gameType int,game IGame) int{ 28 | list,ok := r[gameType] 29 | if !ok { 30 | r[gameType] = &GameList{ 31 | list:make(map[int]IGame), 32 | } 33 | list = r[gameType] 34 | } 35 | //todo 暂时用数量来表示 36 | 37 | list.Lock() 38 | gameCount := len(list.list) 39 | logrus.Info("新游戏加入房间:"+GetGameName(gameType)+strconv.Itoa(gameType)+strconv.Itoa(gameCount)) 40 | list.list[gameCount] = game 41 | list.Unlock() 42 | 43 | return gameCount 44 | } 45 | //根据游戏类型和gameId查找game 46 | func (r Room)GetGame(gameType int,gameID int) (IGame,error){ 47 | list,ok := r[gameType] 48 | if ok{ 49 | game,ok := list.list[gameID] 50 | if ok { 51 | return game,nil 52 | }else{ 53 | logrus.Error("不存在该游戏") 54 | return nil,errors.New("不存在该游戏") 55 | } 56 | }else{ 57 | logrus.Error("不存在该类型的游戏") 58 | return nil,errors.New("不存在该类型的游戏") 59 | } 60 | } -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/mask.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of 2 | // this source code is governed by a BSD-style license that can be found in the 3 | // LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package websocket 8 | 9 | import "unsafe" 10 | 11 | const wordSize = int(unsafe.Sizeof(uintptr(0))) 12 | 13 | func maskBytes(key [4]byte, pos int, b []byte) int { 14 | // Mask one byte at a time for small buffers. 15 | if len(b) < 2*wordSize { 16 | for i := range b { 17 | b[i] ^= key[pos&3] 18 | pos++ 19 | } 20 | return pos & 3 21 | } 22 | 23 | // Mask one byte at a time to word boundary. 24 | if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 { 25 | n = wordSize - n 26 | for i := range b[:n] { 27 | b[i] ^= key[pos&3] 28 | pos++ 29 | } 30 | b = b[n:] 31 | } 32 | 33 | // Create aligned word size key. 34 | var k [wordSize]byte 35 | for i := range k { 36 | k[i] = key[(pos+i)&3] 37 | } 38 | kw := *(*uintptr)(unsafe.Pointer(&k)) 39 | 40 | // Mask one word at a time. 41 | n := (len(b) / wordSize) * wordSize 42 | for i := 0; i < n; i += wordSize { 43 | *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw 44 | } 45 | 46 | // Mask one byte at a time for remaining bytes. 47 | b = b[n:] 48 | for i := range b { 49 | b[i] ^= key[pos&3] 50 | pos++ 51 | } 52 | 53 | return pos & 3 54 | } 55 | -------------------------------------------------------------------------------- /program/game/iplayer.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "landlord/program/model" 5 | "github.com/wqtapp/poker" 6 | "github.com/wqtapp/pokergame" 7 | ) 8 | 9 | type IPlayer interface { 10 | //由game回调的方法 11 | PlayCardSuccess(cardIndexs []int) //出牌成功 12 | PlayCardError(err string) //出牌错误 13 | GetReadyStatus() bool 14 | GetAutoStatus() bool 15 | GetPlayerUser() *model.User 16 | GetIndex() int 17 | SetIndex(index int) 18 | SetPokerCards(cards poker.PokerSet) 19 | StartCallScore() 20 | StartPlay() 21 | IsOutOfCards() bool //是否出完牌 22 | SendMsg(msg []byte) 23 | 24 | //响应客户端请求的方法 25 | JoinGame(gameType int,gameId int) //加入游戏 26 | CreateGame(gameID int,baseScore int) //创建游戏 27 | LeaveGame() //离开游戏 28 | SayToOthers(msg []byte) //给同一游戏的其他玩家发消息 29 | SayToAnother(id int,msg []byte) //给同意游戏的特定索引玩家发消息 30 | ResolveMsg(msgB []byte) error //处理客户端消息 31 | Ready() //玩家准备 32 | UnReady() //玩家取消准备 33 | CallScore(score int) //抢地主 34 | PlayCards(cards []int) //出牌 35 | Pass() //过牌 36 | HintCards() //提示出牌 37 | GetPlayedCardIndexs() []int 38 | GetPlayerCards(indexs []int) poker.PokerSet 39 | 40 | SetPokerRecorder(recorder pokergame.IRecorder) 41 | SetPokerAnalyzer(analyzer pokergame.IAnalyzer) 42 | } 43 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/client_clone_legacy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !go1.8 6 | 7 | package websocket 8 | 9 | import "crypto/tls" 10 | 11 | // cloneTLSConfig clones all public fields except the fields 12 | // SessionTicketsDisabled and SessionTicketKey. This avoids copying the 13 | // sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a 14 | // config in active use. 15 | func cloneTLSConfig(cfg *tls.Config) *tls.Config { 16 | if cfg == nil { 17 | return &tls.Config{} 18 | } 19 | return &tls.Config{ 20 | Rand: cfg.Rand, 21 | Time: cfg.Time, 22 | Certificates: cfg.Certificates, 23 | NameToCertificate: cfg.NameToCertificate, 24 | GetCertificate: cfg.GetCertificate, 25 | RootCAs: cfg.RootCAs, 26 | NextProtos: cfg.NextProtos, 27 | ServerName: cfg.ServerName, 28 | ClientAuth: cfg.ClientAuth, 29 | ClientCAs: cfg.ClientCAs, 30 | InsecureSkipVerify: cfg.InsecureSkipVerify, 31 | CipherSuites: cfg.CipherSuites, 32 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 33 | ClientSessionCache: cfg.ClientSessionCache, 34 | MinVersion: cfg.MinVersion, 35 | MaxVersion: cfg.MaxVersion, 36 | CurvePreferences: cfg.CurvePreferences, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "encoding/json" 9 | "io" 10 | ) 11 | 12 | // WriteJSON writes the JSON encoding of v as a message. 13 | // 14 | // Deprecated: Use c.WriteJSON instead. 15 | func WriteJSON(c *Conn, v interface{}) error { 16 | return c.WriteJSON(v) 17 | } 18 | 19 | // WriteJSON writes the JSON encoding of v as a message. 20 | // 21 | // See the documentation for encoding/json Marshal for details about the 22 | // conversion of Go values to JSON. 23 | func (c *Conn) WriteJSON(v interface{}) error { 24 | w, err := c.NextWriter(TextMessage) 25 | if err != nil { 26 | return err 27 | } 28 | err1 := json.NewEncoder(w).Encode(v) 29 | err2 := w.Close() 30 | if err1 != nil { 31 | return err1 32 | } 33 | return err2 34 | } 35 | 36 | // ReadJSON reads the next JSON-encoded message from the connection and stores 37 | // it in the value pointed to by v. 38 | // 39 | // Deprecated: Use c.ReadJSON instead. 40 | func ReadJSON(c *Conn, v interface{}) error { 41 | return c.ReadJSON(v) 42 | } 43 | 44 | // ReadJSON reads the next JSON-encoded message from the connection and stores 45 | // it in the value pointed to by v. 46 | // 47 | // See the documentation for the encoding/json Unmarshal function for details 48 | // about the conversion of JSON to a Go value. 49 | func (c *Conn) ReadJSON(v interface{}) error { 50 | _, r, err := c.NextReader() 51 | if err != nil { 52 | return err 53 | } 54 | err = json.NewDecoder(r).Decode(v) 55 | if err == io.EOF { 56 | // One value is expected in the message. 57 | err = io.ErrUnexpectedEOF 58 | } 59 | return err 60 | } 61 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/proxy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bufio" 9 | "encoding/base64" 10 | "errors" 11 | "net" 12 | "net/http" 13 | "net/url" 14 | "strings" 15 | ) 16 | 17 | type netDialerFunc func(network, addr string) (net.Conn, error) 18 | 19 | func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) { 20 | return fn(network, addr) 21 | } 22 | 23 | func init() { 24 | proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) { 25 | return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil 26 | }) 27 | } 28 | 29 | type httpProxyDialer struct { 30 | proxyURL *url.URL 31 | fowardDial func(network, addr string) (net.Conn, error) 32 | } 33 | 34 | func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) { 35 | hostPort, _ := hostPortNoPort(hpd.proxyURL) 36 | conn, err := hpd.fowardDial(network, hostPort) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | connectHeader := make(http.Header) 42 | if user := hpd.proxyURL.User; user != nil { 43 | proxyUser := user.Username() 44 | if proxyPassword, passwordSet := user.Password(); passwordSet { 45 | credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword)) 46 | connectHeader.Set("Proxy-Authorization", "Basic "+credential) 47 | } 48 | } 49 | 50 | connectReq := &http.Request{ 51 | Method: "CONNECT", 52 | URL: &url.URL{Opaque: addr}, 53 | Host: addr, 54 | Header: connectHeader, 55 | } 56 | 57 | if err := connectReq.Write(conn); err != nil { 58 | conn.Close() 59 | return nil, err 60 | } 61 | 62 | // Read response. It's OK to use and discard buffered reader here becaue 63 | // the remote server does not speak until spoken to. 64 | br := bufio.NewReader(conn) 65 | resp, err := http.ReadResponse(br, connectReq) 66 | if err != nil { 67 | conn.Close() 68 | return nil, err 69 | } 70 | 71 | if resp.StatusCode != 200 { 72 | conn.Close() 73 | f := strings.SplitN(resp.Status, " ", 2) 74 | return nil, errors.New(f[1]) 75 | } 76 | return conn, nil 77 | } 78 | -------------------------------------------------------------------------------- /views/static/css/index.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100%; 3 | width: 100%; 4 | background: beige; 5 | position: absolute 6 | } 7 | .divPlay { 8 | height:300px; 9 | width:100%; 10 | background: beige; 11 | position: relative; 12 | border:3px solid burlywood; 13 | } 14 | .divPlayer{ 15 | height: 100%; 16 | width: 15%; 17 | background: oldlace; 18 | border:3px solid burlywood; 19 | float: left; 20 | } 21 | .divNickName{ 22 | height: 100%; 23 | width: 49%; 24 | border:1px solid burlywood; 25 | float: left; 26 | text-align: center; 27 | } 28 | .divCharacter{ 29 | height: 100%; 30 | width: 49%; 31 | border:1px solid burlywood; 32 | float: left; 33 | text-align: center; 34 | } 35 | .divPlayerInfo{ 36 | height: 25%; 37 | width: 100%; 38 | border:1px solid burlywood; 39 | } 40 | .divPlayerMsg{ 41 | height: 20px; 42 | width: 100%; 43 | border:1px solid burlywood; 44 | text-align: center; 45 | overflow: hidden; 46 | } 47 | .divLoardCard{ 48 | height: 75%; 49 | width: 100%; 50 | border:1px solid burlywood; 51 | } 52 | .divPlayCards{ 53 | height: 100%; 54 | width: 68%; 55 | background: oldlace; 56 | border:3px solid burlywood; 57 | float: left; 58 | } 59 | .divCards { 60 | height:250px; 61 | width:100%; 62 | background: beige; 63 | position: relative; 64 | overflow: auto; 65 | border:3px solid burlywood; 66 | } 67 | .divCard { 68 | align-self:center; 69 | height: 100%; 70 | width: max-content; 71 | background:burlywood; 72 | position: relative; 73 | display: inline-block; 74 | border:1px solid aqua; 75 | } 76 | .divNum { 77 | height: 47%; 78 | width: 100%; 79 | position: relative; 80 | } 81 | .divColor{ 82 | height: 47%; 83 | width: 100%; 84 | align-self: center; 85 | position: relative; 86 | } 87 | .chooseCard{ 88 | height: 6%; 89 | width: 50%; 90 | position: relative; 91 | margin-left: 25%; 92 | margin-right: 25%; 93 | border: 1px solid aqua; 94 | } 95 | 96 | .chooseYes{ 97 | background-color: aqua; 98 | } 99 | 100 | .divButton { 101 | height: 150px; 102 | width: 100%; 103 | background: beige; 104 | position: relative; 105 | border:3px solid burlywood; 106 | } -------------------------------------------------------------------------------- /program/game/igame.go: -------------------------------------------------------------------------------- 1 | package game 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "github.com/wqtapp/pokergame" 6 | "github.com/wqtapp/poker" 7 | ) 8 | 9 | const ( 10 | GAME_TYPE_OF_DOUDOZHU = iota 11 | GAME_TYPE_OF_SHENGJI 12 | GAME_TYPE_OF_BAOHUANG 13 | GAME_TYPE_OF_ZHAJINHUA 14 | ) 15 | 16 | var gameIDNameDic map[int]string 17 | 18 | func init(){ 19 | gameIDNameDic = make(map[int]string) 20 | gameIDNameDic[GAME_TYPE_OF_DOUDOZHU] = "斗地主" 21 | gameIDNameDic[GAME_TYPE_OF_SHENGJI] = "升级" 22 | gameIDNameDic[GAME_TYPE_OF_BAOHUANG] = "保皇" 23 | gameIDNameDic[GAME_TYPE_OF_ZHAJINHUA] = "斗地主" 24 | } 25 | 26 | func GetGameName(gameID int) string{ 27 | name,ok := gameIDNameDic[gameID] 28 | if ok{ 29 | return name 30 | }else{ 31 | logrus.Error("未定义游戏名称") 32 | return "未定义游戏名称" 33 | } 34 | } 35 | 36 | //游戏使用接口类型,便于实现多态 37 | type IGame interface { 38 | GetGameID() int //获取游戏id 39 | GetGameName() string //获取游戏名称 40 | GetGameType() int //获取游戏类型 41 | GetLastCard() *LastCardsType //获取游戏最后出的牌 42 | 43 | AddPlayer(p IPlayer) error //游戏添加玩家 44 | RemovePlayer(p IPlayer) error //游戏移除玩家 45 | SayToOthers(p IPlayer,msg []byte) //跟其他玩家说话 46 | SayToAnother(p IPlayer,otherIndex int,msg []byte) //跟一个玩家说话 47 | PlayerReady(p IPlayer) //玩家准备 48 | PlayerUnReady(p IPlayer) //玩家取消准备 49 | PlayerCallScore(p IPlayer,score int) //玩家叫地主 50 | PlayerPlayCards(p IPlayer,cardsIndex []int) //玩家出牌 51 | PlayerPassCard(p IPlayer) //玩家过牌 52 | HintCards(p IPlayer) []int //提示玩家可出的牌 53 | BroadCastMsg(p IPlayer,msgType int,msg string) 54 | IsLastCardUserFinish() bool 55 | } 56 | 57 | type LastCardsType struct{ 58 | PlayerCardIndexs []int //扑克牌在出牌玩家所有牌中的index 59 | PlayerIndex int //出牌的玩家index 60 | Cards poker.PokerSet //出的牌 61 | PokerSetTypeInfo *pokergame.SetInfo 62 | } 63 | 64 | func NewLastCards(playerIndex int,cards poker.PokerSet,cardIndexs []int,setTypeInfo *pokergame.SetInfo) *LastCardsType { 65 | lastCards := &LastCardsType{ 66 | PlayerIndex:playerIndex, 67 | Cards:cards, 68 | PlayerCardIndexs:cardIndexs, 69 | PokerSetTypeInfo:setTypeInfo, 70 | } 71 | return lastCards 72 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/gorilla/websocket" 6 | "net/http" 7 | "strconv" 8 | "sync" 9 | "landlord/program/game/player" 10 | _ "github.com/jinzhu/gorm/dialects/mysql" 11 | "landlord/program/model" 12 | "landlord/program/game/msg" 13 | "landlord/program/game" 14 | "github.com/sirupsen/logrus" 15 | "github.com/jinzhu/gorm" 16 | "landlord/program/connection" 17 | ) 18 | 19 | var addr = flag.String("addr", "localhost:8888", "http service address") 20 | 21 | var upgrader = websocket.Upgrader{} // use default options 22 | 23 | type TempUser struct{ 24 | userID int 25 | sync.RWMutex 26 | } 27 | 28 | var user TempUser 29 | func echo(w http.ResponseWriter, r *http.Request) { 30 | 31 | con, err := upgrader.Upgrade(w, r, nil) 32 | if err != nil { 33 | logrus.Error("upgrade:", err) 34 | return 35 | } 36 | defer con.Close() 37 | 38 | var wg sync.WaitGroup 39 | wg.Add(1) 40 | //暂时用变量模拟用户登陆,后续从数据库读取用户信息,实例化用户,游戏过程中用redis来暂存游戏信息,用户推出后持久化到数据库 41 | user.Lock() 42 | user.userID++ 43 | nowId := user.userID-1 44 | user.Unlock() 45 | logrus.Info("玩家:"+strconv.Itoa(nowId)+"登陆游戏") 46 | currUser := &model.User{ 47 | Id:nowId, 48 | NickName:"玩家"+strconv.Itoa(nowId), 49 | Avatar:"no_avatar", 50 | } 51 | currPlayer := player.NewPlayer(currUser,connection.NewWebSocketConnection(con)) 52 | 53 | player.SendMsgToPlayer(currPlayer,msg.MSG_TYPE_OF_LOGIN,"用户登陆") 54 | 55 | shang := currPlayer.User.Id/3 56 | 57 | if currPlayer.User.Id%3 == 0{ 58 | currPlayer.CreateGame(game.GAME_TYPE_OF_DOUDOZHU,10) 59 | logrus.Info("玩家:"+strconv.Itoa(currPlayer.GetPlayerUser().Id)+"创建游戏:"+game.GetGameName(game.GAME_TYPE_OF_DOUDOZHU)) 60 | }else{ 61 | currPlayer.JoinGame(game.GAME_TYPE_OF_DOUDOZHU,shang) 62 | logrus.Info("玩家:"+strconv.Itoa(currPlayer.GetPlayerUser().Id)+"加入游戏:"+game.GetGameName(game.GAME_TYPE_OF_DOUDOZHU)+ 63 | strconv.Itoa(game.GAME_TYPE_OF_DOUDOZHU)+":"+strconv.Itoa(shang)) 64 | } 65 | 66 | wg.Add(1) 67 | //启动一个goroutine监听该客户端发来的消息 68 | go player.HandlerUserMsg(&wg,con,currPlayer) 69 | 70 | wg.Wait() 71 | } 72 | 73 | func home(w http.ResponseWriter, r *http.Request) { 74 | http.Redirect(w,r,"/pages/index.html",301) 75 | } 76 | 77 | func main() { 78 | flag.Parse() 79 | 80 | db,err := gorm.Open("mysql", "root:root@/chessgame?charset=utf8&parseTime=True&loc=Local") 81 | if err != nil{ 82 | logrus.Fatal(err.Error()) 83 | } 84 | db.AutoMigrate(&model.User{}) 85 | 86 | http.HandleFunc("/echo", echo) 87 | http.HandleFunc("/", home) 88 | http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./views/static")))) 89 | http.Handle("/pages/", http.StripPrefix("/pages/", http.FileServer(http.Dir("./views/pages")))) 90 | err = http.ListenAndServe(*addr, nil) 91 | if err != nil{ 92 | logrus.Fatal(err.Error()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/type_check.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | // tomlType represents any Go type that corresponds to a TOML type. 4 | // While the first draft of the TOML spec has a simplistic type system that 5 | // probably doesn't need this level of sophistication, we seem to be militating 6 | // toward adding real composite types. 7 | type tomlType interface { 8 | typeString() string 9 | } 10 | 11 | // typeEqual accepts any two types and returns true if they are equal. 12 | func typeEqual(t1, t2 tomlType) bool { 13 | if t1 == nil || t2 == nil { 14 | return false 15 | } 16 | return t1.typeString() == t2.typeString() 17 | } 18 | 19 | func typeIsHash(t tomlType) bool { 20 | return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash) 21 | } 22 | 23 | type tomlBaseType string 24 | 25 | func (btype tomlBaseType) typeString() string { 26 | return string(btype) 27 | } 28 | 29 | func (btype tomlBaseType) String() string { 30 | return btype.typeString() 31 | } 32 | 33 | var ( 34 | tomlInteger tomlBaseType = "Integer" 35 | tomlFloat tomlBaseType = "Float" 36 | tomlDatetime tomlBaseType = "Datetime" 37 | tomlString tomlBaseType = "String" 38 | tomlBool tomlBaseType = "Bool" 39 | tomlArray tomlBaseType = "Array" 40 | tomlHash tomlBaseType = "Hash" 41 | tomlArrayHash tomlBaseType = "ArrayHash" 42 | ) 43 | 44 | // typeOfPrimitive returns a tomlType of any primitive value in TOML. 45 | // Primitive values are: Integer, Float, Datetime, String and Bool. 46 | // 47 | // Passing a lexer item other than the following will cause a BUG message 48 | // to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime. 49 | func (p *parser) typeOfPrimitive(lexItem item) tomlType { 50 | switch lexItem.typ { 51 | case itemInteger: 52 | return tomlInteger 53 | case itemFloat: 54 | return tomlFloat 55 | case itemDatetime: 56 | return tomlDatetime 57 | case itemString: 58 | return tomlString 59 | case itemMultilineString: 60 | return tomlString 61 | case itemRawString: 62 | return tomlString 63 | case itemRawMultilineString: 64 | return tomlString 65 | case itemBool: 66 | return tomlBool 67 | } 68 | p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) 69 | panic("unreachable") 70 | } 71 | 72 | // typeOfArray returns a tomlType for an array given a list of types of its 73 | // values. 74 | // 75 | // In the current spec, if an array is homogeneous, then its type is always 76 | // "Array". If the array is not homogeneous, an error is generated. 77 | func (p *parser) typeOfArray(types []tomlType) tomlType { 78 | // Empty arrays are cool. 79 | if len(types) == 0 { 80 | return tomlArray 81 | } 82 | 83 | theType := types[0] 84 | for _, t := range types[1:] { 85 | if !typeEqual(theType, t) { 86 | p.panicf("Array contains values of type '%s' and '%s', but "+ 87 | "arrays must be homogeneous.", theType, t) 88 | } 89 | } 90 | return tomlArray 91 | } 92 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:b16fbfbcc20645cb419f78325bb2e85ec729b338e996a228124d68931a6f2a37" 6 | name = "github.com/BurntSushi/toml" 7 | packages = ["."] 8 | pruneopts = "UT" 9 | revision = "b26d9c308763d68093482582cea63d69be07a0f0" 10 | version = "v0.3.0" 11 | 12 | [[projects]] 13 | digest = "1:850c49ca338a10fec2cb9e78f793043ed23965489d09e30bcc19fe29719da313" 14 | name = "github.com/go-sql-driver/mysql" 15 | packages = ["."] 16 | pruneopts = "UT" 17 | revision = "a0583e0143b1624142adab07e0e97fe106d99561" 18 | version = "v1.3.0" 19 | 20 | [[projects]] 21 | digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d" 22 | name = "github.com/gorilla/websocket" 23 | packages = ["."] 24 | pruneopts = "UT" 25 | revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d" 26 | version = "v1.4.0" 27 | 28 | [[projects]] 29 | digest = "1:b6f4477f09f11785097a121811f3a90d8d26cab73c1aa71abda859bdf6ad2334" 30 | name = "github.com/jinzhu/gorm" 31 | packages = [ 32 | ".", 33 | "dialects/mysql", 34 | ] 35 | pruneopts = "UT" 36 | revision = "6ed508ec6a4ecb3531899a69cbc746ccf65a4166" 37 | version = "v1.9.1" 38 | 39 | [[projects]] 40 | branch = "master" 41 | digest = "1:fd97437fbb6b7dce04132cf06775bd258cce305c44add58eb55ca86c6c325160" 42 | name = "github.com/jinzhu/inflection" 43 | packages = ["."] 44 | pruneopts = "UT" 45 | revision = "04140366298a54a039076d798123ffa108fff46c" 46 | 47 | [[projects]] 48 | digest = "1:36a75ab002c1cfb43bcc5d2ec10fbdc2a1136e9246a65e535f1d827435f28f17" 49 | name = "github.com/sirupsen/logrus" 50 | packages = ["."] 51 | pruneopts = "UT" 52 | revision = "418b41d23a1bf978c06faea5313ba194650ac088" 53 | version = "v0.8.7" 54 | 55 | [[projects]] 56 | digest = "1:2cab41f59638fdb77dda96ab4f9b5860ef30f48967557254eba127e43c756f2e" 57 | name = "github.com/tidwall/gjson" 58 | packages = ["."] 59 | pruneopts = "UT" 60 | revision = "1e3f6aeaa5bad08d777ea7807b279a07885dd8b2" 61 | version = "v1.1.3" 62 | 63 | [[projects]] 64 | branch = "master" 65 | digest = "1:d3f968e2a2c9f8506ed44b01b605ade0176ba6cf73ff679073e77cfdef2c0d55" 66 | name = "github.com/tidwall/match" 67 | packages = ["."] 68 | pruneopts = "UT" 69 | revision = "1731857f09b1f38450e2c12409748407822dc6be" 70 | 71 | [[projects]] 72 | branch = "master" 73 | digest = "1:541ef6f87031e3e573252b7d15c8ead37729bd2074d80f2dbcd3573bf9501618" 74 | name = "github.com/wqtapp/poker" 75 | packages = ["."] 76 | pruneopts = "UT" 77 | revision = "fca6e2271d479d46d0193942572ebf6eabce3d39" 78 | 79 | [[projects]] 80 | branch = "master" 81 | digest = "1:bb7bd216967b00876a307d8903c79dca798e8552c7a5b63340532ea624e597f5" 82 | name = "github.com/wqtapp/pokergame" 83 | packages = ["."] 84 | pruneopts = "UT" 85 | revision = "554a0f6422271e91a40b39f26799a0646240a343" 86 | 87 | [solve-meta] 88 | analyzer-name = "dep" 89 | analyzer-version = 1 90 | input-imports = [ 91 | "github.com/BurntSushi/toml", 92 | "github.com/gorilla/websocket", 93 | "github.com/jinzhu/gorm", 94 | "github.com/jinzhu/gorm/dialects/mysql", 95 | "github.com/sirupsen/logrus", 96 | "github.com/tidwall/gjson", 97 | "github.com/wqtapp/poker", 98 | "github.com/wqtapp/pokergame", 99 | ] 100 | solver-name = "gps-cdcl" 101 | solver-version = 1 102 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/prepared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bytes" 9 | "net" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // PreparedMessage caches on the wire representations of a message payload. 15 | // Use PreparedMessage to efficiently send a message payload to multiple 16 | // connections. PreparedMessage is especially useful when compression is used 17 | // because the CPU and memory expensive compression operation can be executed 18 | // once for a given set of compression options. 19 | type PreparedMessage struct { 20 | messageType int 21 | data []byte 22 | mu sync.Mutex 23 | frames map[prepareKey]*preparedFrame 24 | } 25 | 26 | // prepareKey defines a unique set of options to cache prepared frames in PreparedMessage. 27 | type prepareKey struct { 28 | isServer bool 29 | compress bool 30 | compressionLevel int 31 | } 32 | 33 | // preparedFrame contains data in wire representation. 34 | type preparedFrame struct { 35 | once sync.Once 36 | data []byte 37 | } 38 | 39 | // NewPreparedMessage returns an initialized PreparedMessage. You can then send 40 | // it to connection using WritePreparedMessage method. Valid wire 41 | // representation will be calculated lazily only once for a set of current 42 | // connection options. 43 | func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) { 44 | pm := &PreparedMessage{ 45 | messageType: messageType, 46 | frames: make(map[prepareKey]*preparedFrame), 47 | data: data, 48 | } 49 | 50 | // Prepare a plain server frame. 51 | _, frameData, err := pm.frame(prepareKey{isServer: true, compress: false}) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | // To protect against caller modifying the data argument, remember the data 57 | // copied to the plain server frame. 58 | pm.data = frameData[len(frameData)-len(data):] 59 | return pm, nil 60 | } 61 | 62 | func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) { 63 | pm.mu.Lock() 64 | frame, ok := pm.frames[key] 65 | if !ok { 66 | frame = &preparedFrame{} 67 | pm.frames[key] = frame 68 | } 69 | pm.mu.Unlock() 70 | 71 | var err error 72 | frame.once.Do(func() { 73 | // Prepare a frame using a 'fake' connection. 74 | // TODO: Refactor code in conn.go to allow more direct construction of 75 | // the frame. 76 | mu := make(chan bool, 1) 77 | mu <- true 78 | var nc prepareConn 79 | c := &Conn{ 80 | conn: &nc, 81 | mu: mu, 82 | isServer: key.isServer, 83 | compressionLevel: key.compressionLevel, 84 | enableWriteCompression: true, 85 | writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize), 86 | } 87 | if key.compress { 88 | c.newCompressionWriter = compressNoContextTakeover 89 | } 90 | err = c.WriteMessage(pm.messageType, pm.data) 91 | frame.data = nc.buf.Bytes() 92 | }) 93 | return pm.messageType, frame.data, err 94 | } 95 | 96 | type prepareConn struct { 97 | buf bytes.Buffer 98 | net.Conn 99 | } 100 | 101 | func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) } 102 | func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil } 103 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/decode_meta.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import "strings" 4 | 5 | // MetaData allows access to meta information about TOML data that may not 6 | // be inferrable via reflection. In particular, whether a key has been defined 7 | // and the TOML type of a key. 8 | type MetaData struct { 9 | mapping map[string]interface{} 10 | types map[string]tomlType 11 | keys []Key 12 | decoded map[string]bool 13 | context Key // Used only during decoding. 14 | } 15 | 16 | // IsDefined returns true if the key given exists in the TOML data. The key 17 | // should be specified hierarchially. e.g., 18 | // 19 | // // access the TOML key 'a.b.c' 20 | // IsDefined("a", "b", "c") 21 | // 22 | // IsDefined will return false if an empty key given. Keys are case sensitive. 23 | func (md *MetaData) IsDefined(key ...string) bool { 24 | if len(key) == 0 { 25 | return false 26 | } 27 | 28 | var hash map[string]interface{} 29 | var ok bool 30 | var hashOrVal interface{} = md.mapping 31 | for _, k := range key { 32 | if hash, ok = hashOrVal.(map[string]interface{}); !ok { 33 | return false 34 | } 35 | if hashOrVal, ok = hash[k]; !ok { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | 42 | // Type returns a string representation of the type of the key specified. 43 | // 44 | // Type will return the empty string if given an empty key or a key that 45 | // does not exist. Keys are case sensitive. 46 | func (md *MetaData) Type(key ...string) string { 47 | fullkey := strings.Join(key, ".") 48 | if typ, ok := md.types[fullkey]; ok { 49 | return typ.typeString() 50 | } 51 | return "" 52 | } 53 | 54 | // Key is the type of any TOML key, including key groups. Use (MetaData).Keys 55 | // to get values of this type. 56 | type Key []string 57 | 58 | func (k Key) String() string { 59 | return strings.Join(k, ".") 60 | } 61 | 62 | func (k Key) maybeQuotedAll() string { 63 | var ss []string 64 | for i := range k { 65 | ss = append(ss, k.maybeQuoted(i)) 66 | } 67 | return strings.Join(ss, ".") 68 | } 69 | 70 | func (k Key) maybeQuoted(i int) string { 71 | quote := false 72 | for _, c := range k[i] { 73 | if !isBareKeyChar(c) { 74 | quote = true 75 | break 76 | } 77 | } 78 | if quote { 79 | return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\"" 80 | } 81 | return k[i] 82 | } 83 | 84 | func (k Key) add(piece string) Key { 85 | newKey := make(Key, len(k)+1) 86 | copy(newKey, k) 87 | newKey[len(k)] = piece 88 | return newKey 89 | } 90 | 91 | // Keys returns a slice of every key in the TOML data, including key groups. 92 | // Each key is itself a slice, where the first element is the top of the 93 | // hierarchy and the last is the most specific. 94 | // 95 | // The list will have the same order as the keys appeared in the TOML data. 96 | // 97 | // All keys returned are non-empty. 98 | func (md *MetaData) Keys() []Key { 99 | return md.keys 100 | } 101 | 102 | // Undecoded returns all keys that have not been decoded in the order in which 103 | // they appear in the original TOML document. 104 | // 105 | // This includes keys that haven't been decoded because of a Primitive value. 106 | // Once the Primitive value is decoded, the keys will be considered decoded. 107 | // 108 | // Also note that decoding into an empty interface will result in no decoding, 109 | // and so no keys will be considered decoded. 110 | // 111 | // In this sense, the Undecoded keys correspond to keys in the TOML document 112 | // that do not have a concrete type in your representation. 113 | func (md *MetaData) Undecoded() []Key { 114 | undecoded := make([]Key, 0, len(md.keys)) 115 | for _, key := range md.keys { 116 | if !md.decoded[key.String()] { 117 | undecoded = append(undecoded, key) 118 | } 119 | } 120 | return undecoded 121 | } 122 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/compression.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "compress/flate" 9 | "errors" 10 | "io" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | const ( 16 | minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6 17 | maxCompressionLevel = flate.BestCompression 18 | defaultCompressionLevel = 1 19 | ) 20 | 21 | var ( 22 | flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool 23 | flateReaderPool = sync.Pool{New: func() interface{} { 24 | return flate.NewReader(nil) 25 | }} 26 | ) 27 | 28 | func decompressNoContextTakeover(r io.Reader) io.ReadCloser { 29 | const tail = 30 | // Add four bytes as specified in RFC 31 | "\x00\x00\xff\xff" + 32 | // Add final block to squelch unexpected EOF error from flate reader. 33 | "\x01\x00\x00\xff\xff" 34 | 35 | fr, _ := flateReaderPool.Get().(io.ReadCloser) 36 | fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) 37 | return &flateReadWrapper{fr} 38 | } 39 | 40 | func isValidCompressionLevel(level int) bool { 41 | return minCompressionLevel <= level && level <= maxCompressionLevel 42 | } 43 | 44 | func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser { 45 | p := &flateWriterPools[level-minCompressionLevel] 46 | tw := &truncWriter{w: w} 47 | fw, _ := p.Get().(*flate.Writer) 48 | if fw == nil { 49 | fw, _ = flate.NewWriter(tw, level) 50 | } else { 51 | fw.Reset(tw) 52 | } 53 | return &flateWriteWrapper{fw: fw, tw: tw, p: p} 54 | } 55 | 56 | // truncWriter is an io.Writer that writes all but the last four bytes of the 57 | // stream to another io.Writer. 58 | type truncWriter struct { 59 | w io.WriteCloser 60 | n int 61 | p [4]byte 62 | } 63 | 64 | func (w *truncWriter) Write(p []byte) (int, error) { 65 | n := 0 66 | 67 | // fill buffer first for simplicity. 68 | if w.n < len(w.p) { 69 | n = copy(w.p[w.n:], p) 70 | p = p[n:] 71 | w.n += n 72 | if len(p) == 0 { 73 | return n, nil 74 | } 75 | } 76 | 77 | m := len(p) 78 | if m > len(w.p) { 79 | m = len(w.p) 80 | } 81 | 82 | if nn, err := w.w.Write(w.p[:m]); err != nil { 83 | return n + nn, err 84 | } 85 | 86 | copy(w.p[:], w.p[m:]) 87 | copy(w.p[len(w.p)-m:], p[len(p)-m:]) 88 | nn, err := w.w.Write(p[:len(p)-m]) 89 | return n + nn, err 90 | } 91 | 92 | type flateWriteWrapper struct { 93 | fw *flate.Writer 94 | tw *truncWriter 95 | p *sync.Pool 96 | } 97 | 98 | func (w *flateWriteWrapper) Write(p []byte) (int, error) { 99 | if w.fw == nil { 100 | return 0, errWriteClosed 101 | } 102 | return w.fw.Write(p) 103 | } 104 | 105 | func (w *flateWriteWrapper) Close() error { 106 | if w.fw == nil { 107 | return errWriteClosed 108 | } 109 | err1 := w.fw.Flush() 110 | w.p.Put(w.fw) 111 | w.fw = nil 112 | if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { 113 | return errors.New("websocket: internal error, unexpected bytes at end of flate stream") 114 | } 115 | err2 := w.tw.w.Close() 116 | if err1 != nil { 117 | return err1 118 | } 119 | return err2 120 | } 121 | 122 | type flateReadWrapper struct { 123 | fr io.ReadCloser 124 | } 125 | 126 | func (r *flateReadWrapper) Read(p []byte) (int, error) { 127 | if r.fr == nil { 128 | return 0, io.ErrClosedPipe 129 | } 130 | n, err := r.fr.Read(p) 131 | if err == io.EOF { 132 | // Preemptively place the reader back in the pool. This helps with 133 | // scenarios where the application does not call NextReader() soon after 134 | // this final read. 135 | r.Close() 136 | } 137 | return n, err 138 | } 139 | 140 | func (r *flateReadWrapper) Close() error { 141 | if r.fr == nil { 142 | return io.ErrClosedPipe 143 | } 144 | err := r.fr.Close() 145 | flateReaderPool.Put(r.fr) 146 | r.fr = nil 147 | return err 148 | } 149 | -------------------------------------------------------------------------------- /views/pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | 15 |
16 | 26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 |
34 | 35 |
36 | 52 | 65 |
66 | 67 | 68 | 90 | 93 | 94 |
69 |
70 | 71 | 72 | 73 | 74 |
75 | 76 | 77 | 82 | 88 |
89 |
91 |
92 |
95 |
96 |
97 | 98 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/README.md: -------------------------------------------------------------------------------- 1 | # Gorilla WebSocket 2 | 3 | Gorilla WebSocket is a [Go](http://golang.org/) implementation of the 4 | [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. 5 | 6 | [![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket) 7 | [![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) 8 | 9 | ### Documentation 10 | 11 | * [API Reference](http://godoc.org/github.com/gorilla/websocket) 12 | * [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) 13 | * [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) 14 | * [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) 15 | * [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) 16 | 17 | ### Status 18 | 19 | The Gorilla WebSocket package provides a complete and tested implementation of 20 | the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The 21 | package API is stable. 22 | 23 | ### Installation 24 | 25 | go get github.com/gorilla/websocket 26 | 27 | ### Protocol Compliance 28 | 29 | The Gorilla WebSocket package passes the server tests in the [Autobahn Test 30 | Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn 31 | subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). 32 | 33 | ### Gorilla WebSocket compared with other packages 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
github.com/gorillagolang.org/x/net
RFC 6455 Features
Passes Autobahn Test SuiteYesNo
Receive fragmented messageYesNo, see note 1
Send close messageYesNo
Send pings and receive pongsYesNo
Get the type of a received data messageYesYes, see note 2
Other Features
Compression ExtensionsExperimentalNo
Read message using io.ReaderYesNo, see note 3
Write message using io.WriteCloserYesNo, see note 3
53 | 54 | Notes: 55 | 56 | 1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). 57 | 2. The application can get the type of a received data message by implementing 58 | a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal) 59 | function. 60 | 3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. 61 | Read returns when the input buffer is full or a frame boundary is 62 | encountered. Each call to Write sends a single frame message. The Gorilla 63 | io.Reader and io.WriteCloser operate on a single WebSocket message. 64 | 65 | -------------------------------------------------------------------------------- /program/game/msg/msg.go: -------------------------------------------------------------------------------- 1 | package msg 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "github.com/wqtapp/poker" 7 | ) 8 | 9 | const( 10 | MSG_TYPE_OF_READY = iota //准备 11 | MSG_TYPE_OF_UN_READY //取消准备 12 | MSG_TYPE_OF_JOIN_TABLE //加入桌子 13 | MSG_TYPE_OF_LEAVE_TABLE //离开桌子 14 | 15 | MSG_TYPE_OF_HINT //提示 16 | MSG_TYPE_OF_PLAY_CARD //出牌 17 | MSG_TYPE_OF_PASS //过牌 18 | 19 | MSG_TYPE_OF_AUTO //托管 20 | MSG_TYPE_OF_SEND_CARD //发牌 21 | MSG_TYPE_OF_CALL_SCORE //抢地主叫分 22 | MSG_TYPE_OF_CONFIRM //客户端出牌等操作确认信息 23 | MSG_TYPE_OF_CALL_SCORE_TIME_OUT //叫地主超时 24 | MSG_TYPE_OF_PLAY_ERROR //出牌错误 25 | MSG_TYPE_OF_PLAY_CARD_SUCCESS //出牌成功 26 | MSG_TYPE_OF_TABLE_BRODCAST //桌子广播消息 27 | MSG_TYPE_OF_SCORE_CHANGE //牌局分数变化 28 | MSG_TYPE_OF_SETTLE_SCORE //结算玩家分数 29 | MSG_TYPE_OF_GAME_OVER //游戏结束 30 | MSG_TYPE_OF_LOGIN //登陆消息 31 | MSG_TYPE_OF_SEND_BOTTOM_CARDS //发底牌 32 | MSG_TYPE_OF_TIME_TICKER //倒计时数 33 | MSG_TYPE_OF_POKER_RECORDER //推送记牌器消息 34 | ) 35 | type SendCard struct { 36 | Index int //标志当前牌在用户所有牌中的索引位置 37 | Card poker.PokerCard 38 | } 39 | //发送给客户端的消息类型 40 | type SendCardMsg struct{ 41 | MsgType int 42 | Cards []*SendCard 43 | } 44 | 45 | type Msg struct { 46 | MsgType int 47 | Msg string 48 | } 49 | 50 | 51 | func NewSendCardMsg(cards poker.PokerSet) ([]byte,error){ 52 | cardMsg := SendCardMsg{ 53 | MSG_TYPE_OF_SEND_CARD, 54 | []*SendCard{}, 55 | } 56 | for i,card := range cards{ 57 | sendCard := SendCard{} 58 | sendCard.Index =i 59 | sendCard.Card = *card 60 | cardMsg.Cards = append(cardMsg.Cards,&sendCard) 61 | } 62 | return json.Marshal(cardMsg) 63 | } 64 | 65 | func NewCallScoreMsg() ([]byte,error){ 66 | msg := Msg{ 67 | MSG_TYPE_OF_CALL_SCORE, 68 | "", 69 | } 70 | return json.Marshal(msg) 71 | } 72 | func NewTimeCountMsg(second int) ([]byte,error){ 73 | msg := Msg{ 74 | MSG_TYPE_OF_CALL_SCORE, 75 | strconv.Itoa(second), 76 | } 77 | return json.Marshal(msg) 78 | } 79 | func NewCallScoreTimeOutMsg() ([]byte,error){ 80 | msg := Msg{ 81 | MSG_TYPE_OF_CALL_SCORE_TIME_OUT, 82 | "", 83 | } 84 | return json.Marshal(msg) 85 | } 86 | 87 | func NewPlayCardMsg() ([]byte,error){ 88 | msg := Msg{ 89 | MSG_TYPE_OF_PLAY_CARD, 90 | "", 91 | } 92 | return json.Marshal(msg) 93 | } 94 | 95 | func NewPlayCardsErrorMsg(error string) ([]byte,error){ 96 | msg := Msg{ 97 | MSG_TYPE_OF_PLAY_ERROR, 98 | error, 99 | } 100 | return json.Marshal(msg) 101 | } 102 | 103 | func NewPlayCardSuccessMsg() ([]byte,error){ 104 | msg := Msg{ 105 | MSG_TYPE_OF_PLAY_CARD_SUCCESS, 106 | "", 107 | } 108 | return json.Marshal(msg) 109 | } 110 | 111 | type LoginMsg struct{ 112 | MsgType int 113 | Msg string 114 | ID int 115 | } 116 | 117 | func NewLoginMsg(userID int,loginMsg string) ([]byte,error){ 118 | newMsg :=LoginMsg{ 119 | MSG_TYPE_OF_LOGIN, 120 | loginMsg, 121 | -1, 122 | } 123 | newMsg.ID = userID 124 | return json.Marshal(newMsg) 125 | } 126 | 127 | type BroadCastMsg struct{ 128 | MsgType int 129 | SubMsgType int 130 | Msg string 131 | Cards poker.PokerSet 132 | CardsIndex []int 133 | Score int 134 | PlayerId int 135 | SettleInfoDic map[string]string 136 | PlayerIndexIdDic map[string]int 137 | } 138 | func NewBraodCastMsg() BroadCastMsg{ 139 | msg := BroadCastMsg{ 140 | MSG_TYPE_OF_TABLE_BRODCAST, 141 | -1, 142 | "", 143 | poker.PokerSet{}, 144 | []int{}, 145 | -1, 146 | -1, 147 | make(map[string]string), 148 | make(map[string]int), 149 | } 150 | return msg 151 | } 152 | /* 153 | 确认消息 154 | { 155 | "msgType":MSG_TYPE_OF_CONFIRM 156 | "model"{ 157 | 158 | } 159 | } 160 | 发牌消息形式 161 | { 162 | "msgType":MSG_TYPE_OF_SEND_CARD, 163 | "model":{ 164 | pokerCars[{ 165 | "carIndex":, //当前玩家手中的index 166 | "carNum":, //牌的数字 167 | "carColor":, //牌的花色 168 | "" 169 | },] 170 | } 171 | } 172 | 173 | 出牌消息 174 | { 175 | "msgType":MSG_TYPE_OF_SEND_CARD, 176 | "model":{ 177 | pokerCars[{ 178 | "carIndex":, //当前玩家手中的index 179 | },] 180 | } 181 | } 182 | 183 | 184 | */ 185 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/README.md: -------------------------------------------------------------------------------- 1 | ## TOML parser and encoder for Go with reflection 2 | 3 | TOML stands for Tom's Obvious, Minimal Language. This Go package provides a 4 | reflection interface similar to Go's standard library `json` and `xml` 5 | packages. This package also supports the `encoding.TextUnmarshaler` and 6 | `encoding.TextMarshaler` interfaces so that you can define custom data 7 | representations. (There is an example of this below.) 8 | 9 | Spec: https://github.com/toml-lang/toml 10 | 11 | Compatible with TOML version 12 | [v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) 13 | 14 | Documentation: https://godoc.org/github.com/BurntSushi/toml 15 | 16 | Installation: 17 | 18 | ```bash 19 | go get github.com/BurntSushi/toml 20 | ``` 21 | 22 | Try the toml validator: 23 | 24 | ```bash 25 | go get github.com/BurntSushi/toml/cmd/tomlv 26 | tomlv some-toml-file.toml 27 | ``` 28 | 29 | [![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml) 30 | 31 | ### Testing 32 | 33 | This package passes all tests in 34 | [toml-test](https://github.com/BurntSushi/toml-test) for both the decoder 35 | and the encoder. 36 | 37 | ### Examples 38 | 39 | This package works similarly to how the Go standard library handles `XML` 40 | and `JSON`. Namely, data is loaded into Go values via reflection. 41 | 42 | For the simplest example, consider some TOML file as just a list of keys 43 | and values: 44 | 45 | ```toml 46 | Age = 25 47 | Cats = [ "Cauchy", "Plato" ] 48 | Pi = 3.14 49 | Perfection = [ 6, 28, 496, 8128 ] 50 | DOB = 1987-07-05T05:45:00Z 51 | ``` 52 | 53 | Which could be defined in Go as: 54 | 55 | ```go 56 | type Config struct { 57 | Age int 58 | Cats []string 59 | Pi float64 60 | Perfection []int 61 | DOB time.Time // requires `import time` 62 | } 63 | ``` 64 | 65 | And then decoded with: 66 | 67 | ```go 68 | var conf Config 69 | if _, err := toml.Decode(tomlData, &conf); err != nil { 70 | // handle error 71 | } 72 | ``` 73 | 74 | You can also use struct tags if your struct field name doesn't map to a TOML 75 | key value directly: 76 | 77 | ```toml 78 | some_key_NAME = "wat" 79 | ``` 80 | 81 | ```go 82 | type TOML struct { 83 | ObscureKey string `toml:"some_key_NAME"` 84 | } 85 | ``` 86 | 87 | ### Using the `encoding.TextUnmarshaler` interface 88 | 89 | Here's an example that automatically parses duration strings into 90 | `time.Duration` values: 91 | 92 | ```toml 93 | [[song]] 94 | name = "Thunder Road" 95 | duration = "4m49s" 96 | 97 | [[song]] 98 | name = "Stairway to Heaven" 99 | duration = "8m03s" 100 | ``` 101 | 102 | Which can be decoded with: 103 | 104 | ```go 105 | type song struct { 106 | Name string 107 | Duration duration 108 | } 109 | type songs struct { 110 | Song []song 111 | } 112 | var favorites songs 113 | if _, err := toml.Decode(blob, &favorites); err != nil { 114 | log.Fatal(err) 115 | } 116 | 117 | for _, s := range favorites.Song { 118 | fmt.Printf("%s (%s)\n", s.Name, s.Duration) 119 | } 120 | ``` 121 | 122 | And you'll also need a `duration` type that satisfies the 123 | `encoding.TextUnmarshaler` interface: 124 | 125 | ```go 126 | type duration struct { 127 | time.Duration 128 | } 129 | 130 | func (d *duration) UnmarshalText(text []byte) error { 131 | var err error 132 | d.Duration, err = time.ParseDuration(string(text)) 133 | return err 134 | } 135 | ``` 136 | 137 | ### More complex usage 138 | 139 | Here's an example of how to load the example from the official spec page: 140 | 141 | ```toml 142 | # This is a TOML document. Boom. 143 | 144 | title = "TOML Example" 145 | 146 | [owner] 147 | name = "Tom Preston-Werner" 148 | organization = "GitHub" 149 | bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." 150 | dob = 1979-05-27T07:32:00Z # First class dates? Why not? 151 | 152 | [database] 153 | server = "192.168.1.1" 154 | ports = [ 8001, 8001, 8002 ] 155 | connection_max = 5000 156 | enabled = true 157 | 158 | [servers] 159 | 160 | # You can indent as you please. Tabs or spaces. TOML don't care. 161 | [servers.alpha] 162 | ip = "10.0.0.1" 163 | dc = "eqdc10" 164 | 165 | [servers.beta] 166 | ip = "10.0.0.2" 167 | dc = "eqdc10" 168 | 169 | [clients] 170 | data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it 171 | 172 | # Line breaks are OK when inside arrays 173 | hosts = [ 174 | "alpha", 175 | "omega" 176 | ] 177 | ``` 178 | 179 | And the corresponding Go types are: 180 | 181 | ```go 182 | type tomlConfig struct { 183 | Title string 184 | Owner ownerInfo 185 | DB database `toml:"database"` 186 | Servers map[string]server 187 | Clients clients 188 | } 189 | 190 | type ownerInfo struct { 191 | Name string 192 | Org string `toml:"organization"` 193 | Bio string 194 | DOB time.Time 195 | } 196 | 197 | type database struct { 198 | Server string 199 | Ports []int 200 | ConnMax int `toml:"connection_max"` 201 | Enabled bool 202 | } 203 | 204 | type server struct { 205 | IP string 206 | DC string 207 | } 208 | 209 | type clients struct { 210 | Data [][]interface{} 211 | Hosts []string 212 | } 213 | ``` 214 | 215 | Note that a case insensitive match will be tried if an exact match can't be 216 | found. 217 | 218 | A working example of the above can be found in `_examples/example.{go,toml}`. 219 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "crypto/rand" 9 | "crypto/sha1" 10 | "encoding/base64" 11 | "io" 12 | "net/http" 13 | "strings" 14 | "unicode/utf8" 15 | ) 16 | 17 | var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") 18 | 19 | func computeAcceptKey(challengeKey string) string { 20 | h := sha1.New() 21 | h.Write([]byte(challengeKey)) 22 | h.Write(keyGUID) 23 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) 24 | } 25 | 26 | func generateChallengeKey() (string, error) { 27 | p := make([]byte, 16) 28 | if _, err := io.ReadFull(rand.Reader, p); err != nil { 29 | return "", err 30 | } 31 | return base64.StdEncoding.EncodeToString(p), nil 32 | } 33 | 34 | // Octet types from RFC 2616. 35 | var octetTypes [256]byte 36 | 37 | const ( 38 | isTokenOctet = 1 << iota 39 | isSpaceOctet 40 | ) 41 | 42 | func init() { 43 | // From RFC 2616 44 | // 45 | // OCTET = 46 | // CHAR = 47 | // CTL = 48 | // CR = 49 | // LF = 50 | // SP = 51 | // HT = 52 | // <"> = 53 | // CRLF = CR LF 54 | // LWS = [CRLF] 1*( SP | HT ) 55 | // TEXT = 56 | // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> 57 | // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT 58 | // token = 1* 59 | // qdtext = > 60 | 61 | for c := 0; c < 256; c++ { 62 | var t byte 63 | isCtl := c <= 31 || c == 127 64 | isChar := 0 <= c && c <= 127 65 | isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 66 | if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { 67 | t |= isSpaceOctet 68 | } 69 | if isChar && !isCtl && !isSeparator { 70 | t |= isTokenOctet 71 | } 72 | octetTypes[c] = t 73 | } 74 | } 75 | 76 | func skipSpace(s string) (rest string) { 77 | i := 0 78 | for ; i < len(s); i++ { 79 | if octetTypes[s[i]]&isSpaceOctet == 0 { 80 | break 81 | } 82 | } 83 | return s[i:] 84 | } 85 | 86 | func nextToken(s string) (token, rest string) { 87 | i := 0 88 | for ; i < len(s); i++ { 89 | if octetTypes[s[i]]&isTokenOctet == 0 { 90 | break 91 | } 92 | } 93 | return s[:i], s[i:] 94 | } 95 | 96 | func nextTokenOrQuoted(s string) (value string, rest string) { 97 | if !strings.HasPrefix(s, "\"") { 98 | return nextToken(s) 99 | } 100 | s = s[1:] 101 | for i := 0; i < len(s); i++ { 102 | switch s[i] { 103 | case '"': 104 | return s[:i], s[i+1:] 105 | case '\\': 106 | p := make([]byte, len(s)-1) 107 | j := copy(p, s[:i]) 108 | escape := true 109 | for i = i + 1; i < len(s); i++ { 110 | b := s[i] 111 | switch { 112 | case escape: 113 | escape = false 114 | p[j] = b 115 | j++ 116 | case b == '\\': 117 | escape = true 118 | case b == '"': 119 | return string(p[:j]), s[i+1:] 120 | default: 121 | p[j] = b 122 | j++ 123 | } 124 | } 125 | return "", "" 126 | } 127 | } 128 | return "", "" 129 | } 130 | 131 | // equalASCIIFold returns true if s is equal to t with ASCII case folding. 132 | func equalASCIIFold(s, t string) bool { 133 | for s != "" && t != "" { 134 | sr, size := utf8.DecodeRuneInString(s) 135 | s = s[size:] 136 | tr, size := utf8.DecodeRuneInString(t) 137 | t = t[size:] 138 | if sr == tr { 139 | continue 140 | } 141 | if 'A' <= sr && sr <= 'Z' { 142 | sr = sr + 'a' - 'A' 143 | } 144 | if 'A' <= tr && tr <= 'Z' { 145 | tr = tr + 'a' - 'A' 146 | } 147 | if sr != tr { 148 | return false 149 | } 150 | } 151 | return s == t 152 | } 153 | 154 | // tokenListContainsValue returns true if the 1#token header with the given 155 | // name contains a token equal to value with ASCII case folding. 156 | func tokenListContainsValue(header http.Header, name string, value string) bool { 157 | headers: 158 | for _, s := range header[name] { 159 | for { 160 | var t string 161 | t, s = nextToken(skipSpace(s)) 162 | if t == "" { 163 | continue headers 164 | } 165 | s = skipSpace(s) 166 | if s != "" && s[0] != ',' { 167 | continue headers 168 | } 169 | if equalASCIIFold(t, value) { 170 | return true 171 | } 172 | if s == "" { 173 | continue headers 174 | } 175 | s = s[1:] 176 | } 177 | } 178 | return false 179 | } 180 | 181 | // parseExtensions parses WebSocket extensions from a header. 182 | func parseExtensions(header http.Header) []map[string]string { 183 | // From RFC 6455: 184 | // 185 | // Sec-WebSocket-Extensions = extension-list 186 | // extension-list = 1#extension 187 | // extension = extension-token *( ";" extension-param ) 188 | // extension-token = registered-token 189 | // registered-token = token 190 | // extension-param = token [ "=" (token | quoted-string) ] 191 | // ;When using the quoted-string syntax variant, the value 192 | // ;after quoted-string unescaping MUST conform to the 193 | // ;'token' ABNF. 194 | 195 | var result []map[string]string 196 | headers: 197 | for _, s := range header["Sec-Websocket-Extensions"] { 198 | for { 199 | var t string 200 | t, s = nextToken(skipSpace(s)) 201 | if t == "" { 202 | continue headers 203 | } 204 | ext := map[string]string{"": t} 205 | for { 206 | s = skipSpace(s) 207 | if !strings.HasPrefix(s, ";") { 208 | break 209 | } 210 | var k string 211 | k, s = nextToken(skipSpace(s[1:])) 212 | if k == "" { 213 | continue headers 214 | } 215 | s = skipSpace(s) 216 | var v string 217 | if strings.HasPrefix(s, "=") { 218 | v, s = nextTokenOrQuoted(skipSpace(s[1:])) 219 | s = skipSpace(s) 220 | } 221 | if s != "" && s[0] != ',' && s[0] != ';' { 222 | continue headers 223 | } 224 | ext[k] = v 225 | } 226 | if s != "" && s[0] != ',' { 227 | continue headers 228 | } 229 | result = append(result, ext) 230 | if s == "" { 231 | continue headers 232 | } 233 | s = s[1:] 234 | } 235 | } 236 | return result 237 | } 238 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/type_fields.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | // Struct field handling is adapted from code in encoding/json: 4 | // 5 | // Copyright 2010 The Go Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the Go distribution. 8 | 9 | import ( 10 | "reflect" 11 | "sort" 12 | "sync" 13 | ) 14 | 15 | // A field represents a single field found in a struct. 16 | type field struct { 17 | name string // the name of the field (`toml` tag included) 18 | tag bool // whether field has a `toml` tag 19 | index []int // represents the depth of an anonymous field 20 | typ reflect.Type // the type of the field 21 | } 22 | 23 | // byName sorts field by name, breaking ties with depth, 24 | // then breaking ties with "name came from toml tag", then 25 | // breaking ties with index sequence. 26 | type byName []field 27 | 28 | func (x byName) Len() int { return len(x) } 29 | 30 | func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 31 | 32 | func (x byName) Less(i, j int) bool { 33 | if x[i].name != x[j].name { 34 | return x[i].name < x[j].name 35 | } 36 | if len(x[i].index) != len(x[j].index) { 37 | return len(x[i].index) < len(x[j].index) 38 | } 39 | if x[i].tag != x[j].tag { 40 | return x[i].tag 41 | } 42 | return byIndex(x).Less(i, j) 43 | } 44 | 45 | // byIndex sorts field by index sequence. 46 | type byIndex []field 47 | 48 | func (x byIndex) Len() int { return len(x) } 49 | 50 | func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 51 | 52 | func (x byIndex) Less(i, j int) bool { 53 | for k, xik := range x[i].index { 54 | if k >= len(x[j].index) { 55 | return false 56 | } 57 | if xik != x[j].index[k] { 58 | return xik < x[j].index[k] 59 | } 60 | } 61 | return len(x[i].index) < len(x[j].index) 62 | } 63 | 64 | // typeFields returns a list of fields that TOML should recognize for the given 65 | // type. The algorithm is breadth-first search over the set of structs to 66 | // include - the top struct and then any reachable anonymous structs. 67 | func typeFields(t reflect.Type) []field { 68 | // Anonymous fields to explore at the current level and the next. 69 | current := []field{} 70 | next := []field{{typ: t}} 71 | 72 | // Count of queued names for current level and the next. 73 | count := map[reflect.Type]int{} 74 | nextCount := map[reflect.Type]int{} 75 | 76 | // Types already visited at an earlier level. 77 | visited := map[reflect.Type]bool{} 78 | 79 | // Fields found. 80 | var fields []field 81 | 82 | for len(next) > 0 { 83 | current, next = next, current[:0] 84 | count, nextCount = nextCount, map[reflect.Type]int{} 85 | 86 | for _, f := range current { 87 | if visited[f.typ] { 88 | continue 89 | } 90 | visited[f.typ] = true 91 | 92 | // Scan f.typ for fields to include. 93 | for i := 0; i < f.typ.NumField(); i++ { 94 | sf := f.typ.Field(i) 95 | if sf.PkgPath != "" && !sf.Anonymous { // unexported 96 | continue 97 | } 98 | opts := getOptions(sf.Tag) 99 | if opts.skip { 100 | continue 101 | } 102 | index := make([]int, len(f.index)+1) 103 | copy(index, f.index) 104 | index[len(f.index)] = i 105 | 106 | ft := sf.Type 107 | if ft.Name() == "" && ft.Kind() == reflect.Ptr { 108 | // Follow pointer. 109 | ft = ft.Elem() 110 | } 111 | 112 | // Record found field and index sequence. 113 | if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { 114 | tagged := opts.name != "" 115 | name := opts.name 116 | if name == "" { 117 | name = sf.Name 118 | } 119 | fields = append(fields, field{name, tagged, index, ft}) 120 | if count[f.typ] > 1 { 121 | // If there were multiple instances, add a second, 122 | // so that the annihilation code will see a duplicate. 123 | // It only cares about the distinction between 1 or 2, 124 | // so don't bother generating any more copies. 125 | fields = append(fields, fields[len(fields)-1]) 126 | } 127 | continue 128 | } 129 | 130 | // Record new anonymous struct to explore in next round. 131 | nextCount[ft]++ 132 | if nextCount[ft] == 1 { 133 | f := field{name: ft.Name(), index: index, typ: ft} 134 | next = append(next, f) 135 | } 136 | } 137 | } 138 | } 139 | 140 | sort.Sort(byName(fields)) 141 | 142 | // Delete all fields that are hidden by the Go rules for embedded fields, 143 | // except that fields with TOML tags are promoted. 144 | 145 | // The fields are sorted in primary order of name, secondary order 146 | // of field index length. Loop over names; for each name, delete 147 | // hidden fields by choosing the one dominant field that survives. 148 | out := fields[:0] 149 | for advance, i := 0, 0; i < len(fields); i += advance { 150 | // One iteration per name. 151 | // Find the sequence of fields with the name of this first field. 152 | fi := fields[i] 153 | name := fi.name 154 | for advance = 1; i+advance < len(fields); advance++ { 155 | fj := fields[i+advance] 156 | if fj.name != name { 157 | break 158 | } 159 | } 160 | if advance == 1 { // Only one field with this name 161 | out = append(out, fi) 162 | continue 163 | } 164 | dominant, ok := dominantField(fields[i : i+advance]) 165 | if ok { 166 | out = append(out, dominant) 167 | } 168 | } 169 | 170 | fields = out 171 | sort.Sort(byIndex(fields)) 172 | 173 | return fields 174 | } 175 | 176 | // dominantField looks through the fields, all of which are known to 177 | // have the same name, to find the single field that dominates the 178 | // others using Go's embedding rules, modified by the presence of 179 | // TOML tags. If there are multiple top-level fields, the boolean 180 | // will be false: This condition is an error in Go and we skip all 181 | // the fields. 182 | func dominantField(fields []field) (field, bool) { 183 | // The fields are sorted in increasing index-length order. The winner 184 | // must therefore be one with the shortest index length. Drop all 185 | // longer entries, which is easy: just truncate the slice. 186 | length := len(fields[0].index) 187 | tagged := -1 // Index of first tagged field. 188 | for i, f := range fields { 189 | if len(f.index) > length { 190 | fields = fields[:i] 191 | break 192 | } 193 | if f.tag { 194 | if tagged >= 0 { 195 | // Multiple tagged fields at the same level: conflict. 196 | // Return no field. 197 | return field{}, false 198 | } 199 | tagged = i 200 | } 201 | } 202 | if tagged >= 0 { 203 | return fields[tagged], true 204 | } 205 | // All remaining fields have the same length. If there's more than one, 206 | // we have a conflict (two fields named "X" at the same level) and we 207 | // return no field. 208 | if len(fields) > 1 { 209 | return field{}, false 210 | } 211 | return fields[0], true 212 | } 213 | 214 | var fieldCache struct { 215 | sync.RWMutex 216 | m map[reflect.Type][]field 217 | } 218 | 219 | // cachedTypeFields is like typeFields but uses a cache to avoid repeated work. 220 | func cachedTypeFields(t reflect.Type) []field { 221 | fieldCache.RLock() 222 | f := fieldCache.m[t] 223 | fieldCache.RUnlock() 224 | if f != nil { 225 | return f 226 | } 227 | 228 | // Compute fields without lock. 229 | // Might duplicate effort but won't hold other computations back. 230 | f = typeFields(t) 231 | if f == nil { 232 | f = []field{} 233 | } 234 | 235 | fieldCache.Lock() 236 | if fieldCache.m == nil { 237 | fieldCache.m = map[reflect.Type][]field{} 238 | } 239 | fieldCache.m[t] = f 240 | fieldCache.Unlock() 241 | return f 242 | } 243 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package websocket implements the WebSocket protocol defined in RFC 6455. 6 | // 7 | // Overview 8 | // 9 | // The Conn type represents a WebSocket connection. A server application calls 10 | // the Upgrader.Upgrade method from an HTTP request handler to get a *Conn: 11 | // 12 | // var upgrader = websocket.Upgrader{ 13 | // ReadBufferSize: 1024, 14 | // WriteBufferSize: 1024, 15 | // } 16 | // 17 | // func handler(w http.ResponseWriter, r *http.Request) { 18 | // conn, err := upgrader.Upgrade(w, r, nil) 19 | // if err != nil { 20 | // log.Println(err) 21 | // return 22 | // } 23 | // ... Use conn to send and receive messages. 24 | // } 25 | // 26 | // Call the connection's WriteMessage and ReadMessage methods to send and 27 | // receive messages as a slice of bytes. This snippet of code shows how to echo 28 | // messages using these methods: 29 | // 30 | // for { 31 | // messageType, p, err := conn.ReadMessage() 32 | // if err != nil { 33 | // log.Println(err) 34 | // return 35 | // } 36 | // if err := conn.WriteMessage(messageType, p); err != nil { 37 | // log.Println(err) 38 | // return 39 | // } 40 | // } 41 | // 42 | // In above snippet of code, p is a []byte and messageType is an int with value 43 | // websocket.BinaryMessage or websocket.TextMessage. 44 | // 45 | // An application can also send and receive messages using the io.WriteCloser 46 | // and io.Reader interfaces. To send a message, call the connection NextWriter 47 | // method to get an io.WriteCloser, write the message to the writer and close 48 | // the writer when done. To receive a message, call the connection NextReader 49 | // method to get an io.Reader and read until io.EOF is returned. This snippet 50 | // shows how to echo messages using the NextWriter and NextReader methods: 51 | // 52 | // for { 53 | // messageType, r, err := conn.NextReader() 54 | // if err != nil { 55 | // return 56 | // } 57 | // w, err := conn.NextWriter(messageType) 58 | // if err != nil { 59 | // return err 60 | // } 61 | // if _, err := io.Copy(w, r); err != nil { 62 | // return err 63 | // } 64 | // if err := w.Close(); err != nil { 65 | // return err 66 | // } 67 | // } 68 | // 69 | // Data Messages 70 | // 71 | // The WebSocket protocol distinguishes between text and binary data messages. 72 | // Text messages are interpreted as UTF-8 encoded text. The interpretation of 73 | // binary messages is left to the application. 74 | // 75 | // This package uses the TextMessage and BinaryMessage integer constants to 76 | // identify the two data message types. The ReadMessage and NextReader methods 77 | // return the type of the received message. The messageType argument to the 78 | // WriteMessage and NextWriter methods specifies the type of a sent message. 79 | // 80 | // It is the application's responsibility to ensure that text messages are 81 | // valid UTF-8 encoded text. 82 | // 83 | // Control Messages 84 | // 85 | // The WebSocket protocol defines three types of control messages: close, ping 86 | // and pong. Call the connection WriteControl, WriteMessage or NextWriter 87 | // methods to send a control message to the peer. 88 | // 89 | // Connections handle received close messages by calling the handler function 90 | // set with the SetCloseHandler method and by returning a *CloseError from the 91 | // NextReader, ReadMessage or the message Read method. The default close 92 | // handler sends a close message to the peer. 93 | // 94 | // Connections handle received ping messages by calling the handler function 95 | // set with the SetPingHandler method. The default ping handler sends a pong 96 | // message to the peer. 97 | // 98 | // Connections handle received pong messages by calling the handler function 99 | // set with the SetPongHandler method. The default pong handler does nothing. 100 | // If an application sends ping messages, then the application should set a 101 | // pong handler to receive the corresponding pong. 102 | // 103 | // The control message handler functions are called from the NextReader, 104 | // ReadMessage and message reader Read methods. The default close and ping 105 | // handlers can block these methods for a short time when the handler writes to 106 | // the connection. 107 | // 108 | // The application must read the connection to process close, ping and pong 109 | // messages sent from the peer. If the application is not otherwise interested 110 | // in messages from the peer, then the application should start a goroutine to 111 | // read and discard messages from the peer. A simple example is: 112 | // 113 | // func readLoop(c *websocket.Conn) { 114 | // for { 115 | // if _, _, err := c.NextReader(); err != nil { 116 | // c.Close() 117 | // break 118 | // } 119 | // } 120 | // } 121 | // 122 | // Concurrency 123 | // 124 | // Connections support one concurrent reader and one concurrent writer. 125 | // 126 | // Applications are responsible for ensuring that no more than one goroutine 127 | // calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, 128 | // WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and 129 | // that no more than one goroutine calls the read methods (NextReader, 130 | // SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) 131 | // concurrently. 132 | // 133 | // The Close and WriteControl methods can be called concurrently with all other 134 | // methods. 135 | // 136 | // Origin Considerations 137 | // 138 | // Web browsers allow Javascript applications to open a WebSocket connection to 139 | // any host. It's up to the server to enforce an origin policy using the Origin 140 | // request header sent by the browser. 141 | // 142 | // The Upgrader calls the function specified in the CheckOrigin field to check 143 | // the origin. If the CheckOrigin function returns false, then the Upgrade 144 | // method fails the WebSocket handshake with HTTP status 403. 145 | // 146 | // If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail 147 | // the handshake if the Origin request header is present and the Origin host is 148 | // not equal to the Host request header. 149 | // 150 | // The deprecated package-level Upgrade function does not perform origin 151 | // checking. The application is responsible for checking the Origin header 152 | // before calling the Upgrade function. 153 | // 154 | // Compression EXPERIMENTAL 155 | // 156 | // Per message compression extensions (RFC 7692) are experimentally supported 157 | // by this package in a limited capacity. Setting the EnableCompression option 158 | // to true in Dialer or Upgrader will attempt to negotiate per message deflate 159 | // support. 160 | // 161 | // var upgrader = websocket.Upgrader{ 162 | // EnableCompression: true, 163 | // } 164 | // 165 | // If compression was successfully negotiated with the connection's peer, any 166 | // message received in compressed form will be automatically decompressed. 167 | // All Read methods will return uncompressed bytes. 168 | // 169 | // Per message compression of messages written to a connection can be enabled 170 | // or disabled by calling the corresponding Conn method: 171 | // 172 | // conn.EnableWriteCompression(false) 173 | // 174 | // Currently this package does not support compression with "context takeover". 175 | // This means that messages must be compressed and decompressed in isolation, 176 | // without retaining sliding window or dictionary state across messages. For 177 | // more details refer to RFC 7692. 178 | // 179 | // Use of compression is experimental and may result in decreased performance. 180 | package websocket 181 | -------------------------------------------------------------------------------- /views/static/js/home.js: -------------------------------------------------------------------------------- 1 | const MSG_TYPE_OF_READY = 0; //准备 2 | const MSG_TYPE_OF_UN_READY =1; //取消准备 3 | const MSG_TYPE_OF_JOIN_TABLE = 2; //加入桌子 4 | const MSG_TYPE_OF_LEAVE_TABLE = 3; //离开桌子 5 | const MSG_TYPE_OF_HINT = 4; //提示 6 | const MSG_TYPE_OF_PLAY_CARD = 5; //出牌 7 | const MSG_TYPE_OF_PASS = 6; //过牌 8 | const MSG_TYPE_OF_AUTO = 7; //托管 9 | const MSG_TYPE_OF_SEND_CARD = 8; //发牌 10 | const MSG_TYPE_OF_CALL_SCORE = 9; //抢地主叫分 11 | const MSG_TYPE_OF_CONFIRM = 10; //客户端出牌等操作确认信息 12 | const MSG_TYPE_OF_CALL_SCORE_TIME_OUT = 11; //叫地主超时 13 | const MSG_TYPE_OF_PLAY_ERROR = 12 //出牌错误 14 | const TYPE_OF_PLAY_CARD_SUCCESS = 13 //出牌成功 15 | const TYPE_OF_TABLE_BRODCAST = 14 //游戏桌子广播消息 16 | const MSG_TYPE_OF_SCORE_CHANGE = 15 //牌局分数变化 17 | const MSG_TYPE_OF_SETTLE_SCORE = 16 //结算玩家分数 18 | const MSG_TYPE_OF_GAME_OVER = 17 //游戏结束 19 | const MSG_TYPE_OF_LOGIN = 18 //玩家登陆成功 20 | const MSG_TYPE_OF_SEND_BOTTOM_CARDS = 19 //发底牌 21 | const MSG_TYPE_OF_TIME_TICKER = 20 //倒计时 22 | var ws; 23 | var lastPlayCardIndex = []; 24 | var currPlayerId = -1; 25 | var currPlayerIndex = -1; 26 | function print(message) { 27 | var d = document.createElement("div"); 28 | d.innerHTML = message; 29 | $("#output").append(d); 30 | }; 31 | 32 | function openConnection(){ 33 | if (ws) { 34 | return false; 35 | } 36 | ws = new WebSocket("ws://localhost:8888/echo"); 37 | ws.onopen = function(evt) { 38 | print("OPEN"); 39 | } 40 | ws.onclose = function(evt) { 41 | print("CLOSE"); 42 | ws = null; 43 | } 44 | ws.onmessage = function(evt) { 45 | 46 | data = JSON.parse(evt.data) 47 | switch(data.MsgType){ 48 | case MSG_TYPE_OF_LOGIN: 49 | currPlayerId = data.ID 50 | break; 51 | case MSG_TYPE_OF_HINT: 52 | break; 53 | case MSG_TYPE_OF_PLAY_CARD: 54 | $("#divPlay").show(); 55 | break; 56 | case MSG_TYPE_OF_SEND_CARD: 57 | $("#userCards").html(''); 58 | $.each(data.Cards,function(i,o){ 59 | $("#userCards").prepend(String.format($("#tempPlayCard").html(),o.Card.CardName,o.Card.CardSuit,o.Index)); 60 | }) 61 | break; 62 | case MSG_TYPE_OF_CALL_SCORE: 63 | $("#divScore").show() 64 | print("请叫分"); 65 | break; 66 | case MSG_TYPE_OF_CALL_SCORE_TIME_OUT: 67 | $("#divScore").hide(); 68 | break; 69 | case MSG_TYPE_OF_PLAY_ERROR: 70 | alert(data.Msg); 71 | break; 72 | case TYPE_OF_PLAY_CARD_SUCCESS: 73 | $("#divPlay").hide() 74 | for (i=0;i currPlayerIndex){ 112 | if(data.PlayerIndexIdDic[x] == currPlayerIndex+1){ 113 | $("#rightPlayer").html(String.format($("#tempDivPlayerInfo").html(),id)); 114 | $("#PlayerName"+id).html("玩家"+x); 115 | $("#PlayerMsg"+id).html("玩家"+x+"加入游戏"); 116 | }else{ 117 | $("#leftPlayer").html(String.format($("#tempDivPlayerInfo").html(),id)); 118 | $("#PlayerName"+id).html("玩家"+x); 119 | $("#PlayerMsg"+id).html("玩家"+x+"加入游戏"); 120 | } 121 | 122 | }else{ 123 | if(data.PlayerIndexIdDic[x] == currPlayerIndex-1){ 124 | $("#leftPlayer").html(String.format($("#tempDivPlayerInfo").html(),id)); 125 | $("#PlayerName"+id).html("玩家"+x); 126 | $("#PlayerMsg"+id).html("玩家"+x+"加入游戏"); 127 | }else{ 128 | $("#rightPlayer").html(String.format($("#tempDivPlayerInfo").html(),id)); 129 | $("#PlayerName"+id).html("玩家"+x); 130 | $("#PlayerMsg"+id).html("玩家"+x+"加入游戏"); 131 | } 132 | } 133 | } 134 | console.log(data); 135 | break; 136 | case MSG_TYPE_OF_SEND_BOTTOM_CARDS: 137 | $.each(data.Cards,function(i,o){ 138 | $("#LoardCards"+data.PlayerId).append(String.format($("#tempCard").html(),o.CardName,o.CardSuit)); 139 | }) 140 | break; 141 | case MSG_TYPE_OF_PLAY_CARD: 142 | $("#PlayerMsg"+data.PlayerId).html("玩家"+data.PlayerId+"出牌"); 143 | $("#divPlayCards").html(''); 144 | $.each(data.Cards,function(i,o){ 145 | $("#divPlayCards").append(String.format($("#tempCard").html(),o.CardName,o.CardSuit)); 146 | }) 147 | 148 | if(data.PlayerId == currPlayerId){ 149 | $("#divPlay").hide(); 150 | $.each(data.CardsIndex,function(i,o){ 151 | $("#cardDiv"+o).remove(); 152 | }) 153 | } 154 | console.log(data); 155 | break; 156 | case MSG_TYPE_OF_PASS: 157 | $("#PlayerMsg"+data.PlayerId).html("玩家"+data.PlayerId+"过牌"); 158 | console.log(data); 159 | break; 160 | case MSG_TYPE_OF_CALL_SCORE: 161 | $("#PlayerMsg"+data.PlayerId).html("玩家"+data.PlayerId+"叫地主"+data.Score+"分"); 162 | if (data.PlayerId == currPlayerId){ 163 | $("#divScore").hide(); 164 | } 165 | console.log(data); 166 | break; 167 | case MSG_TYPE_OF_SCORE_CHANGE: 168 | print("底分变为"+data.Score) 169 | console.log(data); 170 | break; 171 | case MSG_TYPE_OF_GAME_OVER: 172 | print("游戏结束"); 173 | console.log(data); 174 | break; 175 | case MSG_TYPE_OF_TIME_TICKER: 176 | $(".timeDown").html(data.Msg); 177 | break; 178 | default: 179 | print("未知消息子类型"); 180 | console.log(data); 181 | } 182 | break; 183 | default: 184 | console.log(evt.data) 185 | } 186 | } 187 | ws.onerror = function(evt) { 188 | print("ERROR: " + evt.data); 189 | } 190 | } 191 | 192 | 193 | function closeConnection() { 194 | if (!ws) { 195 | return false; 196 | } 197 | ws.close(); 198 | } 199 | 200 | function send(evt) { 201 | if (!ws) { 202 | return false; 203 | } 204 | ws.send(input.value); 205 | } 206 | 207 | function ready() { 208 | var readyMsg = {} 209 | readyMsg.MsgType = MSG_TYPE_OF_READY; 210 | ws.send(JSON.stringify(readyMsg)); 211 | } 212 | 213 | function unReady(){ 214 | 215 | } 216 | 217 | function chooseCard(obj){ 218 | if($(obj).hasClass('chooseYes')){ 219 | $(obj).removeClass('chooseYes'); 220 | }else{ 221 | $(obj).addClass('chooseYes'); 222 | } 223 | } 224 | 225 | function sendScore() { 226 | msg = {}; 227 | msg.MsgType = MSG_TYPE_OF_CALL_SCORE; 228 | msg.Data = { 229 | "Score":$("#score").val() 230 | } 231 | if(!ws){ 232 | return false 233 | } 234 | ws.send(JSON.stringify(msg)); 235 | $("#divScore").hide(); 236 | } 237 | 238 | function pass(){ 239 | msg = {}; 240 | cardIndex = []; 241 | lastPlayCardIndex = []; 242 | msg.MsgType = MSG_TYPE_OF_PASS; 243 | ws.send(JSON.stringify(msg)); 244 | } 245 | 246 | function playCards(){ 247 | msg = {}; 248 | msg.MsgType = MSG_TYPE_OF_PLAY_CARD; 249 | cardIndex = []; 250 | lastPlayCardIndex = [] 251 | $("#userCards").find('div').each(function(i,o){ 252 | if($(o).hasClass('chooseYes')){ 253 | cardIndex.push($($(o).find('input')[0]).val()); 254 | lastPlayCardIndex.push($($(o).find('input')[0]).val()); 255 | } 256 | }) 257 | if(cardIndex.length == 0){ 258 | alert('请选择牌,再点击出牌'); 259 | } 260 | msg.Data = { 261 | 'CardIndex':cardIndex 262 | } 263 | ws.send(JSON.stringify(msg)); 264 | } 265 | 266 | function cardHints(){ 267 | cardIndex = []; 268 | lastPlayCardIndex = []; 269 | $("#divPlay").show(); 270 | msg = {}; 271 | msg.MsgType = MSG_TYPE_OF_HINT; 272 | ws.send(JSON.stringify(msg)); 273 | } -------------------------------------------------------------------------------- /program/game/player/player.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import ( 4 | "sync" 5 | "strconv" 6 | "github.com/tidwall/gjson" 7 | "fmt" 8 | "landlord/program/model" 9 | "landlord/program/game/msg" 10 | "landlord/program/game/games" 11 | "landlord/program/game" 12 | "github.com/sirupsen/logrus" 13 | "time" 14 | "landlord/program/connection" 15 | "github.com/wqtapp/poker" 16 | "github.com/wqtapp/pokergame" 17 | ) 18 | 19 | /** 20 | 定义游戏玩家对象 21 | */ 22 | type Player struct { 23 | User *model.User 24 | Conn *connection.WebSocketConnection//用户socket链接 25 | sync.RWMutex 26 | PokerCards poker.PokerSet //玩家手里的扑克牌0 27 | 28 | Index int //在桌子上的索引 29 | IsReady bool //是否准备 30 | IsAuto bool //是否托管 31 | PlayedCardIndexs []int //已经出牌的ID 32 | callScoreChan chan int //叫地主通道 33 | playCardsChan chan []int //出牌的索引切片通道 34 | stopTimeChan chan byte //停止倒计时的通道 35 | 36 | IsOnline bool //是否在线,用于断线重连 37 | UpLineTime time.Time 38 | OffLine time.Time 39 | PokerRecorder pokergame.IRecorder 40 | PokerAnalyzer pokergame.IAnalyzer 41 | 42 | UseablePokerSets []poker.PokerSet 43 | CurrHintSetIndex int 44 | } 45 | 46 | func NewPlayer(user *model.User,conn *connection.WebSocketConnection) *Player { 47 | player := Player{ 48 | User:user, 49 | Conn:conn, 50 | PlayedCardIndexs:[]int{}, 51 | callScoreChan:make(chan int), 52 | playCardsChan:make(chan []int), 53 | stopTimeChan:make(chan byte), 54 | } 55 | return &player 56 | } 57 | 58 | func (p *Player) GetPlayerUser() *model.User{ 59 | return p.User 60 | } 61 | 62 | func (p *Player) GetIndex() int{ 63 | return p.Index 64 | } 65 | 66 | func (p *Player) SetIndex(index int){ 67 | p.Lock() 68 | p.Index = index 69 | p.Unlock() 70 | } 71 | 72 | func (p *Player) GetReadyStatus() bool{ 73 | return p.IsReady 74 | } 75 | 76 | func (p *Player) GetAutoStatus() bool{ 77 | return p.IsAuto 78 | } 79 | 80 | func (p *Player) GetPlayedCardIndexs() []int{ 81 | return p.PlayedCardIndexs 82 | } 83 | 84 | func (p *Player) GetPlayerCards(indexs []int) poker.PokerSet{ 85 | if indexs != nil && len(indexs) > 0{ 86 | temCards := poker.PokerSet{} 87 | for _,i := range indexs{ 88 | temCards = append(temCards,p.PokerCards[i]) 89 | } 90 | return temCards 91 | }else{ 92 | return p.PokerCards 93 | } 94 | } 95 | 96 | func (p *Player) SetPokerCards(cards poker.PokerSet){ 97 | 98 | p.Lock() 99 | p.PokerCards = cards 100 | p.Unlock() 101 | logrus.Debug("发牌给玩家"+strconv.Itoa(p.GetPlayerUser().Id),cards) 102 | msg,err := msg.NewSendCardMsg(cards) 103 | if err == nil{ 104 | p.SendMsg(msg) 105 | }else{ 106 | fmt.Println(err.Error()) 107 | } 108 | } 109 | 110 | func (p *Player) StartCallScore(){ 111 | currMsg,err := msg.NewCallScoreMsg() 112 | if err == nil{ 113 | p.SendMsg(currMsg) 114 | 115 | go func(){ 116 | score := <-p.callScoreChan 117 | game,err := game.GetPlayerGame(p) 118 | if err == nil{ 119 | game.PlayerCallScore(p,score) 120 | }else{ 121 | currMsg,err1 := msg.NewPlayCardsErrorMsg(err.Error()) 122 | if err1 == nil{ 123 | p.SendMsg(currMsg) 124 | } 125 | fmt.Println(err.Error()) 126 | } 127 | }() 128 | //启动定时器,限制叫地主时间,过时自动不叫 129 | go func(){ 130 | //给玩家发送定时消息 131 | game,err := game.GetPlayerGame(p) 132 | if err == nil{ 133 | second := 7 134 | for { 135 | select { 136 | case <-p.stopTimeChan: 137 | fmt.Println("用户叫地主,定时器退出") 138 | return 139 | default: 140 | game.BroadCastMsg(p,msg.MSG_TYPE_OF_TIME_TICKER,strconv.Itoa(second)) 141 | second-- 142 | if second <= 0{ 143 | p.callScoreChan<-0 144 | return 145 | } 146 | time.Sleep(time.Second) 147 | } 148 | } 149 | }else{ 150 | fmt.Println("未获得用户game") 151 | } 152 | }() 153 | }else{ 154 | fmt.Println(err.Error()) 155 | } 156 | } 157 | 158 | func (p *Player) StartPlay(){ 159 | currMsg,err := msg.NewPlayCardMsg() 160 | if err == nil{ 161 | p.Lock() 162 | currGame,err := game.GetPlayerGame(p) 163 | if err == nil{ 164 | lastCards := currGame.GetLastCard() 165 | //如果上家没有出牌或者上次是当前玩家出牌,提示可用最小牌即可,否则根据上轮出牌给出可用的扑克牌 166 | if lastCards == nil || lastCards.PlayerIndex == p.Index || currGame.IsLastCardUserFinish(){ 167 | p.UseablePokerSets = []poker.PokerSet{p.PokerAnalyzer.GetMinPlayableCards()} 168 | }else{ 169 | p.UseablePokerSets = p.PokerAnalyzer.GetUseableCards(lastCards.PokerSetTypeInfo) 170 | } 171 | //重新分析完牌型,将当前提示的牌型索引重置为0 172 | p.CurrHintSetIndex = 0 173 | } 174 | //如果有牌可以出,发送出牌消息,否则发送要不起消息 175 | if len(p.UseablePokerSets) > 0 && p.UseablePokerSets[0].CountCards() > 0{ 176 | p.Unlock() 177 | p.SendMsg(currMsg) 178 | }else{ 179 | p.Unlock() 180 | //此处应发送要不起消息 181 | p.SendMsg(currMsg) 182 | //todo 183 | } 184 | 185 | go func(){ 186 | cardIndexs := <-p.playCardsChan 187 | if len(cardIndexs) == 0{ 188 | currGame.PlayerPassCard(p) 189 | }else{ 190 | currGame.PlayerPlayCards(p,cardIndexs) 191 | } 192 | }() 193 | //启动定时器,限制出牌时间,超时自动出牌 194 | go func(){ 195 | //给玩家发送定时消息 196 | second := 3 197 | for { 198 | select { 199 | case <-p.stopTimeChan: 200 | return 201 | default: 202 | currGame.BroadCastMsg(p,msg.MSG_TYPE_OF_TIME_TICKER,strconv.Itoa(second)) 203 | second-- 204 | if second <= 0{ 205 | p.autoPlay(currGame) 206 | return 207 | } 208 | time.Sleep(time.Second) 209 | } 210 | } 211 | }() 212 | }else{ 213 | fmt.Println(err.Error()) 214 | } 215 | } 216 | 217 | func (p *Player)autoPlay(currGame game.IGame){ 218 | if len(p.UseablePokerSets) > 0 { 219 | indexs,err := p.PokerCards.GetPokerIndexs(p.UseablePokerSets[0]) 220 | if err == nil{ 221 | p.playCardsChan<- indexs 222 | }else{ 223 | fmt.Println(err.Error()) 224 | p.playCardsChan<- []int{} 225 | } 226 | }else{ 227 | p.playCardsChan<- []int{} 228 | } 229 | } 230 | 231 | func fiterCardIndex(cardIndexs []int,playedCardIndexs []int) []int{ 232 | //检测待出牌切片中牌是否已经出过 233 | for j,index := range cardIndexs { 234 | for _,playedIndex := range playedCardIndexs{ 235 | if index == playedIndex{ 236 | cardIndexs[j] = -1 237 | break 238 | } 239 | } 240 | } 241 | //重新整理待出的牌 242 | playIndexs := []int{} 243 | for _,index := range cardIndexs { 244 | if index != -1{ 245 | playIndexs = append(playIndexs,index) 246 | } 247 | } 248 | return playIndexs 249 | } 250 | 251 | func (p *Player) CallScore(score int){ 252 | p.stopTimeChan<-1 253 | p.callScoreChan<-score 254 | } 255 | //出牌 256 | func (p *Player) PlayCards(cardIndexs []int){ 257 | 258 | p.RLock() 259 | for _,index := range cardIndexs{ 260 | //判断是否是之前出过的牌 261 | if p.PlayedCardIndexs != nil { 262 | for _,playedIndex := range p.PlayedCardIndexs{ 263 | if index == playedIndex { 264 | p.PlayCardError("出牌中包含已出的牌") 265 | p.RUnlock() 266 | return 267 | } 268 | } 269 | } 270 | } 271 | p.RUnlock() 272 | p.stopTimeChan<-1 273 | p.playCardsChan<-cardIndexs 274 | } 275 | //按照桌号加入牌桌 276 | func (p *Player) JoinGame(gameType int,gameId int){ 277 | game,err := game.GetRoom().GetGame(gameType,gameId) 278 | if err != nil{ 279 | fmt.Println(err.Error()) 280 | }else{ 281 | err := game.AddPlayer(p) 282 | if err != nil{ 283 | println(err.Error()) 284 | } 285 | } 286 | } 287 | //开牌桌 288 | func (p *Player) CreateGame(gameType int,baseScore int){ 289 | err := games.NewGame(gameType,baseScore).AddPlayer(p) 290 | if err != nil{ 291 | println(err.Error()) 292 | } 293 | } 294 | 295 | func (p *Player) LeaveGame() { 296 | game,err := game.GetPlayerGame(p) 297 | if err == nil { 298 | err := game.RemovePlayer(p) 299 | if err != nil{ 300 | println(err.Error()) 301 | } 302 | }else{ 303 | println(err.Error()) 304 | } 305 | } 306 | //用户跟该桌所有人说话 307 | func (p *Player) SayToOthers(msg []byte){ 308 | 309 | game,err := game.GetPlayerGame(p) 310 | if err == nil { 311 | game.SayToOthers(p,msg) 312 | }else{ 313 | //todo 314 | } 315 | } 316 | //用户跟该桌某一个说话 317 | func (p *Player) SayToAnother(id int,msg []byte){ 318 | game,err := game.GetPlayerGame(p) 319 | if err == nil { 320 | game.SayToAnother(p,id,msg) 321 | }else{ 322 | //todo 323 | } 324 | } 325 | 326 | func (p *Player)ResolveMsg(msgB []byte) error{ 327 | fmt.Println(string(msgB)) 328 | msgType,err := strconv.Atoi(gjson.Get(string(msgB),"MsgType").String()) 329 | if err != nil{ 330 | p.SendMsg(msgB) 331 | return err 332 | } 333 | 334 | switch msgType { 335 | case msg.MSG_TYPE_OF_AUTO: 336 | 337 | case msg.MSG_TYPE_OF_UN_READY: 338 | go p.UnReady() 339 | case msg.MSG_TYPE_OF_READY: 340 | go p.Ready() 341 | case msg.MSG_TYPE_OF_PLAY_CARD: 342 | cardIndex := gjson.Get(string(msgB),"Data.CardIndex").Array() 343 | cards := []int{} 344 | for _,card := range cardIndex{ 345 | cards = append(cards,int(card.Int())) 346 | } 347 | go p.PlayCards(cards) 348 | case msg.MSG_TYPE_OF_PASS: 349 | go p.Pass() 350 | case msg.MSG_TYPE_OF_LEAVE_TABLE: 351 | 352 | case msg.MSG_TYPE_OF_JOIN_TABLE: 353 | 354 | case msg.MSG_TYPE_OF_HINT: 355 | 356 | case msg.MSG_TYPE_OF_CALL_SCORE: 357 | score,_ := strconv.Atoi(gjson.Get(string(msgB),"Data.Score").String()) 358 | go p.CallScore(score) 359 | 360 | default: 361 | p.Conn.SendMsgWithType(msgType,msgB) 362 | } 363 | 364 | return nil 365 | } 366 | 367 | func (p *Player)Ready(){ 368 | p.Lock() 369 | p.IsReady = true 370 | p.Unlock() 371 | 372 | game,err := game.GetPlayerGame(p) 373 | if err == nil { 374 | game.PlayerReady(p) 375 | }else{ 376 | msg,err1 := msg.NewPlayCardsErrorMsg(err.Error()) 377 | if err1 == nil{ 378 | p.SendMsg(msg) 379 | } 380 | fmt.Println(err.Error()) 381 | } 382 | } 383 | 384 | func (p *Player) UnReady(){ 385 | p.Lock() 386 | p.IsReady = false 387 | p.Unlock() 388 | 389 | game,err := game.GetPlayerGame(p) 390 | if err == nil { 391 | game.PlayerUnReady(p) 392 | }else{ 393 | msg,err1 := msg.NewPlayCardsErrorMsg(err.Error()) 394 | if err1 == nil{ 395 | p.SendMsg(msg) 396 | } 397 | fmt.Println(err.Error()) 398 | } 399 | } 400 | //过牌 401 | func (p *Player)Pass(){ 402 | game,err := game.GetPlayerGame(p) 403 | p.stopTimeChan<-1 404 | if err == nil { 405 | game.PlayerPassCard(p) 406 | }else{ 407 | msg,err1 := msg.NewPlayCardsErrorMsg(err.Error()) 408 | if err1 == nil{ 409 | p.SendMsg(msg) 410 | } 411 | p.StartPlay() 412 | fmt.Println(err.Error()) 413 | } 414 | } 415 | //出牌成功 416 | func (p *Player) PlayCardSuccess(cardIndexs []int){ 417 | 418 | if p.PlayedCardIndexs == nil{ 419 | p.PlayedCardIndexs = []int{} 420 | } 421 | 422 | for _,index := range cardIndexs{ 423 | p.PlayedCardIndexs = append(p.PlayedCardIndexs,index) 424 | } 425 | 426 | SendMsgToPlayer(p,msg.MSG_TYPE_OF_PLAY_CARD_SUCCESS,"用户出牌成功") 427 | } 428 | 429 | func (p *Player)IsOutOfCards() bool{ 430 | return len(p.PlayedCardIndexs) == len(p.PokerCards) 431 | } 432 | //出牌出错 433 | func (p *Player) PlayCardError(error string){ 434 | SendMsgToPlayer(p,msg.MSG_TYPE_OF_PLAY_ERROR,error) 435 | } 436 | //提示出牌 437 | func(p *Player) HintCards(){ 438 | game,err := game.GetPlayerGame(p) 439 | if err == nil { 440 | game.HintCards(p) 441 | }else{ 442 | msg,err1 := msg.NewPlayCardsErrorMsg(err.Error()) 443 | if err1 == nil{ 444 | p.SendMsg(msg) 445 | } 446 | fmt.Println(err.Error()) 447 | } 448 | } 449 | 450 | func (p *Player) SendMsg(msg []byte){ 451 | p.Conn.SendMsg(msg) 452 | } 453 | 454 | func (p *Player) SetPokerRecorder(recorder pokergame.IRecorder){ 455 | p.PokerRecorder = recorder 456 | } 457 | func (p *Player) SetPokerAnalyzer(analyzer pokergame.IAnalyzer){ 458 | p.PokerAnalyzer = analyzer 459 | } -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "crypto/tls" 11 | "errors" 12 | "io" 13 | "io/ioutil" 14 | "net" 15 | "net/http" 16 | "net/http/httptrace" 17 | "net/url" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | // ErrBadHandshake is returned when the server response to opening handshake is 23 | // invalid. 24 | var ErrBadHandshake = errors.New("websocket: bad handshake") 25 | 26 | var errInvalidCompression = errors.New("websocket: invalid compression negotiation") 27 | 28 | // NewClient creates a new client connection using the given net connection. 29 | // The URL u specifies the host and request URI. Use requestHeader to specify 30 | // the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies 31 | // (Cookie). Use the response.Header to get the selected subprotocol 32 | // (Sec-WebSocket-Protocol) and cookies (Set-Cookie). 33 | // 34 | // If the WebSocket handshake fails, ErrBadHandshake is returned along with a 35 | // non-nil *http.Response so that callers can handle redirects, authentication, 36 | // etc. 37 | // 38 | // Deprecated: Use Dialer instead. 39 | func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { 40 | d := Dialer{ 41 | ReadBufferSize: readBufSize, 42 | WriteBufferSize: writeBufSize, 43 | NetDial: func(net, addr string) (net.Conn, error) { 44 | return netConn, nil 45 | }, 46 | } 47 | return d.Dial(u.String(), requestHeader) 48 | } 49 | 50 | // A Dialer contains options for connecting to WebSocket server. 51 | type Dialer struct { 52 | // NetDial specifies the dial function for creating TCP connections. If 53 | // NetDial is nil, net.Dial is used. 54 | NetDial func(network, addr string) (net.Conn, error) 55 | 56 | // NetDialContext specifies the dial function for creating TCP connections. If 57 | // NetDialContext is nil, net.DialContext is used. 58 | NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error) 59 | 60 | // Proxy specifies a function to return a proxy for a given 61 | // Request. If the function returns a non-nil error, the 62 | // request is aborted with the provided error. 63 | // If Proxy is nil or returns a nil *URL, no proxy is used. 64 | Proxy func(*http.Request) (*url.URL, error) 65 | 66 | // TLSClientConfig specifies the TLS configuration to use with tls.Client. 67 | // If nil, the default configuration is used. 68 | TLSClientConfig *tls.Config 69 | 70 | // HandshakeTimeout specifies the duration for the handshake to complete. 71 | HandshakeTimeout time.Duration 72 | 73 | // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer 74 | // size is zero, then a useful default size is used. The I/O buffer sizes 75 | // do not limit the size of the messages that can be sent or received. 76 | ReadBufferSize, WriteBufferSize int 77 | 78 | // WriteBufferPool is a pool of buffers for write operations. If the value 79 | // is not set, then write buffers are allocated to the connection for the 80 | // lifetime of the connection. 81 | // 82 | // A pool is most useful when the application has a modest volume of writes 83 | // across a large number of connections. 84 | // 85 | // Applications should use a single pool for each unique value of 86 | // WriteBufferSize. 87 | WriteBufferPool BufferPool 88 | 89 | // Subprotocols specifies the client's requested subprotocols. 90 | Subprotocols []string 91 | 92 | // EnableCompression specifies if the client should attempt to negotiate 93 | // per message compression (RFC 7692). Setting this value to true does not 94 | // guarantee that compression will be supported. Currently only "no context 95 | // takeover" modes are supported. 96 | EnableCompression bool 97 | 98 | // Jar specifies the cookie jar. 99 | // If Jar is nil, cookies are not sent in requests and ignored 100 | // in responses. 101 | Jar http.CookieJar 102 | } 103 | 104 | // Dial creates a new client connection by calling DialContext with a background context. 105 | func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { 106 | return d.DialContext(context.Background(), urlStr, requestHeader) 107 | } 108 | 109 | var errMalformedURL = errors.New("malformed ws or wss URL") 110 | 111 | func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { 112 | hostPort = u.Host 113 | hostNoPort = u.Host 114 | if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { 115 | hostNoPort = hostNoPort[:i] 116 | } else { 117 | switch u.Scheme { 118 | case "wss": 119 | hostPort += ":443" 120 | case "https": 121 | hostPort += ":443" 122 | default: 123 | hostPort += ":80" 124 | } 125 | } 126 | return hostPort, hostNoPort 127 | } 128 | 129 | // DefaultDialer is a dialer with all fields set to the default values. 130 | var DefaultDialer = &Dialer{ 131 | Proxy: http.ProxyFromEnvironment, 132 | HandshakeTimeout: 45 * time.Second, 133 | } 134 | 135 | // nilDialer is dialer to use when receiver is nil. 136 | var nilDialer = *DefaultDialer 137 | 138 | // DialContext creates a new client connection. Use requestHeader to specify the 139 | // origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). 140 | // Use the response.Header to get the selected subprotocol 141 | // (Sec-WebSocket-Protocol) and cookies (Set-Cookie). 142 | // 143 | // The context will be used in the request and in the Dialer 144 | // 145 | // If the WebSocket handshake fails, ErrBadHandshake is returned along with a 146 | // non-nil *http.Response so that callers can handle redirects, authentication, 147 | // etcetera. The response body may not contain the entire response and does not 148 | // need to be closed by the application. 149 | func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { 150 | if d == nil { 151 | d = &nilDialer 152 | } 153 | 154 | challengeKey, err := generateChallengeKey() 155 | if err != nil { 156 | return nil, nil, err 157 | } 158 | 159 | u, err := url.Parse(urlStr) 160 | if err != nil { 161 | return nil, nil, err 162 | } 163 | 164 | switch u.Scheme { 165 | case "ws": 166 | u.Scheme = "http" 167 | case "wss": 168 | u.Scheme = "https" 169 | default: 170 | return nil, nil, errMalformedURL 171 | } 172 | 173 | if u.User != nil { 174 | // User name and password are not allowed in websocket URIs. 175 | return nil, nil, errMalformedURL 176 | } 177 | 178 | req := &http.Request{ 179 | Method: "GET", 180 | URL: u, 181 | Proto: "HTTP/1.1", 182 | ProtoMajor: 1, 183 | ProtoMinor: 1, 184 | Header: make(http.Header), 185 | Host: u.Host, 186 | } 187 | req = req.WithContext(ctx) 188 | 189 | // Set the cookies present in the cookie jar of the dialer 190 | if d.Jar != nil { 191 | for _, cookie := range d.Jar.Cookies(u) { 192 | req.AddCookie(cookie) 193 | } 194 | } 195 | 196 | // Set the request headers using the capitalization for names and values in 197 | // RFC examples. Although the capitalization shouldn't matter, there are 198 | // servers that depend on it. The Header.Set method is not used because the 199 | // method canonicalizes the header names. 200 | req.Header["Upgrade"] = []string{"websocket"} 201 | req.Header["Connection"] = []string{"Upgrade"} 202 | req.Header["Sec-WebSocket-Key"] = []string{challengeKey} 203 | req.Header["Sec-WebSocket-Version"] = []string{"13"} 204 | if len(d.Subprotocols) > 0 { 205 | req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")} 206 | } 207 | for k, vs := range requestHeader { 208 | switch { 209 | case k == "Host": 210 | if len(vs) > 0 { 211 | req.Host = vs[0] 212 | } 213 | case k == "Upgrade" || 214 | k == "Connection" || 215 | k == "Sec-Websocket-Key" || 216 | k == "Sec-Websocket-Version" || 217 | k == "Sec-Websocket-Extensions" || 218 | (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): 219 | return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) 220 | case k == "Sec-Websocket-Protocol": 221 | req.Header["Sec-WebSocket-Protocol"] = vs 222 | default: 223 | req.Header[k] = vs 224 | } 225 | } 226 | 227 | if d.EnableCompression { 228 | req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"} 229 | } 230 | 231 | if d.HandshakeTimeout != 0 { 232 | var cancel func() 233 | ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout) 234 | defer cancel() 235 | } 236 | 237 | // Get network dial function. 238 | var netDial func(network, add string) (net.Conn, error) 239 | 240 | if d.NetDialContext != nil { 241 | netDial = func(network, addr string) (net.Conn, error) { 242 | return d.NetDialContext(ctx, network, addr) 243 | } 244 | } else if d.NetDial != nil { 245 | netDial = d.NetDial 246 | } else { 247 | netDialer := &net.Dialer{} 248 | netDial = func(network, addr string) (net.Conn, error) { 249 | return netDialer.DialContext(ctx, network, addr) 250 | } 251 | } 252 | 253 | // If needed, wrap the dial function to set the connection deadline. 254 | if deadline, ok := ctx.Deadline(); ok { 255 | forwardDial := netDial 256 | netDial = func(network, addr string) (net.Conn, error) { 257 | c, err := forwardDial(network, addr) 258 | if err != nil { 259 | return nil, err 260 | } 261 | err = c.SetDeadline(deadline) 262 | if err != nil { 263 | c.Close() 264 | return nil, err 265 | } 266 | return c, nil 267 | } 268 | } 269 | 270 | // If needed, wrap the dial function to connect through a proxy. 271 | if d.Proxy != nil { 272 | proxyURL, err := d.Proxy(req) 273 | if err != nil { 274 | return nil, nil, err 275 | } 276 | if proxyURL != nil { 277 | dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial)) 278 | if err != nil { 279 | return nil, nil, err 280 | } 281 | netDial = dialer.Dial 282 | } 283 | } 284 | 285 | hostPort, hostNoPort := hostPortNoPort(u) 286 | trace := httptrace.ContextClientTrace(ctx) 287 | if trace != nil && trace.GetConn != nil { 288 | trace.GetConn(hostPort) 289 | } 290 | 291 | netConn, err := netDial("tcp", hostPort) 292 | if trace != nil && trace.GotConn != nil { 293 | trace.GotConn(httptrace.GotConnInfo{ 294 | Conn: netConn, 295 | }) 296 | } 297 | if err != nil { 298 | return nil, nil, err 299 | } 300 | 301 | defer func() { 302 | if netConn != nil { 303 | netConn.Close() 304 | } 305 | }() 306 | 307 | if u.Scheme == "https" { 308 | cfg := cloneTLSConfig(d.TLSClientConfig) 309 | if cfg.ServerName == "" { 310 | cfg.ServerName = hostNoPort 311 | } 312 | tlsConn := tls.Client(netConn, cfg) 313 | netConn = tlsConn 314 | 315 | var err error 316 | if trace != nil { 317 | err = doHandshakeWithTrace(trace, tlsConn, cfg) 318 | } else { 319 | err = doHandshake(tlsConn, cfg) 320 | } 321 | 322 | if err != nil { 323 | return nil, nil, err 324 | } 325 | } 326 | 327 | conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil) 328 | 329 | if err := req.Write(netConn); err != nil { 330 | return nil, nil, err 331 | } 332 | 333 | if trace != nil && trace.GotFirstResponseByte != nil { 334 | if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 { 335 | trace.GotFirstResponseByte() 336 | } 337 | } 338 | 339 | resp, err := http.ReadResponse(conn.br, req) 340 | if err != nil { 341 | return nil, nil, err 342 | } 343 | 344 | if d.Jar != nil { 345 | if rc := resp.Cookies(); len(rc) > 0 { 346 | d.Jar.SetCookies(u, rc) 347 | } 348 | } 349 | 350 | if resp.StatusCode != 101 || 351 | !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || 352 | !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || 353 | resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) { 354 | // Before closing the network connection on return from this 355 | // function, slurp up some of the response to aid application 356 | // debugging. 357 | buf := make([]byte, 1024) 358 | n, _ := io.ReadFull(resp.Body, buf) 359 | resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) 360 | return nil, resp, ErrBadHandshake 361 | } 362 | 363 | for _, ext := range parseExtensions(resp.Header) { 364 | if ext[""] != "permessage-deflate" { 365 | continue 366 | } 367 | _, snct := ext["server_no_context_takeover"] 368 | _, cnct := ext["client_no_context_takeover"] 369 | if !snct || !cnct { 370 | return nil, resp, errInvalidCompression 371 | } 372 | conn.newCompressionWriter = compressNoContextTakeover 373 | conn.newDecompressionReader = decompressNoContextTakeover 374 | break 375 | } 376 | 377 | resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) 378 | conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") 379 | 380 | netConn.SetDeadline(time.Time{}) 381 | netConn = nil // to avoid close in defer. 382 | return conn, resp, nil 383 | } 384 | 385 | func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error { 386 | if err := tlsConn.Handshake(); err != nil { 387 | return err 388 | } 389 | if !cfg.InsecureSkipVerify { 390 | if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { 391 | return err 392 | } 393 | } 394 | return nil 395 | } 396 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bufio" 9 | "errors" 10 | "io" 11 | "net/http" 12 | "net/url" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // HandshakeError describes an error with the handshake from the peer. 18 | type HandshakeError struct { 19 | message string 20 | } 21 | 22 | func (e HandshakeError) Error() string { return e.message } 23 | 24 | // Upgrader specifies parameters for upgrading an HTTP connection to a 25 | // WebSocket connection. 26 | type Upgrader struct { 27 | // HandshakeTimeout specifies the duration for the handshake to complete. 28 | HandshakeTimeout time.Duration 29 | 30 | // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer 31 | // size is zero, then buffers allocated by the HTTP server are used. The 32 | // I/O buffer sizes do not limit the size of the messages that can be sent 33 | // or received. 34 | ReadBufferSize, WriteBufferSize int 35 | 36 | // WriteBufferPool is a pool of buffers for write operations. If the value 37 | // is not set, then write buffers are allocated to the connection for the 38 | // lifetime of the connection. 39 | // 40 | // A pool is most useful when the application has a modest volume of writes 41 | // across a large number of connections. 42 | // 43 | // Applications should use a single pool for each unique value of 44 | // WriteBufferSize. 45 | WriteBufferPool BufferPool 46 | 47 | // Subprotocols specifies the server's supported protocols in order of 48 | // preference. If this field is not nil, then the Upgrade method negotiates a 49 | // subprotocol by selecting the first match in this list with a protocol 50 | // requested by the client. If there's no match, then no protocol is 51 | // negotiated (the Sec-Websocket-Protocol header is not included in the 52 | // handshake response). 53 | Subprotocols []string 54 | 55 | // Error specifies the function for generating HTTP error responses. If Error 56 | // is nil, then http.Error is used to generate the HTTP response. 57 | Error func(w http.ResponseWriter, r *http.Request, status int, reason error) 58 | 59 | // CheckOrigin returns true if the request Origin header is acceptable. If 60 | // CheckOrigin is nil, then a safe default is used: return false if the 61 | // Origin request header is present and the origin host is not equal to 62 | // request Host header. 63 | // 64 | // A CheckOrigin function should carefully validate the request origin to 65 | // prevent cross-site request forgery. 66 | CheckOrigin func(r *http.Request) bool 67 | 68 | // EnableCompression specify if the server should attempt to negotiate per 69 | // message compression (RFC 7692). Setting this value to true does not 70 | // guarantee that compression will be supported. Currently only "no context 71 | // takeover" modes are supported. 72 | EnableCompression bool 73 | } 74 | 75 | func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { 76 | err := HandshakeError{reason} 77 | if u.Error != nil { 78 | u.Error(w, r, status, err) 79 | } else { 80 | w.Header().Set("Sec-Websocket-Version", "13") 81 | http.Error(w, http.StatusText(status), status) 82 | } 83 | return nil, err 84 | } 85 | 86 | // checkSameOrigin returns true if the origin is not set or is equal to the request host. 87 | func checkSameOrigin(r *http.Request) bool { 88 | origin := r.Header["Origin"] 89 | if len(origin) == 0 { 90 | return true 91 | } 92 | u, err := url.Parse(origin[0]) 93 | if err != nil { 94 | return false 95 | } 96 | return equalASCIIFold(u.Host, r.Host) 97 | } 98 | 99 | func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { 100 | if u.Subprotocols != nil { 101 | clientProtocols := Subprotocols(r) 102 | for _, serverProtocol := range u.Subprotocols { 103 | for _, clientProtocol := range clientProtocols { 104 | if clientProtocol == serverProtocol { 105 | return clientProtocol 106 | } 107 | } 108 | } 109 | } else if responseHeader != nil { 110 | return responseHeader.Get("Sec-Websocket-Protocol") 111 | } 112 | return "" 113 | } 114 | 115 | // Upgrade upgrades the HTTP server connection to the WebSocket protocol. 116 | // 117 | // The responseHeader is included in the response to the client's upgrade 118 | // request. Use the responseHeader to specify cookies (Set-Cookie) and the 119 | // application negotiated subprotocol (Sec-WebSocket-Protocol). 120 | // 121 | // If the upgrade fails, then Upgrade replies to the client with an HTTP error 122 | // response. 123 | func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { 124 | const badHandshake = "websocket: the client is not using the websocket protocol: " 125 | 126 | if !tokenListContainsValue(r.Header, "Connection", "upgrade") { 127 | return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header") 128 | } 129 | 130 | if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { 131 | return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header") 132 | } 133 | 134 | if r.Method != "GET" { 135 | return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET") 136 | } 137 | 138 | if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { 139 | return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") 140 | } 141 | 142 | if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { 143 | return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported") 144 | } 145 | 146 | checkOrigin := u.CheckOrigin 147 | if checkOrigin == nil { 148 | checkOrigin = checkSameOrigin 149 | } 150 | if !checkOrigin(r) { 151 | return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin") 152 | } 153 | 154 | challengeKey := r.Header.Get("Sec-Websocket-Key") 155 | if challengeKey == "" { 156 | return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank") 157 | } 158 | 159 | subprotocol := u.selectSubprotocol(r, responseHeader) 160 | 161 | // Negotiate PMCE 162 | var compress bool 163 | if u.EnableCompression { 164 | for _, ext := range parseExtensions(r.Header) { 165 | if ext[""] != "permessage-deflate" { 166 | continue 167 | } 168 | compress = true 169 | break 170 | } 171 | } 172 | 173 | h, ok := w.(http.Hijacker) 174 | if !ok { 175 | return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") 176 | } 177 | var brw *bufio.ReadWriter 178 | netConn, brw, err := h.Hijack() 179 | if err != nil { 180 | return u.returnError(w, r, http.StatusInternalServerError, err.Error()) 181 | } 182 | 183 | if brw.Reader.Buffered() > 0 { 184 | netConn.Close() 185 | return nil, errors.New("websocket: client sent data before handshake is complete") 186 | } 187 | 188 | var br *bufio.Reader 189 | if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 { 190 | // Reuse hijacked buffered reader as connection reader. 191 | br = brw.Reader 192 | } 193 | 194 | buf := bufioWriterBuffer(netConn, brw.Writer) 195 | 196 | var writeBuf []byte 197 | if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 { 198 | // Reuse hijacked write buffer as connection buffer. 199 | writeBuf = buf 200 | } 201 | 202 | c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf) 203 | c.subprotocol = subprotocol 204 | 205 | if compress { 206 | c.newCompressionWriter = compressNoContextTakeover 207 | c.newDecompressionReader = decompressNoContextTakeover 208 | } 209 | 210 | // Use larger of hijacked buffer and connection write buffer for header. 211 | p := buf 212 | if len(c.writeBuf) > len(p) { 213 | p = c.writeBuf 214 | } 215 | p = p[:0] 216 | 217 | p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) 218 | p = append(p, computeAcceptKey(challengeKey)...) 219 | p = append(p, "\r\n"...) 220 | if c.subprotocol != "" { 221 | p = append(p, "Sec-WebSocket-Protocol: "...) 222 | p = append(p, c.subprotocol...) 223 | p = append(p, "\r\n"...) 224 | } 225 | if compress { 226 | p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) 227 | } 228 | for k, vs := range responseHeader { 229 | if k == "Sec-Websocket-Protocol" { 230 | continue 231 | } 232 | for _, v := range vs { 233 | p = append(p, k...) 234 | p = append(p, ": "...) 235 | for i := 0; i < len(v); i++ { 236 | b := v[i] 237 | if b <= 31 { 238 | // prevent response splitting. 239 | b = ' ' 240 | } 241 | p = append(p, b) 242 | } 243 | p = append(p, "\r\n"...) 244 | } 245 | } 246 | p = append(p, "\r\n"...) 247 | 248 | // Clear deadlines set by HTTP server. 249 | netConn.SetDeadline(time.Time{}) 250 | 251 | if u.HandshakeTimeout > 0 { 252 | netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) 253 | } 254 | if _, err = netConn.Write(p); err != nil { 255 | netConn.Close() 256 | return nil, err 257 | } 258 | if u.HandshakeTimeout > 0 { 259 | netConn.SetWriteDeadline(time.Time{}) 260 | } 261 | 262 | return c, nil 263 | } 264 | 265 | // Upgrade upgrades the HTTP server connection to the WebSocket protocol. 266 | // 267 | // Deprecated: Use websocket.Upgrader instead. 268 | // 269 | // Upgrade does not perform origin checking. The application is responsible for 270 | // checking the Origin header before calling Upgrade. An example implementation 271 | // of the same origin policy check is: 272 | // 273 | // if req.Header.Get("Origin") != "http://"+req.Host { 274 | // http.Error(w, "Origin not allowed", http.StatusForbidden) 275 | // return 276 | // } 277 | // 278 | // If the endpoint supports subprotocols, then the application is responsible 279 | // for negotiating the protocol used on the connection. Use the Subprotocols() 280 | // function to get the subprotocols requested by the client. Use the 281 | // Sec-Websocket-Protocol response header to specify the subprotocol selected 282 | // by the application. 283 | // 284 | // The responseHeader is included in the response to the client's upgrade 285 | // request. Use the responseHeader to specify cookies (Set-Cookie) and the 286 | // negotiated subprotocol (Sec-Websocket-Protocol). 287 | // 288 | // The connection buffers IO to the underlying network connection. The 289 | // readBufSize and writeBufSize parameters specify the size of the buffers to 290 | // use. Messages can be larger than the buffers. 291 | // 292 | // If the request is not a valid WebSocket handshake, then Upgrade returns an 293 | // error of type HandshakeError. Applications should handle this error by 294 | // replying to the client with an HTTP error response. 295 | func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { 296 | u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} 297 | u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { 298 | // don't return errors to maintain backwards compatibility 299 | } 300 | u.CheckOrigin = func(r *http.Request) bool { 301 | // allow all connections by default 302 | return true 303 | } 304 | return u.Upgrade(w, r, responseHeader) 305 | } 306 | 307 | // Subprotocols returns the subprotocols requested by the client in the 308 | // Sec-Websocket-Protocol header. 309 | func Subprotocols(r *http.Request) []string { 310 | h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) 311 | if h == "" { 312 | return nil 313 | } 314 | protocols := strings.Split(h, ",") 315 | for i := range protocols { 316 | protocols[i] = strings.TrimSpace(protocols[i]) 317 | } 318 | return protocols 319 | } 320 | 321 | // IsWebSocketUpgrade returns true if the client requested upgrade to the 322 | // WebSocket protocol. 323 | func IsWebSocketUpgrade(r *http.Request) bool { 324 | return tokenListContainsValue(r.Header, "Connection", "upgrade") && 325 | tokenListContainsValue(r.Header, "Upgrade", "websocket") 326 | } 327 | 328 | // bufioReaderSize size returns the size of a bufio.Reader. 329 | func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int { 330 | // This code assumes that peek on a reset reader returns 331 | // bufio.Reader.buf[:0]. 332 | // TODO: Use bufio.Reader.Size() after Go 1.10 333 | br.Reset(originalReader) 334 | if p, err := br.Peek(0); err == nil { 335 | return cap(p) 336 | } 337 | return 0 338 | } 339 | 340 | // writeHook is an io.Writer that records the last slice passed to it vio 341 | // io.Writer.Write. 342 | type writeHook struct { 343 | p []byte 344 | } 345 | 346 | func (wh *writeHook) Write(p []byte) (int, error) { 347 | wh.p = p 348 | return len(p), nil 349 | } 350 | 351 | // bufioWriterBuffer grabs the buffer from a bufio.Writer. 352 | func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte { 353 | // This code assumes that bufio.Writer.buf[:1] is passed to the 354 | // bufio.Writer's underlying writer. 355 | var wh writeHook 356 | bw.Reset(&wh) 357 | bw.WriteByte(0) 358 | bw.Flush() 359 | 360 | bw.Reset(originalWriter) 361 | 362 | return wh.p[:cap(wh.p)] 363 | } 364 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/websocket/x_net_proxy.go: -------------------------------------------------------------------------------- 1 | // Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT. 2 | //go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy 3 | 4 | // Package proxy provides support for a variety of protocols to proxy network 5 | // data. 6 | // 7 | 8 | package websocket 9 | 10 | import ( 11 | "errors" 12 | "io" 13 | "net" 14 | "net/url" 15 | "os" 16 | "strconv" 17 | "strings" 18 | "sync" 19 | ) 20 | 21 | type proxy_direct struct{} 22 | 23 | // Direct is a direct proxy: one that makes network connections directly. 24 | var proxy_Direct = proxy_direct{} 25 | 26 | func (proxy_direct) Dial(network, addr string) (net.Conn, error) { 27 | return net.Dial(network, addr) 28 | } 29 | 30 | // A PerHost directs connections to a default Dialer unless the host name 31 | // requested matches one of a number of exceptions. 32 | type proxy_PerHost struct { 33 | def, bypass proxy_Dialer 34 | 35 | bypassNetworks []*net.IPNet 36 | bypassIPs []net.IP 37 | bypassZones []string 38 | bypassHosts []string 39 | } 40 | 41 | // NewPerHost returns a PerHost Dialer that directs connections to either 42 | // defaultDialer or bypass, depending on whether the connection matches one of 43 | // the configured rules. 44 | func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost { 45 | return &proxy_PerHost{ 46 | def: defaultDialer, 47 | bypass: bypass, 48 | } 49 | } 50 | 51 | // Dial connects to the address addr on the given network through either 52 | // defaultDialer or bypass. 53 | func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) { 54 | host, _, err := net.SplitHostPort(addr) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return p.dialerForRequest(host).Dial(network, addr) 60 | } 61 | 62 | func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer { 63 | if ip := net.ParseIP(host); ip != nil { 64 | for _, net := range p.bypassNetworks { 65 | if net.Contains(ip) { 66 | return p.bypass 67 | } 68 | } 69 | for _, bypassIP := range p.bypassIPs { 70 | if bypassIP.Equal(ip) { 71 | return p.bypass 72 | } 73 | } 74 | return p.def 75 | } 76 | 77 | for _, zone := range p.bypassZones { 78 | if strings.HasSuffix(host, zone) { 79 | return p.bypass 80 | } 81 | if host == zone[1:] { 82 | // For a zone ".example.com", we match "example.com" 83 | // too. 84 | return p.bypass 85 | } 86 | } 87 | for _, bypassHost := range p.bypassHosts { 88 | if bypassHost == host { 89 | return p.bypass 90 | } 91 | } 92 | return p.def 93 | } 94 | 95 | // AddFromString parses a string that contains comma-separated values 96 | // specifying hosts that should use the bypass proxy. Each value is either an 97 | // IP address, a CIDR range, a zone (*.example.com) or a host name 98 | // (localhost). A best effort is made to parse the string and errors are 99 | // ignored. 100 | func (p *proxy_PerHost) AddFromString(s string) { 101 | hosts := strings.Split(s, ",") 102 | for _, host := range hosts { 103 | host = strings.TrimSpace(host) 104 | if len(host) == 0 { 105 | continue 106 | } 107 | if strings.Contains(host, "/") { 108 | // We assume that it's a CIDR address like 127.0.0.0/8 109 | if _, net, err := net.ParseCIDR(host); err == nil { 110 | p.AddNetwork(net) 111 | } 112 | continue 113 | } 114 | if ip := net.ParseIP(host); ip != nil { 115 | p.AddIP(ip) 116 | continue 117 | } 118 | if strings.HasPrefix(host, "*.") { 119 | p.AddZone(host[1:]) 120 | continue 121 | } 122 | p.AddHost(host) 123 | } 124 | } 125 | 126 | // AddIP specifies an IP address that will use the bypass proxy. Note that 127 | // this will only take effect if a literal IP address is dialed. A connection 128 | // to a named host will never match an IP. 129 | func (p *proxy_PerHost) AddIP(ip net.IP) { 130 | p.bypassIPs = append(p.bypassIPs, ip) 131 | } 132 | 133 | // AddNetwork specifies an IP range that will use the bypass proxy. Note that 134 | // this will only take effect if a literal IP address is dialed. A connection 135 | // to a named host will never match. 136 | func (p *proxy_PerHost) AddNetwork(net *net.IPNet) { 137 | p.bypassNetworks = append(p.bypassNetworks, net) 138 | } 139 | 140 | // AddZone specifies a DNS suffix that will use the bypass proxy. A zone of 141 | // "example.com" matches "example.com" and all of its subdomains. 142 | func (p *proxy_PerHost) AddZone(zone string) { 143 | if strings.HasSuffix(zone, ".") { 144 | zone = zone[:len(zone)-1] 145 | } 146 | if !strings.HasPrefix(zone, ".") { 147 | zone = "." + zone 148 | } 149 | p.bypassZones = append(p.bypassZones, zone) 150 | } 151 | 152 | // AddHost specifies a host name that will use the bypass proxy. 153 | func (p *proxy_PerHost) AddHost(host string) { 154 | if strings.HasSuffix(host, ".") { 155 | host = host[:len(host)-1] 156 | } 157 | p.bypassHosts = append(p.bypassHosts, host) 158 | } 159 | 160 | // A Dialer is a means to establish a connection. 161 | type proxy_Dialer interface { 162 | // Dial connects to the given address via the proxy. 163 | Dial(network, addr string) (c net.Conn, err error) 164 | } 165 | 166 | // Auth contains authentication parameters that specific Dialers may require. 167 | type proxy_Auth struct { 168 | User, Password string 169 | } 170 | 171 | // FromEnvironment returns the dialer specified by the proxy related variables in 172 | // the environment. 173 | func proxy_FromEnvironment() proxy_Dialer { 174 | allProxy := proxy_allProxyEnv.Get() 175 | if len(allProxy) == 0 { 176 | return proxy_Direct 177 | } 178 | 179 | proxyURL, err := url.Parse(allProxy) 180 | if err != nil { 181 | return proxy_Direct 182 | } 183 | proxy, err := proxy_FromURL(proxyURL, proxy_Direct) 184 | if err != nil { 185 | return proxy_Direct 186 | } 187 | 188 | noProxy := proxy_noProxyEnv.Get() 189 | if len(noProxy) == 0 { 190 | return proxy 191 | } 192 | 193 | perHost := proxy_NewPerHost(proxy, proxy_Direct) 194 | perHost.AddFromString(noProxy) 195 | return perHost 196 | } 197 | 198 | // proxySchemes is a map from URL schemes to a function that creates a Dialer 199 | // from a URL with such a scheme. 200 | var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error) 201 | 202 | // RegisterDialerType takes a URL scheme and a function to generate Dialers from 203 | // a URL with that scheme and a forwarding Dialer. Registered schemes are used 204 | // by FromURL. 205 | func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) { 206 | if proxy_proxySchemes == nil { 207 | proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) 208 | } 209 | proxy_proxySchemes[scheme] = f 210 | } 211 | 212 | // FromURL returns a Dialer given a URL specification and an underlying 213 | // Dialer for it to make network requests. 214 | func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) { 215 | var auth *proxy_Auth 216 | if u.User != nil { 217 | auth = new(proxy_Auth) 218 | auth.User = u.User.Username() 219 | if p, ok := u.User.Password(); ok { 220 | auth.Password = p 221 | } 222 | } 223 | 224 | switch u.Scheme { 225 | case "socks5": 226 | return proxy_SOCKS5("tcp", u.Host, auth, forward) 227 | } 228 | 229 | // If the scheme doesn't match any of the built-in schemes, see if it 230 | // was registered by another package. 231 | if proxy_proxySchemes != nil { 232 | if f, ok := proxy_proxySchemes[u.Scheme]; ok { 233 | return f(u, forward) 234 | } 235 | } 236 | 237 | return nil, errors.New("proxy: unknown scheme: " + u.Scheme) 238 | } 239 | 240 | var ( 241 | proxy_allProxyEnv = &proxy_envOnce{ 242 | names: []string{"ALL_PROXY", "all_proxy"}, 243 | } 244 | proxy_noProxyEnv = &proxy_envOnce{ 245 | names: []string{"NO_PROXY", "no_proxy"}, 246 | } 247 | ) 248 | 249 | // envOnce looks up an environment variable (optionally by multiple 250 | // names) once. It mitigates expensive lookups on some platforms 251 | // (e.g. Windows). 252 | // (Borrowed from net/http/transport.go) 253 | type proxy_envOnce struct { 254 | names []string 255 | once sync.Once 256 | val string 257 | } 258 | 259 | func (e *proxy_envOnce) Get() string { 260 | e.once.Do(e.init) 261 | return e.val 262 | } 263 | 264 | func (e *proxy_envOnce) init() { 265 | for _, n := range e.names { 266 | e.val = os.Getenv(n) 267 | if e.val != "" { 268 | return 269 | } 270 | } 271 | } 272 | 273 | // SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address 274 | // with an optional username and password. See RFC 1928 and RFC 1929. 275 | func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) { 276 | s := &proxy_socks5{ 277 | network: network, 278 | addr: addr, 279 | forward: forward, 280 | } 281 | if auth != nil { 282 | s.user = auth.User 283 | s.password = auth.Password 284 | } 285 | 286 | return s, nil 287 | } 288 | 289 | type proxy_socks5 struct { 290 | user, password string 291 | network, addr string 292 | forward proxy_Dialer 293 | } 294 | 295 | const proxy_socks5Version = 5 296 | 297 | const ( 298 | proxy_socks5AuthNone = 0 299 | proxy_socks5AuthPassword = 2 300 | ) 301 | 302 | const proxy_socks5Connect = 1 303 | 304 | const ( 305 | proxy_socks5IP4 = 1 306 | proxy_socks5Domain = 3 307 | proxy_socks5IP6 = 4 308 | ) 309 | 310 | var proxy_socks5Errors = []string{ 311 | "", 312 | "general failure", 313 | "connection forbidden", 314 | "network unreachable", 315 | "host unreachable", 316 | "connection refused", 317 | "TTL expired", 318 | "command not supported", 319 | "address type not supported", 320 | } 321 | 322 | // Dial connects to the address addr on the given network via the SOCKS5 proxy. 323 | func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) { 324 | switch network { 325 | case "tcp", "tcp6", "tcp4": 326 | default: 327 | return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network) 328 | } 329 | 330 | conn, err := s.forward.Dial(s.network, s.addr) 331 | if err != nil { 332 | return nil, err 333 | } 334 | if err := s.connect(conn, addr); err != nil { 335 | conn.Close() 336 | return nil, err 337 | } 338 | return conn, nil 339 | } 340 | 341 | // connect takes an existing connection to a socks5 proxy server, 342 | // and commands the server to extend that connection to target, 343 | // which must be a canonical address with a host and port. 344 | func (s *proxy_socks5) connect(conn net.Conn, target string) error { 345 | host, portStr, err := net.SplitHostPort(target) 346 | if err != nil { 347 | return err 348 | } 349 | 350 | port, err := strconv.Atoi(portStr) 351 | if err != nil { 352 | return errors.New("proxy: failed to parse port number: " + portStr) 353 | } 354 | if port < 1 || port > 0xffff { 355 | return errors.New("proxy: port number out of range: " + portStr) 356 | } 357 | 358 | // the size here is just an estimate 359 | buf := make([]byte, 0, 6+len(host)) 360 | 361 | buf = append(buf, proxy_socks5Version) 362 | if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { 363 | buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword) 364 | } else { 365 | buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone) 366 | } 367 | 368 | if _, err := conn.Write(buf); err != nil { 369 | return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) 370 | } 371 | 372 | if _, err := io.ReadFull(conn, buf[:2]); err != nil { 373 | return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) 374 | } 375 | if buf[0] != 5 { 376 | return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) 377 | } 378 | if buf[1] == 0xff { 379 | return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") 380 | } 381 | 382 | // See RFC 1929 383 | if buf[1] == proxy_socks5AuthPassword { 384 | buf = buf[:0] 385 | buf = append(buf, 1 /* password protocol version */) 386 | buf = append(buf, uint8(len(s.user))) 387 | buf = append(buf, s.user...) 388 | buf = append(buf, uint8(len(s.password))) 389 | buf = append(buf, s.password...) 390 | 391 | if _, err := conn.Write(buf); err != nil { 392 | return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) 393 | } 394 | 395 | if _, err := io.ReadFull(conn, buf[:2]); err != nil { 396 | return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) 397 | } 398 | 399 | if buf[1] != 0 { 400 | return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") 401 | } 402 | } 403 | 404 | buf = buf[:0] 405 | buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */) 406 | 407 | if ip := net.ParseIP(host); ip != nil { 408 | if ip4 := ip.To4(); ip4 != nil { 409 | buf = append(buf, proxy_socks5IP4) 410 | ip = ip4 411 | } else { 412 | buf = append(buf, proxy_socks5IP6) 413 | } 414 | buf = append(buf, ip...) 415 | } else { 416 | if len(host) > 255 { 417 | return errors.New("proxy: destination host name too long: " + host) 418 | } 419 | buf = append(buf, proxy_socks5Domain) 420 | buf = append(buf, byte(len(host))) 421 | buf = append(buf, host...) 422 | } 423 | buf = append(buf, byte(port>>8), byte(port)) 424 | 425 | if _, err := conn.Write(buf); err != nil { 426 | return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) 427 | } 428 | 429 | if _, err := io.ReadFull(conn, buf[:4]); err != nil { 430 | return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) 431 | } 432 | 433 | failure := "unknown error" 434 | if int(buf[1]) < len(proxy_socks5Errors) { 435 | failure = proxy_socks5Errors[buf[1]] 436 | } 437 | 438 | if len(failure) > 0 { 439 | return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) 440 | } 441 | 442 | bytesToDiscard := 0 443 | switch buf[3] { 444 | case proxy_socks5IP4: 445 | bytesToDiscard = net.IPv4len 446 | case proxy_socks5IP6: 447 | bytesToDiscard = net.IPv6len 448 | case proxy_socks5Domain: 449 | _, err := io.ReadFull(conn, buf[:1]) 450 | if err != nil { 451 | return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) 452 | } 453 | bytesToDiscard = int(buf[0]) 454 | default: 455 | return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) 456 | } 457 | 458 | if cap(buf) < bytesToDiscard { 459 | buf = make([]byte, bytesToDiscard) 460 | } else { 461 | buf = buf[:bytesToDiscard] 462 | } 463 | if _, err := io.ReadFull(conn, buf); err != nil { 464 | return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) 465 | } 466 | 467 | // Also need to discard the port number 468 | if _, err := io.ReadFull(conn, buf[:2]); err != nil { 469 | return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) 470 | } 471 | 472 | return nil 473 | } 474 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/decode.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "math" 8 | "reflect" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func e(format string, args ...interface{}) error { 14 | return fmt.Errorf("toml: "+format, args...) 15 | } 16 | 17 | // Unmarshaler is the interface implemented by objects that can unmarshal a 18 | // TOML description of themselves. 19 | type Unmarshaler interface { 20 | UnmarshalTOML(interface{}) error 21 | } 22 | 23 | // Unmarshal decodes the contents of `p` in TOML format into a pointer `v`. 24 | func Unmarshal(p []byte, v interface{}) error { 25 | _, err := Decode(string(p), v) 26 | return err 27 | } 28 | 29 | // Primitive is a TOML value that hasn't been decoded into a Go value. 30 | // When using the various `Decode*` functions, the type `Primitive` may 31 | // be given to any value, and its decoding will be delayed. 32 | // 33 | // A `Primitive` value can be decoded using the `PrimitiveDecode` function. 34 | // 35 | // The underlying representation of a `Primitive` value is subject to change. 36 | // Do not rely on it. 37 | // 38 | // N.B. Primitive values are still parsed, so using them will only avoid 39 | // the overhead of reflection. They can be useful when you don't know the 40 | // exact type of TOML data until run time. 41 | type Primitive struct { 42 | undecoded interface{} 43 | context Key 44 | } 45 | 46 | // DEPRECATED! 47 | // 48 | // Use MetaData.PrimitiveDecode instead. 49 | func PrimitiveDecode(primValue Primitive, v interface{}) error { 50 | md := MetaData{decoded: make(map[string]bool)} 51 | return md.unify(primValue.undecoded, rvalue(v)) 52 | } 53 | 54 | // PrimitiveDecode is just like the other `Decode*` functions, except it 55 | // decodes a TOML value that has already been parsed. Valid primitive values 56 | // can *only* be obtained from values filled by the decoder functions, 57 | // including this method. (i.e., `v` may contain more `Primitive` 58 | // values.) 59 | // 60 | // Meta data for primitive values is included in the meta data returned by 61 | // the `Decode*` functions with one exception: keys returned by the Undecoded 62 | // method will only reflect keys that were decoded. Namely, any keys hidden 63 | // behind a Primitive will be considered undecoded. Executing this method will 64 | // update the undecoded keys in the meta data. (See the example.) 65 | func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error { 66 | md.context = primValue.context 67 | defer func() { md.context = nil }() 68 | return md.unify(primValue.undecoded, rvalue(v)) 69 | } 70 | 71 | // Decode will decode the contents of `data` in TOML format into a pointer 72 | // `v`. 73 | // 74 | // TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be 75 | // used interchangeably.) 76 | // 77 | // TOML arrays of tables correspond to either a slice of structs or a slice 78 | // of maps. 79 | // 80 | // TOML datetimes correspond to Go `time.Time` values. 81 | // 82 | // All other TOML types (float, string, int, bool and array) correspond 83 | // to the obvious Go types. 84 | // 85 | // An exception to the above rules is if a type implements the 86 | // encoding.TextUnmarshaler interface. In this case, any primitive TOML value 87 | // (floats, strings, integers, booleans and datetimes) will be converted to 88 | // a byte string and given to the value's UnmarshalText method. See the 89 | // Unmarshaler example for a demonstration with time duration strings. 90 | // 91 | // Key mapping 92 | // 93 | // TOML keys can map to either keys in a Go map or field names in a Go 94 | // struct. The special `toml` struct tag may be used to map TOML keys to 95 | // struct fields that don't match the key name exactly. (See the example.) 96 | // A case insensitive match to struct names will be tried if an exact match 97 | // can't be found. 98 | // 99 | // The mapping between TOML values and Go values is loose. That is, there 100 | // may exist TOML values that cannot be placed into your representation, and 101 | // there may be parts of your representation that do not correspond to 102 | // TOML values. This loose mapping can be made stricter by using the IsDefined 103 | // and/or Undecoded methods on the MetaData returned. 104 | // 105 | // This decoder will not handle cyclic types. If a cyclic type is passed, 106 | // `Decode` will not terminate. 107 | func Decode(data string, v interface{}) (MetaData, error) { 108 | rv := reflect.ValueOf(v) 109 | if rv.Kind() != reflect.Ptr { 110 | return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v)) 111 | } 112 | if rv.IsNil() { 113 | return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v)) 114 | } 115 | p, err := parse(data) 116 | if err != nil { 117 | return MetaData{}, err 118 | } 119 | md := MetaData{ 120 | p.mapping, p.types, p.ordered, 121 | make(map[string]bool, len(p.ordered)), nil, 122 | } 123 | return md, md.unify(p.mapping, indirect(rv)) 124 | } 125 | 126 | // DecodeFile is just like Decode, except it will automatically read the 127 | // contents of the file at `fpath` and decode it for you. 128 | func DecodeFile(fpath string, v interface{}) (MetaData, error) { 129 | bs, err := ioutil.ReadFile(fpath) 130 | if err != nil { 131 | return MetaData{}, err 132 | } 133 | return Decode(string(bs), v) 134 | } 135 | 136 | // DecodeReader is just like Decode, except it will consume all bytes 137 | // from the reader and decode it for you. 138 | func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { 139 | bs, err := ioutil.ReadAll(r) 140 | if err != nil { 141 | return MetaData{}, err 142 | } 143 | return Decode(string(bs), v) 144 | } 145 | 146 | // unify performs a sort of type unification based on the structure of `rv`, 147 | // which is the client representation. 148 | // 149 | // Any type mismatch produces an error. Finding a type that we don't know 150 | // how to handle produces an unsupported type error. 151 | func (md *MetaData) unify(data interface{}, rv reflect.Value) error { 152 | 153 | // Special case. Look for a `Primitive` value. 154 | if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { 155 | // Save the undecoded data and the key context into the primitive 156 | // value. 157 | context := make(Key, len(md.context)) 158 | copy(context, md.context) 159 | rv.Set(reflect.ValueOf(Primitive{ 160 | undecoded: data, 161 | context: context, 162 | })) 163 | return nil 164 | } 165 | 166 | // Special case. Unmarshaler Interface support. 167 | if rv.CanAddr() { 168 | if v, ok := rv.Addr().Interface().(Unmarshaler); ok { 169 | return v.UnmarshalTOML(data) 170 | } 171 | } 172 | 173 | // Special case. Handle time.Time values specifically. 174 | // TODO: Remove this code when we decide to drop support for Go 1.1. 175 | // This isn't necessary in Go 1.2 because time.Time satisfies the encoding 176 | // interfaces. 177 | if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) { 178 | return md.unifyDatetime(data, rv) 179 | } 180 | 181 | // Special case. Look for a value satisfying the TextUnmarshaler interface. 182 | if v, ok := rv.Interface().(TextUnmarshaler); ok { 183 | return md.unifyText(data, v) 184 | } 185 | // BUG(burntsushi) 186 | // The behavior here is incorrect whenever a Go type satisfies the 187 | // encoding.TextUnmarshaler interface but also corresponds to a TOML 188 | // hash or array. In particular, the unmarshaler should only be applied 189 | // to primitive TOML values. But at this point, it will be applied to 190 | // all kinds of values and produce an incorrect error whenever those values 191 | // are hashes or arrays (including arrays of tables). 192 | 193 | k := rv.Kind() 194 | 195 | // laziness 196 | if k >= reflect.Int && k <= reflect.Uint64 { 197 | return md.unifyInt(data, rv) 198 | } 199 | switch k { 200 | case reflect.Ptr: 201 | elem := reflect.New(rv.Type().Elem()) 202 | err := md.unify(data, reflect.Indirect(elem)) 203 | if err != nil { 204 | return err 205 | } 206 | rv.Set(elem) 207 | return nil 208 | case reflect.Struct: 209 | return md.unifyStruct(data, rv) 210 | case reflect.Map: 211 | return md.unifyMap(data, rv) 212 | case reflect.Array: 213 | return md.unifyArray(data, rv) 214 | case reflect.Slice: 215 | return md.unifySlice(data, rv) 216 | case reflect.String: 217 | return md.unifyString(data, rv) 218 | case reflect.Bool: 219 | return md.unifyBool(data, rv) 220 | case reflect.Interface: 221 | // we only support empty interfaces. 222 | if rv.NumMethod() > 0 { 223 | return e("unsupported type %s", rv.Type()) 224 | } 225 | return md.unifyAnything(data, rv) 226 | case reflect.Float32: 227 | fallthrough 228 | case reflect.Float64: 229 | return md.unifyFloat64(data, rv) 230 | } 231 | return e("unsupported type %s", rv.Kind()) 232 | } 233 | 234 | func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { 235 | tmap, ok := mapping.(map[string]interface{}) 236 | if !ok { 237 | if mapping == nil { 238 | return nil 239 | } 240 | return e("type mismatch for %s: expected table but found %T", 241 | rv.Type().String(), mapping) 242 | } 243 | 244 | for key, datum := range tmap { 245 | var f *field 246 | fields := cachedTypeFields(rv.Type()) 247 | for i := range fields { 248 | ff := &fields[i] 249 | if ff.name == key { 250 | f = ff 251 | break 252 | } 253 | if f == nil && strings.EqualFold(ff.name, key) { 254 | f = ff 255 | } 256 | } 257 | if f != nil { 258 | subv := rv 259 | for _, i := range f.index { 260 | subv = indirect(subv.Field(i)) 261 | } 262 | if isUnifiable(subv) { 263 | md.decoded[md.context.add(key).String()] = true 264 | md.context = append(md.context, key) 265 | if err := md.unify(datum, subv); err != nil { 266 | return err 267 | } 268 | md.context = md.context[0 : len(md.context)-1] 269 | } else if f.name != "" { 270 | // Bad user! No soup for you! 271 | return e("cannot write unexported field %s.%s", 272 | rv.Type().String(), f.name) 273 | } 274 | } 275 | } 276 | return nil 277 | } 278 | 279 | func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { 280 | tmap, ok := mapping.(map[string]interface{}) 281 | if !ok { 282 | if tmap == nil { 283 | return nil 284 | } 285 | return badtype("map", mapping) 286 | } 287 | if rv.IsNil() { 288 | rv.Set(reflect.MakeMap(rv.Type())) 289 | } 290 | for k, v := range tmap { 291 | md.decoded[md.context.add(k).String()] = true 292 | md.context = append(md.context, k) 293 | 294 | rvkey := indirect(reflect.New(rv.Type().Key())) 295 | rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) 296 | if err := md.unify(v, rvval); err != nil { 297 | return err 298 | } 299 | md.context = md.context[0 : len(md.context)-1] 300 | 301 | rvkey.SetString(k) 302 | rv.SetMapIndex(rvkey, rvval) 303 | } 304 | return nil 305 | } 306 | 307 | func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error { 308 | datav := reflect.ValueOf(data) 309 | if datav.Kind() != reflect.Slice { 310 | if !datav.IsValid() { 311 | return nil 312 | } 313 | return badtype("slice", data) 314 | } 315 | sliceLen := datav.Len() 316 | if sliceLen != rv.Len() { 317 | return e("expected array length %d; got TOML array of length %d", 318 | rv.Len(), sliceLen) 319 | } 320 | return md.unifySliceArray(datav, rv) 321 | } 322 | 323 | func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error { 324 | datav := reflect.ValueOf(data) 325 | if datav.Kind() != reflect.Slice { 326 | if !datav.IsValid() { 327 | return nil 328 | } 329 | return badtype("slice", data) 330 | } 331 | n := datav.Len() 332 | if rv.IsNil() || rv.Cap() < n { 333 | rv.Set(reflect.MakeSlice(rv.Type(), n, n)) 334 | } 335 | rv.SetLen(n) 336 | return md.unifySliceArray(datav, rv) 337 | } 338 | 339 | func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { 340 | sliceLen := data.Len() 341 | for i := 0; i < sliceLen; i++ { 342 | v := data.Index(i).Interface() 343 | sliceval := indirect(rv.Index(i)) 344 | if err := md.unify(v, sliceval); err != nil { 345 | return err 346 | } 347 | } 348 | return nil 349 | } 350 | 351 | func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error { 352 | if _, ok := data.(time.Time); ok { 353 | rv.Set(reflect.ValueOf(data)) 354 | return nil 355 | } 356 | return badtype("time.Time", data) 357 | } 358 | 359 | func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error { 360 | if s, ok := data.(string); ok { 361 | rv.SetString(s) 362 | return nil 363 | } 364 | return badtype("string", data) 365 | } 366 | 367 | func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error { 368 | if num, ok := data.(float64); ok { 369 | switch rv.Kind() { 370 | case reflect.Float32: 371 | fallthrough 372 | case reflect.Float64: 373 | rv.SetFloat(num) 374 | default: 375 | panic("bug") 376 | } 377 | return nil 378 | } 379 | return badtype("float", data) 380 | } 381 | 382 | func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error { 383 | if num, ok := data.(int64); ok { 384 | if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 { 385 | switch rv.Kind() { 386 | case reflect.Int, reflect.Int64: 387 | // No bounds checking necessary. 388 | case reflect.Int8: 389 | if num < math.MinInt8 || num > math.MaxInt8 { 390 | return e("value %d is out of range for int8", num) 391 | } 392 | case reflect.Int16: 393 | if num < math.MinInt16 || num > math.MaxInt16 { 394 | return e("value %d is out of range for int16", num) 395 | } 396 | case reflect.Int32: 397 | if num < math.MinInt32 || num > math.MaxInt32 { 398 | return e("value %d is out of range for int32", num) 399 | } 400 | } 401 | rv.SetInt(num) 402 | } else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 { 403 | unum := uint64(num) 404 | switch rv.Kind() { 405 | case reflect.Uint, reflect.Uint64: 406 | // No bounds checking necessary. 407 | case reflect.Uint8: 408 | if num < 0 || unum > math.MaxUint8 { 409 | return e("value %d is out of range for uint8", num) 410 | } 411 | case reflect.Uint16: 412 | if num < 0 || unum > math.MaxUint16 { 413 | return e("value %d is out of range for uint16", num) 414 | } 415 | case reflect.Uint32: 416 | if num < 0 || unum > math.MaxUint32 { 417 | return e("value %d is out of range for uint32", num) 418 | } 419 | } 420 | rv.SetUint(unum) 421 | } else { 422 | panic("unreachable") 423 | } 424 | return nil 425 | } 426 | return badtype("integer", data) 427 | } 428 | 429 | func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error { 430 | if b, ok := data.(bool); ok { 431 | rv.SetBool(b) 432 | return nil 433 | } 434 | return badtype("boolean", data) 435 | } 436 | 437 | func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error { 438 | rv.Set(reflect.ValueOf(data)) 439 | return nil 440 | } 441 | 442 | func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { 443 | var s string 444 | switch sdata := data.(type) { 445 | case TextMarshaler: 446 | text, err := sdata.MarshalText() 447 | if err != nil { 448 | return err 449 | } 450 | s = string(text) 451 | case fmt.Stringer: 452 | s = sdata.String() 453 | case string: 454 | s = sdata 455 | case bool: 456 | s = fmt.Sprintf("%v", sdata) 457 | case int64: 458 | s = fmt.Sprintf("%d", sdata) 459 | case float64: 460 | s = fmt.Sprintf("%f", sdata) 461 | default: 462 | return badtype("primitive (string-like)", data) 463 | } 464 | if err := v.UnmarshalText([]byte(s)); err != nil { 465 | return err 466 | } 467 | return nil 468 | } 469 | 470 | // rvalue returns a reflect.Value of `v`. All pointers are resolved. 471 | func rvalue(v interface{}) reflect.Value { 472 | return indirect(reflect.ValueOf(v)) 473 | } 474 | 475 | // indirect returns the value pointed to by a pointer. 476 | // Pointers are followed until the value is not a pointer. 477 | // New values are allocated for each nil pointer. 478 | // 479 | // An exception to this rule is if the value satisfies an interface of 480 | // interest to us (like encoding.TextUnmarshaler). 481 | func indirect(v reflect.Value) reflect.Value { 482 | if v.Kind() != reflect.Ptr { 483 | if v.CanSet() { 484 | pv := v.Addr() 485 | if _, ok := pv.Interface().(TextUnmarshaler); ok { 486 | return pv 487 | } 488 | } 489 | return v 490 | } 491 | if v.IsNil() { 492 | v.Set(reflect.New(v.Type().Elem())) 493 | } 494 | return indirect(reflect.Indirect(v)) 495 | } 496 | 497 | func isUnifiable(rv reflect.Value) bool { 498 | if rv.CanSet() { 499 | return true 500 | } 501 | if _, ok := rv.Interface().(TextUnmarshaler); ok { 502 | return true 503 | } 504 | return false 505 | } 506 | 507 | func badtype(expected string, data interface{}) error { 508 | return e("cannot load TOML value of type %T into a Go %s", data, expected) 509 | } 510 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/encode.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | "sort" 10 | "strconv" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | type tomlEncodeError struct{ error } 16 | 17 | var ( 18 | errArrayMixedElementTypes = errors.New( 19 | "toml: cannot encode array with mixed element types") 20 | errArrayNilElement = errors.New( 21 | "toml: cannot encode array with nil element") 22 | errNonString = errors.New( 23 | "toml: cannot encode a map with non-string key type") 24 | errAnonNonStruct = errors.New( 25 | "toml: cannot encode an anonymous field that is not a struct") 26 | errArrayNoTable = errors.New( 27 | "toml: TOML array element cannot contain a table") 28 | errNoKey = errors.New( 29 | "toml: top-level values must be Go maps or structs") 30 | errAnything = errors.New("") // used in testing 31 | ) 32 | 33 | var quotedReplacer = strings.NewReplacer( 34 | "\t", "\\t", 35 | "\n", "\\n", 36 | "\r", "\\r", 37 | "\"", "\\\"", 38 | "\\", "\\\\", 39 | ) 40 | 41 | // Encoder controls the encoding of Go values to a TOML document to some 42 | // io.Writer. 43 | // 44 | // The indentation level can be controlled with the Indent field. 45 | type Encoder struct { 46 | // A single indentation level. By default it is two spaces. 47 | Indent string 48 | 49 | // hasWritten is whether we have written any output to w yet. 50 | hasWritten bool 51 | w *bufio.Writer 52 | } 53 | 54 | // NewEncoder returns a TOML encoder that encodes Go values to the io.Writer 55 | // given. By default, a single indentation level is 2 spaces. 56 | func NewEncoder(w io.Writer) *Encoder { 57 | return &Encoder{ 58 | w: bufio.NewWriter(w), 59 | Indent: " ", 60 | } 61 | } 62 | 63 | // Encode writes a TOML representation of the Go value to the underlying 64 | // io.Writer. If the value given cannot be encoded to a valid TOML document, 65 | // then an error is returned. 66 | // 67 | // The mapping between Go values and TOML values should be precisely the same 68 | // as for the Decode* functions. Similarly, the TextMarshaler interface is 69 | // supported by encoding the resulting bytes as strings. (If you want to write 70 | // arbitrary binary data then you will need to use something like base64 since 71 | // TOML does not have any binary types.) 72 | // 73 | // When encoding TOML hashes (i.e., Go maps or structs), keys without any 74 | // sub-hashes are encoded first. 75 | // 76 | // If a Go map is encoded, then its keys are sorted alphabetically for 77 | // deterministic output. More control over this behavior may be provided if 78 | // there is demand for it. 79 | // 80 | // Encoding Go values without a corresponding TOML representation---like map 81 | // types with non-string keys---will cause an error to be returned. Similarly 82 | // for mixed arrays/slices, arrays/slices with nil elements, embedded 83 | // non-struct types and nested slices containing maps or structs. 84 | // (e.g., [][]map[string]string is not allowed but []map[string]string is OK 85 | // and so is []map[string][]string.) 86 | func (enc *Encoder) Encode(v interface{}) error { 87 | rv := eindirect(reflect.ValueOf(v)) 88 | if err := enc.safeEncode(Key([]string{}), rv); err != nil { 89 | return err 90 | } 91 | return enc.w.Flush() 92 | } 93 | 94 | func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { 95 | defer func() { 96 | if r := recover(); r != nil { 97 | if terr, ok := r.(tomlEncodeError); ok { 98 | err = terr.error 99 | return 100 | } 101 | panic(r) 102 | } 103 | }() 104 | enc.encode(key, rv) 105 | return nil 106 | } 107 | 108 | func (enc *Encoder) encode(key Key, rv reflect.Value) { 109 | // Special case. Time needs to be in ISO8601 format. 110 | // Special case. If we can marshal the type to text, then we used that. 111 | // Basically, this prevents the encoder for handling these types as 112 | // generic structs (or whatever the underlying type of a TextMarshaler is). 113 | switch rv.Interface().(type) { 114 | case time.Time, TextMarshaler: 115 | enc.keyEqElement(key, rv) 116 | return 117 | } 118 | 119 | k := rv.Kind() 120 | switch k { 121 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 122 | reflect.Int64, 123 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 124 | reflect.Uint64, 125 | reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: 126 | enc.keyEqElement(key, rv) 127 | case reflect.Array, reflect.Slice: 128 | if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { 129 | enc.eArrayOfTables(key, rv) 130 | } else { 131 | enc.keyEqElement(key, rv) 132 | } 133 | case reflect.Interface: 134 | if rv.IsNil() { 135 | return 136 | } 137 | enc.encode(key, rv.Elem()) 138 | case reflect.Map: 139 | if rv.IsNil() { 140 | return 141 | } 142 | enc.eTable(key, rv) 143 | case reflect.Ptr: 144 | if rv.IsNil() { 145 | return 146 | } 147 | enc.encode(key, rv.Elem()) 148 | case reflect.Struct: 149 | enc.eTable(key, rv) 150 | default: 151 | panic(e("unsupported type for key '%s': %s", key, k)) 152 | } 153 | } 154 | 155 | // eElement encodes any value that can be an array element (primitives and 156 | // arrays). 157 | func (enc *Encoder) eElement(rv reflect.Value) { 158 | switch v := rv.Interface().(type) { 159 | case time.Time: 160 | // Special case time.Time as a primitive. Has to come before 161 | // TextMarshaler below because time.Time implements 162 | // encoding.TextMarshaler, but we need to always use UTC. 163 | enc.wf(v.UTC().Format("2006-01-02T15:04:05Z")) 164 | return 165 | case TextMarshaler: 166 | // Special case. Use text marshaler if it's available for this value. 167 | if s, err := v.MarshalText(); err != nil { 168 | encPanic(err) 169 | } else { 170 | enc.writeQuoted(string(s)) 171 | } 172 | return 173 | } 174 | switch rv.Kind() { 175 | case reflect.Bool: 176 | enc.wf(strconv.FormatBool(rv.Bool())) 177 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 178 | reflect.Int64: 179 | enc.wf(strconv.FormatInt(rv.Int(), 10)) 180 | case reflect.Uint, reflect.Uint8, reflect.Uint16, 181 | reflect.Uint32, reflect.Uint64: 182 | enc.wf(strconv.FormatUint(rv.Uint(), 10)) 183 | case reflect.Float32: 184 | enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32))) 185 | case reflect.Float64: 186 | enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64))) 187 | case reflect.Array, reflect.Slice: 188 | enc.eArrayOrSliceElement(rv) 189 | case reflect.Interface: 190 | enc.eElement(rv.Elem()) 191 | case reflect.String: 192 | enc.writeQuoted(rv.String()) 193 | default: 194 | panic(e("unexpected primitive type: %s", rv.Kind())) 195 | } 196 | } 197 | 198 | // By the TOML spec, all floats must have a decimal with at least one 199 | // number on either side. 200 | func floatAddDecimal(fstr string) string { 201 | if !strings.Contains(fstr, ".") { 202 | return fstr + ".0" 203 | } 204 | return fstr 205 | } 206 | 207 | func (enc *Encoder) writeQuoted(s string) { 208 | enc.wf("\"%s\"", quotedReplacer.Replace(s)) 209 | } 210 | 211 | func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { 212 | length := rv.Len() 213 | enc.wf("[") 214 | for i := 0; i < length; i++ { 215 | elem := rv.Index(i) 216 | enc.eElement(elem) 217 | if i != length-1 { 218 | enc.wf(", ") 219 | } 220 | } 221 | enc.wf("]") 222 | } 223 | 224 | func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { 225 | if len(key) == 0 { 226 | encPanic(errNoKey) 227 | } 228 | for i := 0; i < rv.Len(); i++ { 229 | trv := rv.Index(i) 230 | if isNil(trv) { 231 | continue 232 | } 233 | panicIfInvalidKey(key) 234 | enc.newline() 235 | enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) 236 | enc.newline() 237 | enc.eMapOrStruct(key, trv) 238 | } 239 | } 240 | 241 | func (enc *Encoder) eTable(key Key, rv reflect.Value) { 242 | panicIfInvalidKey(key) 243 | if len(key) == 1 { 244 | // Output an extra newline between top-level tables. 245 | // (The newline isn't written if nothing else has been written though.) 246 | enc.newline() 247 | } 248 | if len(key) > 0 { 249 | enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) 250 | enc.newline() 251 | } 252 | enc.eMapOrStruct(key, rv) 253 | } 254 | 255 | func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) { 256 | switch rv := eindirect(rv); rv.Kind() { 257 | case reflect.Map: 258 | enc.eMap(key, rv) 259 | case reflect.Struct: 260 | enc.eStruct(key, rv) 261 | default: 262 | panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) 263 | } 264 | } 265 | 266 | func (enc *Encoder) eMap(key Key, rv reflect.Value) { 267 | rt := rv.Type() 268 | if rt.Key().Kind() != reflect.String { 269 | encPanic(errNonString) 270 | } 271 | 272 | // Sort keys so that we have deterministic output. And write keys directly 273 | // underneath this key first, before writing sub-structs or sub-maps. 274 | var mapKeysDirect, mapKeysSub []string 275 | for _, mapKey := range rv.MapKeys() { 276 | k := mapKey.String() 277 | if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) { 278 | mapKeysSub = append(mapKeysSub, k) 279 | } else { 280 | mapKeysDirect = append(mapKeysDirect, k) 281 | } 282 | } 283 | 284 | var writeMapKeys = func(mapKeys []string) { 285 | sort.Strings(mapKeys) 286 | for _, mapKey := range mapKeys { 287 | mrv := rv.MapIndex(reflect.ValueOf(mapKey)) 288 | if isNil(mrv) { 289 | // Don't write anything for nil fields. 290 | continue 291 | } 292 | enc.encode(key.add(mapKey), mrv) 293 | } 294 | } 295 | writeMapKeys(mapKeysDirect) 296 | writeMapKeys(mapKeysSub) 297 | } 298 | 299 | func (enc *Encoder) eStruct(key Key, rv reflect.Value) { 300 | // Write keys for fields directly under this key first, because if we write 301 | // a field that creates a new table, then all keys under it will be in that 302 | // table (not the one we're writing here). 303 | rt := rv.Type() 304 | var fieldsDirect, fieldsSub [][]int 305 | var addFields func(rt reflect.Type, rv reflect.Value, start []int) 306 | addFields = func(rt reflect.Type, rv reflect.Value, start []int) { 307 | for i := 0; i < rt.NumField(); i++ { 308 | f := rt.Field(i) 309 | // skip unexported fields 310 | if f.PkgPath != "" && !f.Anonymous { 311 | continue 312 | } 313 | frv := rv.Field(i) 314 | if f.Anonymous { 315 | t := f.Type 316 | switch t.Kind() { 317 | case reflect.Struct: 318 | // Treat anonymous struct fields with 319 | // tag names as though they are not 320 | // anonymous, like encoding/json does. 321 | if getOptions(f.Tag).name == "" { 322 | addFields(t, frv, f.Index) 323 | continue 324 | } 325 | case reflect.Ptr: 326 | if t.Elem().Kind() == reflect.Struct && 327 | getOptions(f.Tag).name == "" { 328 | if !frv.IsNil() { 329 | addFields(t.Elem(), frv.Elem(), f.Index) 330 | } 331 | continue 332 | } 333 | // Fall through to the normal field encoding logic below 334 | // for non-struct anonymous fields. 335 | } 336 | } 337 | 338 | if typeIsHash(tomlTypeOfGo(frv)) { 339 | fieldsSub = append(fieldsSub, append(start, f.Index...)) 340 | } else { 341 | fieldsDirect = append(fieldsDirect, append(start, f.Index...)) 342 | } 343 | } 344 | } 345 | addFields(rt, rv, nil) 346 | 347 | var writeFields = func(fields [][]int) { 348 | for _, fieldIndex := range fields { 349 | sft := rt.FieldByIndex(fieldIndex) 350 | sf := rv.FieldByIndex(fieldIndex) 351 | if isNil(sf) { 352 | // Don't write anything for nil fields. 353 | continue 354 | } 355 | 356 | opts := getOptions(sft.Tag) 357 | if opts.skip { 358 | continue 359 | } 360 | keyName := sft.Name 361 | if opts.name != "" { 362 | keyName = opts.name 363 | } 364 | if opts.omitempty && isEmpty(sf) { 365 | continue 366 | } 367 | if opts.omitzero && isZero(sf) { 368 | continue 369 | } 370 | 371 | enc.encode(key.add(keyName), sf) 372 | } 373 | } 374 | writeFields(fieldsDirect) 375 | writeFields(fieldsSub) 376 | } 377 | 378 | // tomlTypeName returns the TOML type name of the Go value's type. It is 379 | // used to determine whether the types of array elements are mixed (which is 380 | // forbidden). If the Go value is nil, then it is illegal for it to be an array 381 | // element, and valueIsNil is returned as true. 382 | 383 | // Returns the TOML type of a Go value. The type may be `nil`, which means 384 | // no concrete TOML type could be found. 385 | func tomlTypeOfGo(rv reflect.Value) tomlType { 386 | if isNil(rv) || !rv.IsValid() { 387 | return nil 388 | } 389 | switch rv.Kind() { 390 | case reflect.Bool: 391 | return tomlBool 392 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 393 | reflect.Int64, 394 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 395 | reflect.Uint64: 396 | return tomlInteger 397 | case reflect.Float32, reflect.Float64: 398 | return tomlFloat 399 | case reflect.Array, reflect.Slice: 400 | if typeEqual(tomlHash, tomlArrayType(rv)) { 401 | return tomlArrayHash 402 | } 403 | return tomlArray 404 | case reflect.Ptr, reflect.Interface: 405 | return tomlTypeOfGo(rv.Elem()) 406 | case reflect.String: 407 | return tomlString 408 | case reflect.Map: 409 | return tomlHash 410 | case reflect.Struct: 411 | switch rv.Interface().(type) { 412 | case time.Time: 413 | return tomlDatetime 414 | case TextMarshaler: 415 | return tomlString 416 | default: 417 | return tomlHash 418 | } 419 | default: 420 | panic("unexpected reflect.Kind: " + rv.Kind().String()) 421 | } 422 | } 423 | 424 | // tomlArrayType returns the element type of a TOML array. The type returned 425 | // may be nil if it cannot be determined (e.g., a nil slice or a zero length 426 | // slize). This function may also panic if it finds a type that cannot be 427 | // expressed in TOML (such as nil elements, heterogeneous arrays or directly 428 | // nested arrays of tables). 429 | func tomlArrayType(rv reflect.Value) tomlType { 430 | if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { 431 | return nil 432 | } 433 | firstType := tomlTypeOfGo(rv.Index(0)) 434 | if firstType == nil { 435 | encPanic(errArrayNilElement) 436 | } 437 | 438 | rvlen := rv.Len() 439 | for i := 1; i < rvlen; i++ { 440 | elem := rv.Index(i) 441 | switch elemType := tomlTypeOfGo(elem); { 442 | case elemType == nil: 443 | encPanic(errArrayNilElement) 444 | case !typeEqual(firstType, elemType): 445 | encPanic(errArrayMixedElementTypes) 446 | } 447 | } 448 | // If we have a nested array, then we must make sure that the nested 449 | // array contains ONLY primitives. 450 | // This checks arbitrarily nested arrays. 451 | if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) { 452 | nest := tomlArrayType(eindirect(rv.Index(0))) 453 | if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) { 454 | encPanic(errArrayNoTable) 455 | } 456 | } 457 | return firstType 458 | } 459 | 460 | type tagOptions struct { 461 | skip bool // "-" 462 | name string 463 | omitempty bool 464 | omitzero bool 465 | } 466 | 467 | func getOptions(tag reflect.StructTag) tagOptions { 468 | t := tag.Get("toml") 469 | if t == "-" { 470 | return tagOptions{skip: true} 471 | } 472 | var opts tagOptions 473 | parts := strings.Split(t, ",") 474 | opts.name = parts[0] 475 | for _, s := range parts[1:] { 476 | switch s { 477 | case "omitempty": 478 | opts.omitempty = true 479 | case "omitzero": 480 | opts.omitzero = true 481 | } 482 | } 483 | return opts 484 | } 485 | 486 | func isZero(rv reflect.Value) bool { 487 | switch rv.Kind() { 488 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 489 | return rv.Int() == 0 490 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 491 | return rv.Uint() == 0 492 | case reflect.Float32, reflect.Float64: 493 | return rv.Float() == 0.0 494 | } 495 | return false 496 | } 497 | 498 | func isEmpty(rv reflect.Value) bool { 499 | switch rv.Kind() { 500 | case reflect.Array, reflect.Slice, reflect.Map, reflect.String: 501 | return rv.Len() == 0 502 | case reflect.Bool: 503 | return !rv.Bool() 504 | } 505 | return false 506 | } 507 | 508 | func (enc *Encoder) newline() { 509 | if enc.hasWritten { 510 | enc.wf("\n") 511 | } 512 | } 513 | 514 | func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { 515 | if len(key) == 0 { 516 | encPanic(errNoKey) 517 | } 518 | panicIfInvalidKey(key) 519 | enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) 520 | enc.eElement(val) 521 | enc.newline() 522 | } 523 | 524 | func (enc *Encoder) wf(format string, v ...interface{}) { 525 | if _, err := fmt.Fprintf(enc.w, format, v...); err != nil { 526 | encPanic(err) 527 | } 528 | enc.hasWritten = true 529 | } 530 | 531 | func (enc *Encoder) indentStr(key Key) string { 532 | return strings.Repeat(enc.Indent, len(key)-1) 533 | } 534 | 535 | func encPanic(err error) { 536 | panic(tomlEncodeError{err}) 537 | } 538 | 539 | func eindirect(v reflect.Value) reflect.Value { 540 | switch v.Kind() { 541 | case reflect.Ptr, reflect.Interface: 542 | return eindirect(v.Elem()) 543 | default: 544 | return v 545 | } 546 | } 547 | 548 | func isNil(rv reflect.Value) bool { 549 | switch rv.Kind() { 550 | case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 551 | return rv.IsNil() 552 | default: 553 | return false 554 | } 555 | } 556 | 557 | func panicIfInvalidKey(key Key) { 558 | for _, k := range key { 559 | if len(k) == 0 { 560 | encPanic(e("Key '%s' is not a valid table name. Key names "+ 561 | "cannot be empty.", key.maybeQuotedAll())) 562 | } 563 | } 564 | } 565 | 566 | func isValidKeyName(s string) bool { 567 | return len(s) != 0 568 | } 569 | -------------------------------------------------------------------------------- /vendor/github.com/BurntSushi/toml/parse.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "time" 8 | "unicode" 9 | "unicode/utf8" 10 | ) 11 | 12 | type parser struct { 13 | mapping map[string]interface{} 14 | types map[string]tomlType 15 | lx *lexer 16 | 17 | // A list of keys in the order that they appear in the TOML data. 18 | ordered []Key 19 | 20 | // the full key for the current hash in scope 21 | context Key 22 | 23 | // the base key name for everything except hashes 24 | currentKey string 25 | 26 | // rough approximation of line number 27 | approxLine int 28 | 29 | // A map of 'key.group.names' to whether they were created implicitly. 30 | implicits map[string]bool 31 | } 32 | 33 | type parseError string 34 | 35 | func (pe parseError) Error() string { 36 | return string(pe) 37 | } 38 | 39 | func parse(data string) (p *parser, err error) { 40 | defer func() { 41 | if r := recover(); r != nil { 42 | var ok bool 43 | if err, ok = r.(parseError); ok { 44 | return 45 | } 46 | panic(r) 47 | } 48 | }() 49 | 50 | p = &parser{ 51 | mapping: make(map[string]interface{}), 52 | types: make(map[string]tomlType), 53 | lx: lex(data), 54 | ordered: make([]Key, 0), 55 | implicits: make(map[string]bool), 56 | } 57 | for { 58 | item := p.next() 59 | if item.typ == itemEOF { 60 | break 61 | } 62 | p.topLevel(item) 63 | } 64 | 65 | return p, nil 66 | } 67 | 68 | func (p *parser) panicf(format string, v ...interface{}) { 69 | msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s", 70 | p.approxLine, p.current(), fmt.Sprintf(format, v...)) 71 | panic(parseError(msg)) 72 | } 73 | 74 | func (p *parser) next() item { 75 | it := p.lx.nextItem() 76 | if it.typ == itemError { 77 | p.panicf("%s", it.val) 78 | } 79 | return it 80 | } 81 | 82 | func (p *parser) bug(format string, v ...interface{}) { 83 | panic(fmt.Sprintf("BUG: "+format+"\n\n", v...)) 84 | } 85 | 86 | func (p *parser) expect(typ itemType) item { 87 | it := p.next() 88 | p.assertEqual(typ, it.typ) 89 | return it 90 | } 91 | 92 | func (p *parser) assertEqual(expected, got itemType) { 93 | if expected != got { 94 | p.bug("Expected '%s' but got '%s'.", expected, got) 95 | } 96 | } 97 | 98 | func (p *parser) topLevel(item item) { 99 | switch item.typ { 100 | case itemCommentStart: 101 | p.approxLine = item.line 102 | p.expect(itemText) 103 | case itemTableStart: 104 | kg := p.next() 105 | p.approxLine = kg.line 106 | 107 | var key Key 108 | for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() { 109 | key = append(key, p.keyString(kg)) 110 | } 111 | p.assertEqual(itemTableEnd, kg.typ) 112 | 113 | p.establishContext(key, false) 114 | p.setType("", tomlHash) 115 | p.ordered = append(p.ordered, key) 116 | case itemArrayTableStart: 117 | kg := p.next() 118 | p.approxLine = kg.line 119 | 120 | var key Key 121 | for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() { 122 | key = append(key, p.keyString(kg)) 123 | } 124 | p.assertEqual(itemArrayTableEnd, kg.typ) 125 | 126 | p.establishContext(key, true) 127 | p.setType("", tomlArrayHash) 128 | p.ordered = append(p.ordered, key) 129 | case itemKeyStart: 130 | kname := p.next() 131 | p.approxLine = kname.line 132 | p.currentKey = p.keyString(kname) 133 | 134 | val, typ := p.value(p.next()) 135 | p.setValue(p.currentKey, val) 136 | p.setType(p.currentKey, typ) 137 | p.ordered = append(p.ordered, p.context.add(p.currentKey)) 138 | p.currentKey = "" 139 | default: 140 | p.bug("Unexpected type at top level: %s", item.typ) 141 | } 142 | } 143 | 144 | // Gets a string for a key (or part of a key in a table name). 145 | func (p *parser) keyString(it item) string { 146 | switch it.typ { 147 | case itemText: 148 | return it.val 149 | case itemString, itemMultilineString, 150 | itemRawString, itemRawMultilineString: 151 | s, _ := p.value(it) 152 | return s.(string) 153 | default: 154 | p.bug("Unexpected key type: %s", it.typ) 155 | panic("unreachable") 156 | } 157 | } 158 | 159 | // value translates an expected value from the lexer into a Go value wrapped 160 | // as an empty interface. 161 | func (p *parser) value(it item) (interface{}, tomlType) { 162 | switch it.typ { 163 | case itemString: 164 | return p.replaceEscapes(it.val), p.typeOfPrimitive(it) 165 | case itemMultilineString: 166 | trimmed := stripFirstNewline(stripEscapedWhitespace(it.val)) 167 | return p.replaceEscapes(trimmed), p.typeOfPrimitive(it) 168 | case itemRawString: 169 | return it.val, p.typeOfPrimitive(it) 170 | case itemRawMultilineString: 171 | return stripFirstNewline(it.val), p.typeOfPrimitive(it) 172 | case itemBool: 173 | switch it.val { 174 | case "true": 175 | return true, p.typeOfPrimitive(it) 176 | case "false": 177 | return false, p.typeOfPrimitive(it) 178 | } 179 | p.bug("Expected boolean value, but got '%s'.", it.val) 180 | case itemInteger: 181 | if !numUnderscoresOK(it.val) { 182 | p.panicf("Invalid integer %q: underscores must be surrounded by digits", 183 | it.val) 184 | } 185 | val := strings.Replace(it.val, "_", "", -1) 186 | num, err := strconv.ParseInt(val, 10, 64) 187 | if err != nil { 188 | // Distinguish integer values. Normally, it'd be a bug if the lexer 189 | // provides an invalid integer, but it's possible that the number is 190 | // out of range of valid values (which the lexer cannot determine). 191 | // So mark the former as a bug but the latter as a legitimate user 192 | // error. 193 | if e, ok := err.(*strconv.NumError); ok && 194 | e.Err == strconv.ErrRange { 195 | 196 | p.panicf("Integer '%s' is out of the range of 64-bit "+ 197 | "signed integers.", it.val) 198 | } else { 199 | p.bug("Expected integer value, but got '%s'.", it.val) 200 | } 201 | } 202 | return num, p.typeOfPrimitive(it) 203 | case itemFloat: 204 | parts := strings.FieldsFunc(it.val, func(r rune) bool { 205 | switch r { 206 | case '.', 'e', 'E': 207 | return true 208 | } 209 | return false 210 | }) 211 | for _, part := range parts { 212 | if !numUnderscoresOK(part) { 213 | p.panicf("Invalid float %q: underscores must be "+ 214 | "surrounded by digits", it.val) 215 | } 216 | } 217 | if !numPeriodsOK(it.val) { 218 | // As a special case, numbers like '123.' or '1.e2', 219 | // which are valid as far as Go/strconv are concerned, 220 | // must be rejected because TOML says that a fractional 221 | // part consists of '.' followed by 1+ digits. 222 | p.panicf("Invalid float %q: '.' must be followed "+ 223 | "by one or more digits", it.val) 224 | } 225 | val := strings.Replace(it.val, "_", "", -1) 226 | num, err := strconv.ParseFloat(val, 64) 227 | if err != nil { 228 | if e, ok := err.(*strconv.NumError); ok && 229 | e.Err == strconv.ErrRange { 230 | 231 | p.panicf("Float '%s' is out of the range of 64-bit "+ 232 | "IEEE-754 floating-point numbers.", it.val) 233 | } else { 234 | p.panicf("Invalid float value: %q", it.val) 235 | } 236 | } 237 | return num, p.typeOfPrimitive(it) 238 | case itemDatetime: 239 | var t time.Time 240 | var ok bool 241 | var err error 242 | for _, format := range []string{ 243 | "2006-01-02T15:04:05Z07:00", 244 | "2006-01-02T15:04:05", 245 | "2006-01-02", 246 | } { 247 | t, err = time.ParseInLocation(format, it.val, time.Local) 248 | if err == nil { 249 | ok = true 250 | break 251 | } 252 | } 253 | if !ok { 254 | p.panicf("Invalid TOML Datetime: %q.", it.val) 255 | } 256 | return t, p.typeOfPrimitive(it) 257 | case itemArray: 258 | array := make([]interface{}, 0) 259 | types := make([]tomlType, 0) 260 | 261 | for it = p.next(); it.typ != itemArrayEnd; it = p.next() { 262 | if it.typ == itemCommentStart { 263 | p.expect(itemText) 264 | continue 265 | } 266 | 267 | val, typ := p.value(it) 268 | array = append(array, val) 269 | types = append(types, typ) 270 | } 271 | return array, p.typeOfArray(types) 272 | case itemInlineTableStart: 273 | var ( 274 | hash = make(map[string]interface{}) 275 | outerContext = p.context 276 | outerKey = p.currentKey 277 | ) 278 | 279 | p.context = append(p.context, p.currentKey) 280 | p.currentKey = "" 281 | for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { 282 | if it.typ != itemKeyStart { 283 | p.bug("Expected key start but instead found %q, around line %d", 284 | it.val, p.approxLine) 285 | } 286 | if it.typ == itemCommentStart { 287 | p.expect(itemText) 288 | continue 289 | } 290 | 291 | // retrieve key 292 | k := p.next() 293 | p.approxLine = k.line 294 | kname := p.keyString(k) 295 | 296 | // retrieve value 297 | p.currentKey = kname 298 | val, typ := p.value(p.next()) 299 | // make sure we keep metadata up to date 300 | p.setType(kname, typ) 301 | p.ordered = append(p.ordered, p.context.add(p.currentKey)) 302 | hash[kname] = val 303 | } 304 | p.context = outerContext 305 | p.currentKey = outerKey 306 | return hash, tomlHash 307 | } 308 | p.bug("Unexpected value type: %s", it.typ) 309 | panic("unreachable") 310 | } 311 | 312 | // numUnderscoresOK checks whether each underscore in s is surrounded by 313 | // characters that are not underscores. 314 | func numUnderscoresOK(s string) bool { 315 | accept := false 316 | for _, r := range s { 317 | if r == '_' { 318 | if !accept { 319 | return false 320 | } 321 | accept = false 322 | continue 323 | } 324 | accept = true 325 | } 326 | return accept 327 | } 328 | 329 | // numPeriodsOK checks whether every period in s is followed by a digit. 330 | func numPeriodsOK(s string) bool { 331 | period := false 332 | for _, r := range s { 333 | if period && !isDigit(r) { 334 | return false 335 | } 336 | period = r == '.' 337 | } 338 | return !period 339 | } 340 | 341 | // establishContext sets the current context of the parser, 342 | // where the context is either a hash or an array of hashes. Which one is 343 | // set depends on the value of the `array` parameter. 344 | // 345 | // Establishing the context also makes sure that the key isn't a duplicate, and 346 | // will create implicit hashes automatically. 347 | func (p *parser) establishContext(key Key, array bool) { 348 | var ok bool 349 | 350 | // Always start at the top level and drill down for our context. 351 | hashContext := p.mapping 352 | keyContext := make(Key, 0) 353 | 354 | // We only need implicit hashes for key[0:-1] 355 | for _, k := range key[0 : len(key)-1] { 356 | _, ok = hashContext[k] 357 | keyContext = append(keyContext, k) 358 | 359 | // No key? Make an implicit hash and move on. 360 | if !ok { 361 | p.addImplicit(keyContext) 362 | hashContext[k] = make(map[string]interface{}) 363 | } 364 | 365 | // If the hash context is actually an array of tables, then set 366 | // the hash context to the last element in that array. 367 | // 368 | // Otherwise, it better be a table, since this MUST be a key group (by 369 | // virtue of it not being the last element in a key). 370 | switch t := hashContext[k].(type) { 371 | case []map[string]interface{}: 372 | hashContext = t[len(t)-1] 373 | case map[string]interface{}: 374 | hashContext = t 375 | default: 376 | p.panicf("Key '%s' was already created as a hash.", keyContext) 377 | } 378 | } 379 | 380 | p.context = keyContext 381 | if array { 382 | // If this is the first element for this array, then allocate a new 383 | // list of tables for it. 384 | k := key[len(key)-1] 385 | if _, ok := hashContext[k]; !ok { 386 | hashContext[k] = make([]map[string]interface{}, 0, 5) 387 | } 388 | 389 | // Add a new table. But make sure the key hasn't already been used 390 | // for something else. 391 | if hash, ok := hashContext[k].([]map[string]interface{}); ok { 392 | hashContext[k] = append(hash, make(map[string]interface{})) 393 | } else { 394 | p.panicf("Key '%s' was already created and cannot be used as "+ 395 | "an array.", keyContext) 396 | } 397 | } else { 398 | p.setValue(key[len(key)-1], make(map[string]interface{})) 399 | } 400 | p.context = append(p.context, key[len(key)-1]) 401 | } 402 | 403 | // setValue sets the given key to the given value in the current context. 404 | // It will make sure that the key hasn't already been defined, account for 405 | // implicit key groups. 406 | func (p *parser) setValue(key string, value interface{}) { 407 | var tmpHash interface{} 408 | var ok bool 409 | 410 | hash := p.mapping 411 | keyContext := make(Key, 0) 412 | for _, k := range p.context { 413 | keyContext = append(keyContext, k) 414 | if tmpHash, ok = hash[k]; !ok { 415 | p.bug("Context for key '%s' has not been established.", keyContext) 416 | } 417 | switch t := tmpHash.(type) { 418 | case []map[string]interface{}: 419 | // The context is a table of hashes. Pick the most recent table 420 | // defined as the current hash. 421 | hash = t[len(t)-1] 422 | case map[string]interface{}: 423 | hash = t 424 | default: 425 | p.bug("Expected hash to have type 'map[string]interface{}', but "+ 426 | "it has '%T' instead.", tmpHash) 427 | } 428 | } 429 | keyContext = append(keyContext, key) 430 | 431 | if _, ok := hash[key]; ok { 432 | // Typically, if the given key has already been set, then we have 433 | // to raise an error since duplicate keys are disallowed. However, 434 | // it's possible that a key was previously defined implicitly. In this 435 | // case, it is allowed to be redefined concretely. (See the 436 | // `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.) 437 | // 438 | // But we have to make sure to stop marking it as an implicit. (So that 439 | // another redefinition provokes an error.) 440 | // 441 | // Note that since it has already been defined (as a hash), we don't 442 | // want to overwrite it. So our business is done. 443 | if p.isImplicit(keyContext) { 444 | p.removeImplicit(keyContext) 445 | return 446 | } 447 | 448 | // Otherwise, we have a concrete key trying to override a previous 449 | // key, which is *always* wrong. 450 | p.panicf("Key '%s' has already been defined.", keyContext) 451 | } 452 | hash[key] = value 453 | } 454 | 455 | // setType sets the type of a particular value at a given key. 456 | // It should be called immediately AFTER setValue. 457 | // 458 | // Note that if `key` is empty, then the type given will be applied to the 459 | // current context (which is either a table or an array of tables). 460 | func (p *parser) setType(key string, typ tomlType) { 461 | keyContext := make(Key, 0, len(p.context)+1) 462 | for _, k := range p.context { 463 | keyContext = append(keyContext, k) 464 | } 465 | if len(key) > 0 { // allow type setting for hashes 466 | keyContext = append(keyContext, key) 467 | } 468 | p.types[keyContext.String()] = typ 469 | } 470 | 471 | // addImplicit sets the given Key as having been created implicitly. 472 | func (p *parser) addImplicit(key Key) { 473 | p.implicits[key.String()] = true 474 | } 475 | 476 | // removeImplicit stops tagging the given key as having been implicitly 477 | // created. 478 | func (p *parser) removeImplicit(key Key) { 479 | p.implicits[key.String()] = false 480 | } 481 | 482 | // isImplicit returns true if the key group pointed to by the key was created 483 | // implicitly. 484 | func (p *parser) isImplicit(key Key) bool { 485 | return p.implicits[key.String()] 486 | } 487 | 488 | // current returns the full key name of the current context. 489 | func (p *parser) current() string { 490 | if len(p.currentKey) == 0 { 491 | return p.context.String() 492 | } 493 | if len(p.context) == 0 { 494 | return p.currentKey 495 | } 496 | return fmt.Sprintf("%s.%s", p.context, p.currentKey) 497 | } 498 | 499 | func stripFirstNewline(s string) string { 500 | if len(s) == 0 || s[0] != '\n' { 501 | return s 502 | } 503 | return s[1:] 504 | } 505 | 506 | func stripEscapedWhitespace(s string) string { 507 | esc := strings.Split(s, "\\\n") 508 | if len(esc) > 1 { 509 | for i := 1; i < len(esc); i++ { 510 | esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace) 511 | } 512 | } 513 | return strings.Join(esc, "") 514 | } 515 | 516 | func (p *parser) replaceEscapes(str string) string { 517 | var replaced []rune 518 | s := []byte(str) 519 | r := 0 520 | for r < len(s) { 521 | if s[r] != '\\' { 522 | c, size := utf8.DecodeRune(s[r:]) 523 | r += size 524 | replaced = append(replaced, c) 525 | continue 526 | } 527 | r += 1 528 | if r >= len(s) { 529 | p.bug("Escape sequence at end of string.") 530 | return "" 531 | } 532 | switch s[r] { 533 | default: 534 | p.bug("Expected valid escape code after \\, but got %q.", s[r]) 535 | return "" 536 | case 'b': 537 | replaced = append(replaced, rune(0x0008)) 538 | r += 1 539 | case 't': 540 | replaced = append(replaced, rune(0x0009)) 541 | r += 1 542 | case 'n': 543 | replaced = append(replaced, rune(0x000A)) 544 | r += 1 545 | case 'f': 546 | replaced = append(replaced, rune(0x000C)) 547 | r += 1 548 | case 'r': 549 | replaced = append(replaced, rune(0x000D)) 550 | r += 1 551 | case '"': 552 | replaced = append(replaced, rune(0x0022)) 553 | r += 1 554 | case '\\': 555 | replaced = append(replaced, rune(0x005C)) 556 | r += 1 557 | case 'u': 558 | // At this point, we know we have a Unicode escape of the form 559 | // `uXXXX` at [r, r+5). (Because the lexer guarantees this 560 | // for us.) 561 | escaped := p.asciiEscapeToUnicode(s[r+1 : r+5]) 562 | replaced = append(replaced, escaped) 563 | r += 5 564 | case 'U': 565 | // At this point, we know we have a Unicode escape of the form 566 | // `uXXXX` at [r, r+9). (Because the lexer guarantees this 567 | // for us.) 568 | escaped := p.asciiEscapeToUnicode(s[r+1 : r+9]) 569 | replaced = append(replaced, escaped) 570 | r += 9 571 | } 572 | } 573 | return string(replaced) 574 | } 575 | 576 | func (p *parser) asciiEscapeToUnicode(bs []byte) rune { 577 | s := string(bs) 578 | hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) 579 | if err != nil { 580 | p.bug("Could not parse '%s' as a hexadecimal number, but the "+ 581 | "lexer claims it's OK: %s", s, err) 582 | } 583 | if !utf8.ValidRune(rune(hex)) { 584 | p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s) 585 | } 586 | return rune(hex) 587 | } 588 | 589 | func isStringType(ty itemType) bool { 590 | return ty == itemString || ty == itemMultilineString || 591 | ty == itemRawString || ty == itemRawMultilineString 592 | } 593 | --------------------------------------------------------------------------------