<commit_before>// GameServer: main gameserver struct and functions
// does not know or care about where Packets come from,
// they just arrive on our In port.
package main

import (
	"fmt"
	"github.com/mischief/goland/game"
	"github.com/mischief/goland/game/gnet"
	"github.com/mischief/goland/game/gutil"
	"github.com/trustmaster/goflow"
	"image"
	"log"
	"net"
)

var (
	Actions = map[game.Action]func(*GameServer, *ClientPacket){
	        game.ACTION_ITEM_PICKUP: Action_ItemPickup,
	        game.ACTION_ITEM_DROP: Action_ItemDrop,
		game.ACTION_ITEM_LIST_INVENTORY: Action_Inventory,
	}
)

type GameServer struct {
	flow.Graph // graph for our procs; see goflow

	Listener   net.Listener       // acceptor of client connections
	PacketChan chan *ClientPacket // channel where clients packets arrive

	*game.DefaultSubject //

	Sessions map[int64]*WorldSession //client list

	Objects game.GameObjectMap
	//Objects []*game.GameObject
	Map *game.MapChunk

	Parameters *gutil.LuaParMap
}

func NewGameServer(params *gutil.LuaParMap) *GameServer {

	// flow network setup
	gs := new(GameServer)
	gs.Parameters = params
	gs.InitGraphState()

	// add nodes
	gs.Add(NewPacketRouter(gs), "router")
	gs.Add(new(PacketLogger), "logger")

	// connect processes
	gs.Connect("router", "Log", "logger", "In", make(chan *ClientPacket))

	// map external ports
	gs.MapInPort("In", "router", "In")

	gs.PacketChan = make(chan *ClientPacket, 5)
	gs.SetInPort("In", gs.PacketChan)

	// observers setup
	gs.DefaultSubject = game.NewDefaultSubject()

	// objects setup
	gs.Objects = game.NewGameObjectMap()

	return gs
}

func (gs *GameServer) Run() {
	gs.Start()

	for {
		conn, err := gs.Listener.Accept()
		if err != nil {
			log.Println("GameServer: acceptor: ", err)
			continue
		}

		ws := NewWorldSession(gs, conn)
		gs.Attach(ws)

		log.Printf("New World Session: %s", ws)

		go ws.ReceiveProc()
	}

	gs.End()
}

func (gs *GameServer) Start() {
	var err error

	// load assets
	log.Print("GameServer: Loading assets")
	gs.LoadAssets()

	// setup tcp listener
	log.Printf("GameServer: Starting listener")

	dialstr := ":61507"
	if dialstr, ok := gs.Parameters.Get("listener"); !ok {
		log.Println("GameServer: 'listen' not found in config. defaulting to ", dialstr)
	}

	if gs.Listener, err = net.Listen("tcp", dialstr); err != nil {
		log.Fatalf("GameServer: %s", err)
	}

	// setup goflow network
	log.Print("GameServer: Starting flow")

	flow.RunNet(gs)

}

func (gs *GameServer) End() {
}

func (gs *GameServer) LoadAssets() {
	mapfile, ok := gs.Parameters.Get("map")
	if !ok {
		log.Fatal("GameServer: LoadAssets: No map file specified")
	}

	log.Printf("GameServer: LoadAssets: Loading map chunk file: %s", mapfile)
	if gs.Map = game.MapChunkFromFile(mapfile); gs.Map == nil {
		log.Fatal("GameServer: LoadAssets: Can't open map chunk file")
	}

	newi := game.NewItem("flag")

	// set the position of the flag
	newi.SetPos(gs.Map.RandCell())
	gs.Objects.Add(newi.GameObject)
}

func (gs *GameServer) SendPacketAll(pk *gnet.Packet) {
	for s := gs.DefaultSubject.Observers.Front(); s != nil; s = s.Next() {
		s.Value.(*WorldSession).SendPacket(pk)
	}
}

func (gs *GameServer) HandlePacket(cp *ClientPacket) {

	switch cp.Tag {

	// Tchat: chat message from a client
	case "Tchat":
		// broadcast chat
		chatline := cp.Data.(string)
		gs.SendPacketAll(gnet.NewPacket("Rchat", fmt.Sprintf("[chat] %s: %s", cp.Client.Username, chatline)))

		// Taction: movement request
	case "Taction":
		gs.HandleActionPacket(cp)

		//
	case "Tconnect":
		username, ok := cp.Data.(string)

		if !ok {
			cp.Reply(gnet.NewPacket("Rerror", "invalid username or conversion failed"))
			break
		} else {
			cp.Client.Username = username
		}

		// make new player for client
		newpl := game.NewPlayer(username)
		newpl.SetPos(image.Pt(256/2, 256/2))

		cp.Client.Player = newpl

		gs.Objects.Add(newpl.GameObject)

		gs.SendPacketAll(gnet.NewPacket("Rnewobject", newpl.GameObject))

		// tell client about all other objects
		for _, o := range gs.Objects {
			cp.Reply(gnet.NewPacket("Rnewobject", o))
		}

		cp.Reply(gnet.NewPacket("Rchat", "Welcome to Goland!"))

	case "Tdisconnect":
		// notify clients this player went away
		gs.Objects.RemoveObject(cp.Client.Player.GameObject)
		gs.SendPacketAll(gnet.NewPacket("Rdelobject", cp.Client.Player.GameObject))

	case "Tgetplayer":
		if cp.Client.Player != nil {
			cp.Reply(gnet.NewPacket("Rgetplayer", cp.Client.Player.ID))
		} else {
			cp.Reply(gnet.NewPacket("error", "nil Player in WorldSession"))
		}

	case "Tloadmap":
		cp.Reply(gnet.NewPacket("Rloadmap", gs.Map))

	default:
		log.Printf("GameServer: HandlePacket: unknown packet type %s", cp.Tag)
	}
}

func Action_ItemPickup(gs *GameServer, cp *ClientPacket) {
	cp.Reply(gnet.NewPacket("Rchat", "Picking up Item"))
}

func Action_ItemDrop(gs *GameServer, cp *ClientPacket) {
	cp.Reply(gnet.NewPacket("Rchat", "Dropping Item"))
}

func Action_Inventory(gs *GameServer, cp *ClientPacket) {
	cp.Reply(gnet.NewPacket("Rchat", cp.Client.Player.Inventory.String()))
}

func (gs *GameServer) HandleActionPacket(cp *ClientPacket) {
	action := cp.Data.(game.Action)

	_, isdir := game.DirTable[action]
	if isdir {
		gs.HandleMovementPacket(cp)
	}

	Actions[action](gs, cp)

	gs.SendPacketAll(gnet.NewPacket("Raction", cp.Client.Player))
}

// Handle Directionals
func (gs *GameServer) HandleMovementPacket(cp *ClientPacket) {
	action := cp.Data.(game.Action)
	newpos := cp.Client.Player.GetPos().Add(game.DirTable[action])
	valid := true

	// check terrain collision
	if !gs.Map.CheckCollision(nil, newpos) {
		valid = false
		cp.Reply(gnet.NewPacket("Rchat", "Ouch! You bump into a wall."))
	}

	// check gameobject collision
	for _, o := range gs.Objects {

		// check if collision with Item and item name is flag
		if o.GetPos() == newpos {
			if o.Tags["player"] {
				cp.Reply(gnet.NewPacket("Rchat", fmt.Sprintf("Ouch! You bump into %s.", o.Name)))
				valid = false
				break
			}

			if o.Tags["item"] && valid {
				cp.Reply(gnet.NewPacket("Rchat", fmt.Sprintf("You see a %s here.", o.Name)))
			}
		}
	}

	if valid {
		cp.Client.Player.SetPos(newpos)
	}
}<commit_msg>Fixed item collision + inventory adding<commit_after>// GameServer: main gameserver struct and functions
// does not know or care about where Packets come from,
// they just arrive on our In port.
package main

import (
	"fmt"
	"github.com/mischief/goland/game"
	"github.com/mischief/goland/game/gnet"
	"github.com/mischief/goland/game/gutil"
	"github.com/trustmaster/goflow"
	"image"
	"log"
	"net"
)

var (
	Actions = map[game.Action]func(*GameServer, *ClientPacket){
	        game.ACTION_ITEM_PICKUP: Action_ItemPickup,
	        game.ACTION_ITEM_DROP: Action_ItemDrop,
		game.ACTION_ITEM_LIST_INVENTORY: Action_Inventory,
	}
)

type GameServer struct {
	flow.Graph // graph for our procs; see goflow

	Listener   net.Listener       // acceptor of client connections
	PacketChan chan *ClientPacket // channel where clients packets arrive

	*game.DefaultSubject //

	Sessions map[int64]*WorldSession //client list

	Objects game.GameObjectMap
	//Objects []*game.GameObject
	Map *game.MapChunk

	Parameters *gutil.LuaParMap
}

func NewGameServer(params *gutil.LuaParMap) *GameServer {

	// flow network setup
	gs := new(GameServer)
	gs.Parameters = params
	gs.InitGraphState()

	// add nodes
	gs.Add(NewPacketRouter(gs), "router")
	gs.Add(new(PacketLogger), "logger")

	// connect processes
	gs.Connect("router", "Log", "logger", "In", make(chan *ClientPacket))

	// map external ports
	gs.MapInPort("In", "router", "In")

	gs.PacketChan = make(chan *ClientPacket, 5)
	gs.SetInPort("In", gs.PacketChan)

	// observers setup
	gs.DefaultSubject = game.NewDefaultSubject()

	// objects setup
	gs.Objects = game.NewGameObjectMap()

	return gs
}

func (gs *GameServer) Run() {
	gs.Start()

	for {
		conn, err := gs.Listener.Accept()
		if err != nil {
			log.Println("GameServer: acceptor: ", err)
			continue
		}

		ws := NewWorldSession(gs, conn)
		gs.Attach(ws)

		log.Printf("New World Session: %s", ws)

		go ws.ReceiveProc()
	}

	gs.End()
}

func (gs *GameServer) Start() {
	var err error

	// load assets
	log.Print("GameServer: Loading assets")
	gs.LoadAssets()

	// setup tcp listener
	log.Printf("GameServer: Starting listener")

	dialstr := ":61507"
	if dialstr, ok := gs.Parameters.Get("listener"); !ok {
		log.Println("GameServer: 'listen' not found in config. defaulting to ", dialstr)
	}

	if gs.Listener, err = net.Listen("tcp", dialstr); err != nil {
		log.Fatalf("GameServer: %s", err)
	}

	// setup goflow network
	log.Print("GameServer: Starting flow")

	flow.RunNet(gs)

}

func (gs *GameServer) End() {
}

func (gs *GameServer) LoadAssets() {
	mapfile, ok := gs.Parameters.Get("map")
	if !ok {
		log.Fatal("GameServer: LoadAssets: No map file specified")
	}

	log.Printf("GameServer: LoadAssets: Loading map chunk file: %s", mapfile)
	if gs.Map = game.MapChunkFromFile(mapfile); gs.Map == nil {
		log.Fatal("GameServer: LoadAssets: Can't open map chunk file")
	}

	newi := game.NewItem("flag")

	// set the position of the flag
	newi.SetPos(gs.Map.RandCell())
	gs.Objects.Add(newi.GameObject)
}

func (gs *GameServer) SendPacketAll(pk *gnet.Packet) {
	for s := gs.DefaultSubject.Observers.Front(); s != nil; s = s.Next() {
		s.Value.(*WorldSession).SendPacket(pk)
	}
}

func (gs *GameServer) HandlePacket(cp *ClientPacket) {

	switch cp.Tag {

	// Tchat: chat message from a client
	case "Tchat":
		// broadcast chat
		chatline := cp.Data.(string)
		gs.SendPacketAll(gnet.NewPacket("Rchat", fmt.Sprintf("[chat] %s: %s", cp.Client.Username, chatline)))

		// Taction: movement request
	case "Taction":
		gs.HandleActionPacket(cp)

		//
	case "Tconnect":
		username, ok := cp.Data.(string)

		if !ok {
			cp.Reply(gnet.NewPacket("Rerror", "invalid username or conversion failed"))
			break
		} else {
			cp.Client.Username = username
		}

		// make new player for client
		newpl := game.NewPlayer(username)
		newpl.SetPos(image.Pt(256/2, 256/2))

		cp.Client.Player = newpl

		gs.Objects.Add(newpl.GameObject)

		gs.SendPacketAll(gnet.NewPacket("Rnewobject", newpl.GameObject))

		// tell client about all other objects
		for _, o := range gs.Objects {
			cp.Reply(gnet.NewPacket("Rnewobject", o))
		}

		cp.Reply(gnet.NewPacket("Rchat", "Welcome to Goland!"))

	case "Tdisconnect":
		// notify clients this player went away
		gs.Objects.RemoveObject(cp.Client.Player.GameObject)
		gs.SendPacketAll(gnet.NewPacket("Rdelobject", cp.Client.Player.GameObject))

	case "Tgetplayer":
		if cp.Client.Player != nil {
			cp.Reply(gnet.NewPacket("Rgetplayer", cp.Client.Player.ID))
		} else {
			cp.Reply(gnet.NewPacket("error", "nil Player in WorldSession"))
		}

	case "Tloadmap":
		cp.Reply(gnet.NewPacket("Rloadmap", gs.Map))

	default:
		log.Printf("GameServer: HandlePacket: unknown packet type %s", cp.Tag)
	}
}

// Prevent User from re-adding / picking up item
// Disassociate item with map after action successful
func Action_ItemPickup(gs *GameServer, cp *ClientPacket) {
	p := cp.Client.Player
	action := cp.Data.(game.Action)
	pos := p.GetPos().Add(game.DirTable[action])
	for _, o := range gs.Objects {
		if o.GetPos() == pos && o.Tags["item"]{
			if o.Tags["gettable"] == true {
				cp.Reply(gnet.NewPacket("Rchat", "Picking up Item"))
				o.Tags["gettable"] = false
				item := game.NewItem(o.Name)
				item.GameObject = o
				p.Inventory.AddItem(item)
			} else {
				cp.Reply(gnet.NewPacket("Rchat", "No item(s) to get."))
			}
		}
	}
}

func Action_ItemDrop(gs *GameServer, cp *ClientPacket) {
	cp.Reply(gnet.NewPacket("Rchat", "Dropping Item"))
}

func Action_Inventory(gs *GameServer, cp *ClientPacket) {
	cp.Reply(gnet.NewPacket("Rchat", cp.Client.Player.Inventory.String()))
}

func (gs *GameServer) HandleActionPacket(cp *ClientPacket) {
	action := cp.Data.(game.Action)

	_, isdir := game.DirTable[action]
	if isdir {
		gs.HandleMovementPacket(cp)
	}

	Actions[action](gs, cp)

	gs.SendPacketAll(gnet.NewPacket("Raction", cp.Client.Player))
}

// Handle Directionals
func (gs *GameServer) HandleMovementPacket(cp *ClientPacket) {
	action := cp.Data.(game.Action)
	p := cp.Client.Player
	newpos := p.GetPos().Add(game.DirTable[action])
	valid := true

	// check terrain collision
	if !gs.Map.CheckCollision(nil, newpos) {
		valid = false
		cp.Reply(gnet.NewPacket("Rchat", "Ouch! You bump into a wall."))
	}

	// check gameobject collision
	for _, o := range gs.Objects {

		// check if collision with Item and item name is flag
		if o.GetPos() == newpos {
			if o.Tags["player"] {
				cp.Reply(gnet.NewPacket("Rchat", fmt.Sprintf("Ouch! You bump into %s.", o.Name)))
				// XXX DROP FLAG HERE
				hasflag := p.Inventory.ContainsItemNamed("flag")
				if hasflag {
					flag := p.Inventory.GetItemNamed("flag")
					p.Inventory.DropItem(flag) // _ := ?
					flag.Tags["gettable"] = true
				}
				valid = false
				break
			}

			if o.Tags["item"] && valid {
				cp.Reply(gnet.NewPacket("Rchat", fmt.Sprintf("You see a %s here.", o.Name)))
			}
		}
	}

	if valid {
		cp.Client.Player.SetPos(newpos)
	}
}<|endoftext|>


<commit_before>package handler

import (
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/drone/drone/server/datastore"
	"github.com/drone/drone/server/pubsub"
	"github.com/drone/drone/server/worker"

	"github.com/goji/context"
	"github.com/gorilla/websocket"
	"github.com/zenazn/goji/web"
)

const (
	// Time allowed to write the message to the client.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the client.
	pongWait = 60 * time.Second

	// Send pings to client with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

// WsUser will upgrade the connection to a Websocket and will stream
// all events to the browser pertinent to the authenticated user. If the user
// is not authenticated, only public events are streamed.
func WsUser(c web.C, w http.ResponseWriter, r *http.Request) {
	var ctx = context.FromC(c)
	var user = ToUser(c)

	// upgrade the websocket
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// register a channel for global events
	channel := pubsub.Register(ctx, "_global")
	sub := channel.Subscribe()

	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		sub.Close()
		ws.Close()
	}()

	go func() {
		for {
			select {
			case msg := <-sub.Read():
				work, ok := msg.(*worker.Work)
				if !ok {
					break
				}

				// user must have read access to the repository
				// in order to pass this message along
				if role, err := datastore.GetPerm(ctx, user, work.Repo); err != nil || role.Read == false {
					break
				}

				ws.SetWriteDeadline(time.Now().Add(writeWait))
				err := ws.WriteJSON(work)
				if err != nil {
					ws.Close()
					return
				}
			case <-sub.CloseNotify():
				ws.Close()
				return
			case <-ticker.C:
				ws.SetWriteDeadline(time.Now().Add(writeWait))
				err := ws.WriteMessage(websocket.PingMessage, []byte{})
				if err != nil {
					ws.Close()
					return
				}
			}
		}
	}()

	readWebsocket(ws)
}

// WsConsole will upgrade the connection to a Websocket and will stream
// the build output to the browser.
func WsConsole(c web.C, w http.ResponseWriter, r *http.Request) {
	var commitID, _ = strconv.Atoi(c.URLParams["id"])
	var ctx = context.FromC(c)
	var user = ToUser(c)

	commit, err := datastore.GetCommit(ctx, int64(commitID))
	if err != nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	repo, err := datastore.GetRepo(ctx, commit.RepoID)
	if err != nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	role, err := datastore.GetPerm(ctx, user, repo)
	if err != nil || role.Read == false {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	// find a channel that we can subscribe to
	// and listen for stream updates.
	channel := pubsub.Lookup(ctx, commit.ID)
	if channel == nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	sub := channel.Subscribe()
	defer sub.Close()

	// upgrade the websocket
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		ws.Close()
	}()

	go func() {
		for {
			select {
			case msg := <-sub.Read():
				data, ok := msg.([]byte)
				if !ok {
					break
				}
				ws.SetWriteDeadline(time.Now().Add(writeWait))
				err := ws.WriteMessage(websocket.TextMessage, data)
				if err != nil {
					log.Printf("websocket for commit %d closed. Err: %s\n", commitID, err)
					ws.Close()
					return
				}
			case <-sub.CloseNotify():
				log.Printf("websocket for commit %d closed by client\n", commitID)
				ws.Close()
				return
			case <-ticker.C:
				ws.SetWriteDeadline(time.Now().Add(writeWait))
				err := ws.WriteMessage(websocket.PingMessage, []byte{})
				if err != nil {
					log.Printf("websocket for commit %d closed. Err: %s\n", commitID, err)
					ws.Close()
					return
				}
			}
		}
	}()

	readWebsocket(ws)
}

// readWebsocket will block while reading the websocket data
func readWebsocket(ws *websocket.Conn) {
	defer ws.Close()
	ws.SetReadLimit(512)
	ws.SetReadDeadline(time.Now().Add(pongWait))
	ws.SetPongHandler(func(string) error {
		ws.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})
	for {
		_, _, err := ws.ReadMessage()
		if err != nil {
			break
		}
	}
}
<commit_msg>adding logging to WS layer until it is more stable<commit_after>package handler

import (
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/drone/drone/server/datastore"
	"github.com/drone/drone/server/pubsub"
	"github.com/drone/drone/server/worker"

	"github.com/goji/context"
	"github.com/gorilla/websocket"
	"github.com/zenazn/goji/web"
)

const (
	// Time allowed to write the message to the client.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the client.
	pongWait = 60 * time.Second

	// Send pings to client with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

// WsUser will upgrade the connection to a Websocket and will stream
// all events to the browser pertinent to the authenticated user. If the user
// is not authenticated, only public events are streamed.
func WsUser(c web.C, w http.ResponseWriter, r *http.Request) {
	var ctx = context.FromC(c)
	var user = ToUser(c)

	// upgrade the websocket
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// register a channel for global events
	channel := pubsub.Register(ctx, "_global")
	sub := channel.Subscribe()

	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		sub.Close()
		ws.Close()
	}()

	go func() {
		for {
			select {
			case msg := <-sub.Read():
				work, ok := msg.(*worker.Work)
				if !ok {
					break
				}

				// user must have read access to the repository
				// in order to pass this message along
				if role, err := datastore.GetPerm(ctx, user, work.Repo); err != nil || role.Read == false {
					if err != nil {
						log.Printf("WS: Error getting permissions for repository %s. Error: %s\n", work.Repo.Name, err)
					} else {
						log.Printf("WS: No read access for repository %s\n", work.Repo.Name)
					}
					break
				}

				ws.SetWriteDeadline(time.Now().Add(writeWait))
				err := ws.WriteJSON(work)
				if err != nil {
					ws.Close()
					return
				}
			case <-sub.CloseNotify():
				ws.Close()
				return
			case <-ticker.C:
				ws.SetWriteDeadline(time.Now().Add(writeWait))
				err := ws.WriteMessage(websocket.PingMessage, []byte{})
				if err != nil {
					ws.Close()
					return
				}
			}
		}
	}()

	readWebsocket(ws)
}

// WsConsole will upgrade the connection to a Websocket and will stream
// the build output to the browser.
func WsConsole(c web.C, w http.ResponseWriter, r *http.Request) {
	var commitID, _ = strconv.Atoi(c.URLParams["id"])
	var ctx = context.FromC(c)
	var user = ToUser(c)

	commit, err := datastore.GetCommit(ctx, int64(commitID))
	if err != nil {
		log.Printf("WS: Error retrieving commit by ID. %s\n", err)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	repo, err := datastore.GetRepo(ctx, commit.RepoID)
	if err != nil {
		log.Printf("WS: Error retrieving repo by ID. %s\n", err)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	role, err := datastore.GetPerm(ctx, user, repo)
	if err != nil || role.Read == false {
		if user == nil {
			log.Println("WS: Error getting User session")
		}
		log.Println("WS: Error retrieving Read permission.", err)
		w.WriteHeader(http.StatusNotFound)
		return
	}

	// find a channel that we can subscribe to
	// and listen for stream updates.
	channel := pubsub.Lookup(ctx, commit.ID)
	if channel == nil {
		log.Println("WS: Error getting build stream from channel")
		w.WriteHeader(http.StatusNotFound)
		return
	}
	sub := channel.Subscribe()
	defer sub.Close()

	// upgrade the websocket
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		ws.Close()
	}()

	go func() {
		for {
			select {
			case msg := <-sub.Read():
				data, ok := msg.([]byte)
				if !ok {
					break
				}
				ws.SetWriteDeadline(time.Now().Add(writeWait))
				err := ws.WriteMessage(websocket.TextMessage, data)
				if err != nil {
					ws.Close()
					return
				}
			case <-sub.CloseNotify():
				ws.Close()
				return
			case <-ticker.C:
				ws.SetWriteDeadline(time.Now().Add(writeWait))
				err := ws.WriteMessage(websocket.PingMessage, []byte{})
				if err != nil {
					ws.Close()
					return
				}
			}
		}
	}()

	readWebsocket(ws)
}

// readWebsocket will block while reading the websocket data
func readWebsocket(ws *websocket.Conn) {
	defer ws.Close()
	ws.SetReadLimit(512)
	ws.SetReadDeadline(time.Now().Add(pongWait))
	ws.SetPongHandler(func(string) error {
		ws.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})
	for {
		_, _, err := ws.ReadMessage()
		if err != nil {
			break
		}
	}
}
<|endoftext|>


<commit_before>package server

import (
	"expvar"
	"fmt"
	"net/http"
	"net/http/pprof"
	"time"

	"github.com/goware/lg"
	"github.com/pressly/chi"
	"github.com/pressly/go-metrics"
	"github.com/tobi/airbrake-go"
	"golang.org/x/net/context"
)

// Set the parent context in the middleware chain to something else. Useful
// in the instance of having a global server context to signal all requests.
func ParentContext(parent context.Context) func(next chi.Handler) chi.Handler {
	return func(next chi.Handler) chi.Handler {
		fn := func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
			pctx := context.WithValue(parent, chi.URLParamsCtxKey, chi.URLParams(ctx))
			next.ServeHTTPC(pctx, w, r)
		}
		return chi.HandlerFunc(fn)
	}
}

// Airbrake recoverer middleware to capture and report any panics to
// airbrake.io.
func AirbrakeRecoverer(apiKey string) func(http.Handler) http.Handler {
	airbrake.ApiKey = apiKey
	f := func(h http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			if apiKey != "" {
				defer airbrake.CapturePanic(r)
			}
			h.ServeHTTP(w, r)
		}
		return http.HandlerFunc(fn)
	}
	return f
}

func Profiler() http.Handler {
	r := chi.NewRouter()
	r.Handle("/vars", expVars)
	r.Handle("/pprof/", pprof.Index)
	r.Handle("/pprof/cmdline", pprof.Cmdline)
	r.Handle("/pprof/profile", pprof.Profile)
	r.Handle("/pprof/symbol", pprof.Symbol)
	r.Handle("/pprof/block", pprof.Handler("block").ServeHTTP)
	r.Handle("/pprof/heap", pprof.Handler("heap").ServeHTTP)
	r.Handle("/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
	r.Handle("/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
	return r
}

// Replicated from expvar.go as not public.
func expVars(w http.ResponseWriter, r *http.Request) {
	first := true
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	fmt.Fprintf(w, "{\n")
	expvar.Do(func(kv expvar.KeyValue) {
		if !first {
			fmt.Fprintf(w, ",\n")
		}
		first = false
		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
	})
	fmt.Fprintf(w, "\n}\n")
}

type wrappedResponseWriter struct {
	http.ResponseWriter
	status int
}

func (l *wrappedResponseWriter) WriteHeader(status int) {
	l.status = status
	l.ResponseWriter.WriteHeader(status)
}

func (l *wrappedResponseWriter) Status() int {
	return l.status
}

func trackRoute(metricID string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		route := fmt.Sprintf("route.%s", metricID)

		lg.Info("track metric", route)

		routeTimer := metrics.GetOrRegisterTimer(route, nil)
		errCounter := metrics.GetOrRegisterCounter(fmt.Sprintf("%s-err", route), nil)

		handler := func(w http.ResponseWriter, r *http.Request) {
			reqStart := time.Now()

			lw := &wrappedResponseWriter{w, -1}
			next.ServeHTTP(lw, r)

			routeTimer.UpdateSince(reqStart)
			if lw.Status() >= 400 {
				errCounter.Inc(1)
			}
		}
		return http.HandlerFunc(handler)
	}
}
<commit_msg>Wrong go-metrics pkg<commit_after>package server

import (
	"expvar"
	"fmt"
	"net/http"
	"net/http/pprof"
	"time"

	"github.com/pressly/chi"
	"github.com/rcrowley/go-metrics"
	"github.com/tobi/airbrake-go"
	"golang.org/x/net/context"
)

// Set the parent context in the middleware chain to something else. Useful
// in the instance of having a global server context to signal all requests.
func ParentContext(parent context.Context) func(next chi.Handler) chi.Handler {
	return func(next chi.Handler) chi.Handler {
		fn := func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
			pctx := context.WithValue(parent, chi.URLParamsCtxKey, chi.URLParams(ctx))
			next.ServeHTTPC(pctx, w, r)
		}
		return chi.HandlerFunc(fn)
	}
}

// Airbrake recoverer middleware to capture and report any panics to
// airbrake.io.
func AirbrakeRecoverer(apiKey string) func(http.Handler) http.Handler {
	airbrake.ApiKey = apiKey
	f := func(h http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			if apiKey != "" {
				defer airbrake.CapturePanic(r)
			}
			h.ServeHTTP(w, r)
		}
		return http.HandlerFunc(fn)
	}
	return f
}

func Profiler() http.Handler {
	r := chi.NewRouter()
	r.Handle("/vars", expVars)
	r.Handle("/pprof/", pprof.Index)
	r.Handle("/pprof/cmdline", pprof.Cmdline)
	r.Handle("/pprof/profile", pprof.Profile)
	r.Handle("/pprof/symbol", pprof.Symbol)
	r.Handle("/pprof/block", pprof.Handler("block").ServeHTTP)
	r.Handle("/pprof/heap", pprof.Handler("heap").ServeHTTP)
	r.Handle("/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
	r.Handle("/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
	return r
}

// Replicated from expvar.go as not public.
func expVars(w http.ResponseWriter, r *http.Request) {
	first := true
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	fmt.Fprintf(w, "{\n")
	expvar.Do(func(kv expvar.KeyValue) {
		if !first {
			fmt.Fprintf(w, ",\n")
		}
		first = false
		fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
	})
	fmt.Fprintf(w, "\n}\n")
}

type wrappedResponseWriter struct {
	http.ResponseWriter
	status int
}

func (l *wrappedResponseWriter) WriteHeader(status int) {
	l.status = status
	l.ResponseWriter.WriteHeader(status)
}

func (l *wrappedResponseWriter) Status() int {
	return l.status
}

func trackRoute(metricID string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		route := fmt.Sprintf("route.%s", metricID)

		routeTimer := metrics.GetOrRegisterTimer(route, nil)
		errCounter := metrics.GetOrRegisterCounter(fmt.Sprintf("%s-err", route), nil)

		handler := func(w http.ResponseWriter, r *http.Request) {
			reqStart := time.Now()

			lw := &wrappedResponseWriter{w, -1}
			next.ServeHTTP(lw, r)

			routeTimer.UpdateSince(reqStart)
			if lw.Status() >= 400 {
				errCounter.Inc(1)
			}
		}
		return http.HandlerFunc(handler)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"net"
	"testing"
	"time"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/stretchr/testify/assert"
)

func TestTrackStats(t *testing.T) {
	tracker := &Tracker{
		connections: map[net.Conn]*trackerData{
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
		},
		pools: map[int]map[uint32]interface{}{
			1: {
				1: nil,
				2: nil,
				3: nil,
				4: nil,
				5: nil,
			},
			2: {
				6: nil,
				7: nil,
				8: nil,
			},
		},
		poolAmounts: map[int]uint64{
			1: 100,
			2: 1000,
		},
		poolTypes: map[int]message.ShuffleType{
			1: 0,
			2: 1,
		},
		poolVersions: map[int]uint64{
			1: 0,
			2: 1,
		},
		fullPools: map[int]interface{}{
			1: nil,
		},
		poolSize:    5,
		shufflePort: 3000,
		bannedIPs: map[string]*banData{
			"8.8.8.8": &banData{
				score: maxBanScore,
			},
			"8.8.4.4": &banData{
				score: maxBanScore - 1,
			},
		},
	}

	// Test with ban.
	stats := tracker.Stats("8.8.8.8")

	assert.Equal(t, uint32(3), stats.BanScore)
	assert.Equal(t, true, stats.Banned)
	assert.Equal(t, 8, stats.Connections)
	assert.Equal(t, 5, stats.PoolSize)
	assert.Equal(t, 2, len(stats.Pools))
	assert.Equal(t, 3000, stats.ShufflePort)
	assert.Contains(t, stats.Pools,
		PoolStats{
			Members: 5,
			Amount:  100,
			Type:    "DEFAULT",
			Full:    true,
			Version: 0,
		},
		PoolStats{
			Members: 3,
			Amount:  1000,
			Type:    "DUST",
			Full:    false,
			Version: 1,
		},
	)

	// Test without ban.
	stats2 := tracker.Stats("8.8.4.4")

	assert.Equal(t, uint32(2), stats2.BanScore)
	assert.Equal(t, false, stats2.Banned)
	assert.Equal(t, 8, stats2.Connections)
	assert.Equal(t, 5, stats2.PoolSize)
	assert.Equal(t, 2, len(stats2.Pools))
	assert.Equal(t, 3000, stats2.ShufflePort)
	assert.Contains(t, stats2.Pools,
		PoolStats{
			Members: 5,
			Amount:  100,
			Type:    "DEFAULT",
			Full:    true,
			Version: 0,
		},
		PoolStats{
			Members: 3,
			Amount:  1000,
			Type:    "DUST",
			Full:    false,
			Version: 1,
		},
	)
}

type fakeConn struct {
	f interface{}
}

func (fc *fakeConn) Read(b []byte) (n int, err error)   { return 0, nil }
func (fc *fakeConn) Write(b []byte) (n int, err error)  { return 0, nil }
func (fc *fakeConn) Close() error                       { return nil }
func (fc *fakeConn) LocalAddr() net.Addr                { return &fakeAddr{} }
func (fc *fakeConn) RemoteAddr() net.Addr               { return &fakeAddr{} }
func (fc *fakeConn) SetDeadline(t time.Time) error      { return nil }
func (fc *fakeConn) SetReadDeadline(t time.Time) error  { return nil }
func (fc *fakeConn) SetWriteDeadline(t time.Time) error { return nil }

type fakeAddr struct{}

func (fa *fakeAddr) Network() string { return "" }
func (fa *fakeAddr) String() string  { return "" }
<commit_msg>gofmt -s server/stats_test.go<commit_after>package server

import (
	"net"
	"testing"
	"time"

	"github.com/cashshuffle/cashshuffle/message"
	"github.com/stretchr/testify/assert"
)

func TestTrackStats(t *testing.T) {
	tracker := &Tracker{
		connections: map[net.Conn]*trackerData{
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
			&fakeConn{}: {},
		},
		pools: map[int]map[uint32]interface{}{
			1: {
				1: nil,
				2: nil,
				3: nil,
				4: nil,
				5: nil,
			},
			2: {
				6: nil,
				7: nil,
				8: nil,
			},
		},
		poolAmounts: map[int]uint64{
			1: 100,
			2: 1000,
		},
		poolTypes: map[int]message.ShuffleType{
			1: 0,
			2: 1,
		},
		poolVersions: map[int]uint64{
			1: 0,
			2: 1,
		},
		fullPools: map[int]interface{}{
			1: nil,
		},
		poolSize:    5,
		shufflePort: 3000,
		bannedIPs: map[string]*banData{
			"8.8.8.8": {
				score: maxBanScore,
			},
			"8.8.4.4": {
				score: maxBanScore - 1,
			},
		},
	}

	// Test with ban.
	stats := tracker.Stats("8.8.8.8")

	assert.Equal(t, uint32(3), stats.BanScore)
	assert.Equal(t, true, stats.Banned)
	assert.Equal(t, 8, stats.Connections)
	assert.Equal(t, 5, stats.PoolSize)
	assert.Equal(t, 2, len(stats.Pools))
	assert.Equal(t, 3000, stats.ShufflePort)
	assert.Contains(t, stats.Pools,
		PoolStats{
			Members: 5,
			Amount:  100,
			Type:    "DEFAULT",
			Full:    true,
			Version: 0,
		},
		PoolStats{
			Members: 3,
			Amount:  1000,
			Type:    "DUST",
			Full:    false,
			Version: 1,
		},
	)

	// Test without ban.
	stats2 := tracker.Stats("8.8.4.4")

	assert.Equal(t, uint32(2), stats2.BanScore)
	assert.Equal(t, false, stats2.Banned)
	assert.Equal(t, 8, stats2.Connections)
	assert.Equal(t, 5, stats2.PoolSize)
	assert.Equal(t, 2, len(stats2.Pools))
	assert.Equal(t, 3000, stats2.ShufflePort)
	assert.Contains(t, stats2.Pools,
		PoolStats{
			Members: 5,
			Amount:  100,
			Type:    "DEFAULT",
			Full:    true,
			Version: 0,
		},
		PoolStats{
			Members: 3,
			Amount:  1000,
			Type:    "DUST",
			Full:    false,
			Version: 1,
		},
	)
}

type fakeConn struct {
	f interface{}
}

func (fc *fakeConn) Read(b []byte) (n int, err error)   { return 0, nil }
func (fc *fakeConn) Write(b []byte) (n int, err error)  { return 0, nil }
func (fc *fakeConn) Close() error                       { return nil }
func (fc *fakeConn) LocalAddr() net.Addr                { return &fakeAddr{} }
func (fc *fakeConn) RemoteAddr() net.Addr               { return &fakeAddr{} }
func (fc *fakeConn) SetDeadline(t time.Time) error      { return nil }
func (fc *fakeConn) SetReadDeadline(t time.Time) error  { return nil }
func (fc *fakeConn) SetWriteDeadline(t time.Time) error { return nil }

type fakeAddr struct{}

func (fa *fakeAddr) Network() string { return "" }
func (fa *fakeAddr) String() string  { return "" }

<|endoftext|>


<commit_before>package tls

import (
	"crypto/tls"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/unrolled/secure"
)

func (m *Module) loadTLSConfig() (*tls.Config, error) {
	tlsConfig := &tls.Config{
		NextProtos: []string{"h2"}, // http/2
	}

	// keys are stored in a different place currently
	// extra dot is a hack to skip over session key
	glob := m.tlsDirPath("*.key")
	m.Logger.Infof("loading certs from %s", glob)
	matches, err := filepath.Glob(glob)
	if err != nil {
		return nil, err
	}
	if len(matches) == 0 {
		return nil, nil
	}

	for _, keyFile := range matches {
		crtFile := path.Base(strings.TrimSuffix(keyFile, ".key") + ".crt")
		cert, err := tls.LoadX509KeyPair(m.tlsDirPath(crtFile), keyFile)
		if err != nil {
			if os.IsNotExist(err) {
				continue
			}
			return nil, err
		}
		m.Logger.Infof("found certs %s %s", keyFile, crtFile)
		tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
	}
	tlsConfig.BuildNameToCertificate()
	return tlsConfig, nil
}

// StartTLSProxy starts the tls proxy on port 443. SSL is terminated
// and the connection in passed to the Router.
func (m *Module) StartTLSProxy() error {
	if m.serverStarted {
		return nil
	}

	tlsConfig, err := m.loadTLSConfig()
	if err != nil {
		return err
	}
	if tlsConfig == nil {
		return nil
	}
	l, err := tls.Listen("tcp", ":443", tlsConfig)
	if err != nil {
		return err
	}
	server := &http.Server{
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
		Handler:      m.Router,
		TLSConfig:    tlsConfig, // needed for http/2
	}

	// register middleware for https redirection
	https := secure.New(secure.Options{SSLRedirect: true})
	m.Router.Middleware.Prepend(https.HandlerFuncWithNext)

	m.serverStarted = true
	return server.Serve(l)
}
<commit_msg>server: Extend tls timeout.<commit_after>package tls

import (
	"crypto/tls"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/unrolled/secure"
)

func (m *Module) loadTLSConfig() (*tls.Config, error) {
	tlsConfig := &tls.Config{
		NextProtos: []string{"h2"}, // http/2
	}

	// keys are stored in a different place currently
	// extra dot is a hack to skip over session key
	glob := m.tlsDirPath("*.key")
	m.Logger.Infof("loading certs from %s", glob)
	matches, err := filepath.Glob(glob)
	if err != nil {
		return nil, err
	}
	if len(matches) == 0 {
		return nil, nil
	}

	for _, keyFile := range matches {
		crtFile := path.Base(strings.TrimSuffix(keyFile, ".key") + ".crt")
		cert, err := tls.LoadX509KeyPair(m.tlsDirPath(crtFile), keyFile)
		if err != nil {
			if os.IsNotExist(err) {
				continue
			}
			return nil, err
		}
		m.Logger.Infof("found certs %s %s", keyFile, crtFile)
		tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
	}
	tlsConfig.BuildNameToCertificate()
	return tlsConfig, nil
}

// StartTLSProxy starts the tls proxy on port 443. SSL is terminated
// and the connection in passed to the Router.
func (m *Module) StartTLSProxy() error {
	if m.serverStarted {
		return nil
	}

	tlsConfig, err := m.loadTLSConfig()
	if err != nil {
		return err
	}
	if tlsConfig == nil {
		return nil
	}
	l, err := tls.Listen("tcp", ":443", tlsConfig)
	if err != nil {
		return err
	}
	server := &http.Server{
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
		Handler:      m.Router,
		TLSConfig:    tlsConfig, // needed for http/2
	}

	// register middleware for https redirection
	https := secure.New(secure.Options{SSLRedirect: true})
	m.Router.Middleware.Prepend(https.HandlerFuncWithNext)

	m.serverStarted = true
	return server.Serve(l)
}
<|endoftext|>


<commit_before>// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

import (
	"regexp"
	"testing"

	"github.com/coreos/go-systemd/dbus"
	"github.com/prometheus/client_golang/prometheus"
)

// Creates mock UnitLists
func getUnitListFixtures() [][]unit {
	fixture1 := []unit{
		{
			UnitStatus: dbus.UnitStatus{
				Name:        "foo",
				Description: "foo desc",
				LoadState:   "loaded",
				ActiveState: "active",
				SubState:    "running",
				Followed:    "",
				Path:        "/org/freedesktop/systemd1/unit/foo",
				JobId:       0,
				JobType:     "",
				JobPath:     "/",
			},
		},
		{
			UnitStatus: dbus.UnitStatus{
				Name:        "bar",
				Description: "bar desc",
				LoadState:   "not-found",
				ActiveState: "inactive",
				SubState:    "dead",
				Followed:    "",
				Path:        "/org/freedesktop/systemd1/unit/bar",
				JobId:       0,
				JobType:     "",
				JobPath:     "/",
			},
		},
		{
			UnitStatus: dbus.UnitStatus{
				Name:        "foobar",
				Description: "bar desc",
				LoadState:   "not-found",
				ActiveState: "inactive",
				SubState:    "dead",
				Followed:    "",
				Path:        "/org/freedesktop/systemd1/unit/bar",
				JobId:       0,
				JobType:     "",
				JobPath:     "/",
			},
		},
		{
			UnitStatus: dbus.UnitStatus{
				Name:        "baz",
				Description: "bar desc",
				LoadState:   "not-found",
				ActiveState: "inactive",
				SubState:    "dead",
				Followed:    "",
				Path:        "/org/freedesktop/systemd1/unit/bar",
				JobId:       0,
				JobType:     "",
				JobPath:     "/",
			},
		},
	}

	fixture2 := []unit{}

	return [][]unit{fixture1, fixture2}
}

func TestSystemdCollectorDoesntCrash(t *testing.T) {
	c, err := NewSystemdCollector()
	if err != nil {
		t.Fatal(err)
	}
	sink := make(chan prometheus.Metric)
	go func() {
		for {
			<-sink
		}
	}()

	fixtures := getUnitListFixtures()
	collector := (c).(*systemdCollector)
	for _, units := range fixtures {
		collector.collectUnitStatusMetrics(sink, units)
		collector.collectSockets(sink, units)
	}
}

func TestSystemdIgnoreFilter(t *testing.T) {
	fixtures := getUnitListFixtures()
	whitelistPattern := regexp.MustCompile("foo")
	blacklistPattern := regexp.MustCompile("bar")
	filtered := filterUnits(fixtures[0], whitelistPattern, blacklistPattern)
	for _, unit := range filtered {
		if blacklistPattern.MatchString(unit.Name) || !whitelistPattern.MatchString(unit.Name) {
			t.Error(unit.Name, "should not be in the filtered list")
		}
	}
}
func TestSystemdIgnoreFilterDefaultKeepsAll(t *testing.T) {
	c, err := NewSystemdCollector()
	if err != nil {
		t.Fatal(err)
	}
	fixtures := getUnitListFixtures()
	collector := c.(*systemdCollector)
	filtered := filterUnits(fixtures[0], collector.unitWhitelistPattern, collector.unitBlacklistPattern)
	if len(filtered) != len(fixtures[0]) {
		t.Error("Default filters removed units")
	}
}

func TestSystemdSummary(t *testing.T) {
	fixtures := getUnitListFixtures()
	summary := summarizeUnits(fixtures[0])

	for _, state := range unitStatesName {
		if state == "inactive" {
			testSummaryHelper(t, state, summary[state], 3.0)
		} else if state == "active" {
			testSummaryHelper(t, state, summary[state], 1.0)
		} else {
			testSummaryHelper(t, state, summary[state], 0.0)
		}
	}
}

func testSummaryHelper(t *testing.T, state string, actual float64, expected float64) {
	if actual != expected {
		t.Errorf("Summary mode didn't count %s jobs correctly. Actual: %f, expected: %f", state, actual, expected)
	}
}
<commit_msg>build: make staticcheck happy by using real regexp patterns #1025 (#1026)<commit_after>// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

import (
	"regexp"
	"testing"

	"github.com/coreos/go-systemd/dbus"
	"github.com/prometheus/client_golang/prometheus"
)

// Creates mock UnitLists
func getUnitListFixtures() [][]unit {
	fixture1 := []unit{
		{
			UnitStatus: dbus.UnitStatus{
				Name:        "foo",
				Description: "foo desc",
				LoadState:   "loaded",
				ActiveState: "active",
				SubState:    "running",
				Followed:    "",
				Path:        "/org/freedesktop/systemd1/unit/foo",
				JobId:       0,
				JobType:     "",
				JobPath:     "/",
			},
		},
		{
			UnitStatus: dbus.UnitStatus{
				Name:        "bar",
				Description: "bar desc",
				LoadState:   "not-found",
				ActiveState: "inactive",
				SubState:    "dead",
				Followed:    "",
				Path:        "/org/freedesktop/systemd1/unit/bar",
				JobId:       0,
				JobType:     "",
				JobPath:     "/",
			},
		},
		{
			UnitStatus: dbus.UnitStatus{
				Name:        "foobar",
				Description: "bar desc",
				LoadState:   "not-found",
				ActiveState: "inactive",
				SubState:    "dead",
				Followed:    "",
				Path:        "/org/freedesktop/systemd1/unit/bar",
				JobId:       0,
				JobType:     "",
				JobPath:     "/",
			},
		},
		{
			UnitStatus: dbus.UnitStatus{
				Name:        "baz",
				Description: "bar desc",
				LoadState:   "not-found",
				ActiveState: "inactive",
				SubState:    "dead",
				Followed:    "",
				Path:        "/org/freedesktop/systemd1/unit/bar",
				JobId:       0,
				JobType:     "",
				JobPath:     "/",
			},
		},
	}

	fixture2 := []unit{}

	return [][]unit{fixture1, fixture2}
}

func TestSystemdCollectorDoesntCrash(t *testing.T) {
	c, err := NewSystemdCollector()
	if err != nil {
		t.Fatal(err)
	}
	sink := make(chan prometheus.Metric)
	go func() {
		for {
			<-sink
		}
	}()

	fixtures := getUnitListFixtures()
	collector := (c).(*systemdCollector)
	for _, units := range fixtures {
		collector.collectUnitStatusMetrics(sink, units)
		collector.collectSockets(sink, units)
	}
}

func TestSystemdIgnoreFilter(t *testing.T) {
	fixtures := getUnitListFixtures()
	whitelistPattern := regexp.MustCompile("^foo$")
	blacklistPattern := regexp.MustCompile("^bar$")
	filtered := filterUnits(fixtures[0], whitelistPattern, blacklistPattern)
	for _, unit := range filtered {
		if blacklistPattern.MatchString(unit.Name) || !whitelistPattern.MatchString(unit.Name) {
			t.Error(unit.Name, "should not be in the filtered list")
		}
	}
}
func TestSystemdIgnoreFilterDefaultKeepsAll(t *testing.T) {
	c, err := NewSystemdCollector()
	if err != nil {
		t.Fatal(err)
	}
	fixtures := getUnitListFixtures()
	collector := c.(*systemdCollector)
	filtered := filterUnits(fixtures[0], collector.unitWhitelistPattern, collector.unitBlacklistPattern)
	if len(filtered) != len(fixtures[0]) {
		t.Error("Default filters removed units")
	}
}

func TestSystemdSummary(t *testing.T) {
	fixtures := getUnitListFixtures()
	summary := summarizeUnits(fixtures[0])

	for _, state := range unitStatesName {
		if state == "inactive" {
			testSummaryHelper(t, state, summary[state], 3.0)
		} else if state == "active" {
			testSummaryHelper(t, state, summary[state], 1.0)
		} else {
			testSummaryHelper(t, state, summary[state], 0.0)
		}
	}
}

func testSummaryHelper(t *testing.T, state string, actual float64, expected float64) {
	if actual != expected {
		t.Errorf("Summary mode didn't count %s jobs correctly. Actual: %f, expected: %f", state, actual, expected)
	}
}
<|endoftext|>


<commit_before>package ui

import (
	"encoding/json"
	"fmt"
	"sync"
)

type helloFnType func(func(interface{}) error) error

type Service struct {
	Type       string
	In         <-chan interface{}
	Out        chan<- interface{}
	in         chan interface{}
	out        chan interface{}
	newMessage func() interface{}
	helloFn    helloFnType
}

var (
	mu               sync.RWMutex
	defaultUIChannel *UIChannel

	services = make(map[string]*Service)
)

func (s *Service) write() {
	// Watch for new messages and send them to the combined output.
	for msg := range s.out {
		b, err := newEnvelope(s.Type, msg)
		if err != nil {
			log.Error(err.Error())
		}
		defaultUIChannel.Out <- b
	}
}

func Register(t string, newMessage func() interface{}, helloFn helloFnType) (*Service, error) {
	mu.Lock()

	if services[t] != nil {
		// Using panic because this would be a developer error rather that
		// something that could happen naturally.
		panic("Service was already registered.")
	}

	if defaultUIChannel == nil {
		// Don't start until a service is registered.
		start()
	}

	s := &Service{
		Type:       t,
		in:         make(chan interface{}, 100),
		out:        make(chan interface{}, 100),
		newMessage: newMessage,
		helloFn:    helloFn,
	}
	s.In, s.Out = s.in, s.out

	// Sending existent clients the hello message of the new service.
	if helloFn != nil {
		helloFn(func(msg interface{}) error {
			b, err := newEnvelope(s.Type, msg)
			if err != nil {
				return err
			}
			log.Tracef("Sending initial message to existent clients: %q", b)
			defaultUIChannel.Out <- b
			return nil
		})
	}

	// Adding new service to service map.
	services[t] = s
	mu.Unlock()

	go s.write()
	return s, nil
}

func start() {
	// Establish a channel to the UI for sending and receiving updates
	defaultUIChannel = NewChannel("/data", func(write func([]byte) error) error {
		// Sending hello messages.
		mu.RLock()
		for _, s := range services {
			writer := func(msg interface{}) error {
				b, err := newEnvelope(s.Type, msg)
				if err != nil {
					return err
				}
				return write(b)
			}

			// Delegating task...
			if err := s.helloFn(writer); err != nil {
				log.Errorf("Error writing to socket: %q", err)
			}
		}
		mu.RUnlock()
		return nil
	})

	go read()

	log.Debugf("Accepting websocket connections at: %s", defaultUIChannel.URL)
}

func read() {
	// Reading from the combined input.
	for b := range defaultUIChannel.In {
		// Determining message type.
		var envType EnvelopeType
		err := json.Unmarshal(b, &envType)

		if err != nil {
			log.Errorf("Unable to parse JSON update from browser: %q", err)
			continue
		}

		// Delegating response to the service that registered with the given type.
		if services[envType.Type] == nil {
			log.Errorf("Message type %v belongs to an unkown service.", envType.Type)
			return
		}

		env := &Envelope{}
		err = json.Unmarshal(b, env)
		if err != nil {
			log.Errorf("Unable to unmarshal message of type %v: %v", envType.Type, err)
			return
		}
		// Pass this message and continue reading another one.
		services[env.Type].in <- env.Message
	}
}

func newEnvelope(t string, msg interface{}) ([]byte, error) {
	b, err := json.Marshal(&Envelope{
		EnvelopeType: EnvelopeType{t},
		Message:      msg,
	})
	if err != nil {
		return nil, fmt.Errorf("Unable to marshal message of type %v: %v", t, msg)
	}
	return b, nil
}
<commit_msg>Code review fix<commit_after>package ui

import (
	"encoding/json"
	"fmt"
	"sync"
)

type helloFnType func(func(interface{}) error) error

type Service struct {
	Type       string
	In         <-chan interface{}
	Out        chan<- interface{}
	in         chan interface{}
	out        chan interface{}
	newMessage func() interface{}
	helloFn    helloFnType
}

var (
	mu               sync.RWMutex
	defaultUIChannel *UIChannel

	services = make(map[string]*Service)
)

func (s *Service) write() {
	// Watch for new messages and send them to the combined output.
	for msg := range s.out {
		b, err := newEnvelope(s.Type, msg)
		if err != nil {
			log.Error(err)
			continue
		}
		defaultUIChannel.Out <- b
	}
}

func Register(t string, newMessage func() interface{}, helloFn helloFnType) (*Service, error) {
	mu.Lock()

	if services[t] != nil {
		// Using panic because this would be a developer error rather that
		// something that could happen naturally.
		panic("Service was already registered.")
	}

	if defaultUIChannel == nil {
		// Don't start until a service is registered.
		start()
	}

	s := &Service{
		Type:       t,
		in:         make(chan interface{}, 100),
		out:        make(chan interface{}, 100),
		newMessage: newMessage,
		helloFn:    helloFn,
	}
	s.In, s.Out = s.in, s.out

	// Sending existent clients the hello message of the new service.
	if helloFn != nil {
		helloFn(func(msg interface{}) error {
			b, err := newEnvelope(s.Type, msg)
			if err != nil {
				return err
			}
			log.Tracef("Sending initial message to existent clients: %q", b)
			defaultUIChannel.Out <- b
			return nil
		})
	}

	// Adding new service to service map.
	services[t] = s
	mu.Unlock()

	go s.write()
	return s, nil
}

func start() {
	// Establish a channel to the UI for sending and receiving updates
	defaultUIChannel = NewChannel("/data", func(write func([]byte) error) error {
		// Sending hello messages.
		mu.RLock()
		for _, s := range services {
			writer := func(msg interface{}) error {
				b, err := newEnvelope(s.Type, msg)
				if err != nil {
					return err
				}
				return write(b)
			}

			// Delegating task...
			if err := s.helloFn(writer); err != nil {
				log.Errorf("Error writing to socket: %q", err)
			}
		}
		mu.RUnlock()
		return nil
	})

	go read()

	log.Debugf("Accepting websocket connections at: %s", defaultUIChannel.URL)
}

func read() {
	// Reading from the combined input.
	for b := range defaultUIChannel.In {
		// Determining message type.
		var envType EnvelopeType
		err := json.Unmarshal(b, &envType)

		if err != nil {
			log.Errorf("Unable to parse JSON update from browser: %q", err)
			continue
		}

		// Delegating response to the service that registered with the given type.
		if services[envType.Type] == nil {
			log.Errorf("Message type %v belongs to an unkown service.", envType.Type)
			return
		}

		env := &Envelope{}
		err = json.Unmarshal(b, env)
		if err != nil {
			log.Errorf("Unable to unmarshal message of type %v: %v", envType.Type, err)
			return
		}
		// Pass this message and continue reading another one.
		services[env.Type].in <- env.Message
	}
}

func newEnvelope(t string, msg interface{}) ([]byte, error) {
	b, err := json.Marshal(&Envelope{
		EnvelopeType: EnvelopeType{t},
		Message:      msg,
	})
	if err != nil {
		return nil, fmt.Errorf("Unable to marshal message of type %v: %v", t, msg)
	}
	return b, nil
}
<|endoftext|>


<commit_before>package eio

import (
	"errors"
	"math/rand"
	"net/http"
	"strings"
	"sync"
	"time"
)

const (
	defaultPingTimeout  = 60000
	defaultPingInterval = 25000
)

var defaultTransports = []TransportType{POLLING, WEBSOCKET}

// EngineBuilder is a builder for Engine.
type EngineBuilder struct {
	l1              func(format string, v ...interface{})
	l2              func(format string, v ...interface{})
	l3              func(format string, v ...interface{})
	allowTransports []TransportType
	options         *engineOptions
	path            string
	gen             func(uint32) string
	allowRequest    func(*http.Request) error
	checkProtocol   bool
}

// ForceCheckProtocol force check eio protocol version in query EIO.
func (p *EngineBuilder) ForceCheckProtocol() *EngineBuilder {
	p.checkProtocol = true
	return p
}

// SetAllowRequest set a function that receives a given request, and can decide whether to continue or not.
func (p *EngineBuilder) SetAllowRequest(validator func(*http.Request) error) *EngineBuilder {
	p.allowRequest = validator
	return p
}

// SetLoggerInfo set logger for INFO
func (p *EngineBuilder) SetLoggerInfo(logger func(format string, v ...interface{})) *EngineBuilder {
	p.l1 = logger
	return p
}

// SetLoggerWarn set logger for WARN
func (p *EngineBuilder) SetLoggerWarn(logger func(format string, v ...interface{})) *EngineBuilder {
	p.l2 = logger
	return p
}

// SetLoggerError set logger for ERROR
func (p *EngineBuilder) SetLoggerError(logger func(format string, v ...interface{})) *EngineBuilder {
	p.l3 = logger
	return p
}

// SetTransports define transport types allow.
func (p *EngineBuilder) SetTransports(transports ...TransportType) *EngineBuilder {
	p.allowTransports = transports
	return p
}

// SetGenerateID define the method of creating SocketID.
func (p *EngineBuilder) SetGenerateID(gen func(uint32) string) *EngineBuilder {
	p.gen = gen
	return p
}

// SetPath define the http router path for Engine.
func (p *EngineBuilder) SetPath(path string) *EngineBuilder {
	p.path = path
	return p
}

// SetCookie can control enable/disable of cookie.
func (p *EngineBuilder) SetCookie(enable bool) *EngineBuilder {
	p.options.cookie = enable
	return p
}

// SetCookiePath define the path of cookie.
func (p *EngineBuilder) SetCookiePath(path string) *EngineBuilder {
	if len(path) < 1 {
		panic(errors.New("invalid cookie path: path is blank"))
	}
	if strings.HasPrefix(path, "/") {
		panic(errors.New("cookie path must starts with '/'"))
	}
	p.options.cookiePath = path
	return p
}

// SetCookieHTTPOnly if set true HttpOnly io cookie cannot be accessed by client-side APIs,
// such as JavaScript. (true) This option has no effect
// if cookie or cookiePath is set to false.
func (p *EngineBuilder) SetCookieHTTPOnly(httpOnly bool) *EngineBuilder {
	p.options.cookieHTTPOnly = httpOnly
	return p
}

// SetAllowUpgrades define whether to allow transport upgrades. (default allow upgrades)
func (p *EngineBuilder) SetAllowUpgrades(enable bool) *EngineBuilder {
	p.options.allowUpgrades = enable
	return p
}

// SetPingInterval define ping time interval in millseconds for client. (default is 60 seconds)
func (p *EngineBuilder) SetPingInterval(interval time.Duration) *EngineBuilder {
	p.options.pingInterval = uint32(interval.Seconds())
	return p
}

// SetPingTimeout define ping timeout in millseconds for client. (default is 25 seconds)
func (p *EngineBuilder) SetPingTimeout(timeout time.Duration) *EngineBuilder {
	p.options.pingTimeout = uint32(timeout.Seconds())
	return p
}

// ForceAsyncHandle enable message handlers runing async. (default is sync)
func (p *EngineBuilder) ForceAsyncHandle() *EngineBuilder {
	p.options.handleAsync = true
	return p
}

// Build returns a new Engine.
func (p *EngineBuilder) Build() Engine {
	clone := func(origin engineOptions) engineOptions {
		return origin
	}(*p.options)
	sockets := socketMap{
		store: new(sync.Map),
	}
	eng := &engineImpl{
		logInfo:       p.l1,
		logWarn:       p.l2,
		logErr:        p.l3,
		sequence:      rand.Uint32(),
		onSockets:     make([]func(Socket), 0),
		options:       &clone,
		sockets:       &sockets,
		path:          p.path,
		sidGen:        p.gen,
		junkKiller:    make(chan struct{}),
		junkTicker:    nil,
		allowRequest:  p.allowRequest,
		checkProtocol: p.checkProtocol,
	}
	if len(p.allowTransports) < 1 {
		eng.allowTransports = defaultTransports
	} else {
		allows := make([]TransportType, 0)
		copy(allows, p.allowTransports)
		eng.allowTransports = allows
	}
	return eng
}

// NewEngineBuilder create a builder for Engine.
func NewEngineBuilder() *EngineBuilder {
	options := engineOptions{
		cookie:         false,
		cookiePath:     "/",
		cookieHTTPOnly: true,
		pingInterval:   defaultPingInterval,
		pingTimeout:    defaultPingTimeout,
		allowUpgrades:  true,
	}
	builder := EngineBuilder{
		path:    DefaultPath,
		options: &options,
		gen:     randomSessionID,
	}
	return &builder
}
<commit_msg>这个人很懒, 往代码库丢了一只战斗力10738的皮卡丘.<commit_after>package eio

import (
	"errors"
	"math/rand"
	"net/http"
	"strings"
	"sync"
	"time"
)

const (
	defaultPingTimeout  = 60000
	defaultPingInterval = 25000
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

var defaultTransports = []TransportType{POLLING, WEBSOCKET}

// EngineBuilder is a builder for Engine.
type EngineBuilder struct {
	l1              func(format string, v ...interface{})
	l2              func(format string, v ...interface{})
	l3              func(format string, v ...interface{})
	allowTransports []TransportType
	options         *engineOptions
	path            string
	gen             func(uint32) string
	allowRequest    func(*http.Request) error
	checkProtocol   bool
}

// ForceCheckProtocol force check eio protocol version in query EIO.
func (p *EngineBuilder) ForceCheckProtocol() *EngineBuilder {
	p.checkProtocol = true
	return p
}

// SetAllowRequest set a function that receives a given request, and can decide whether to continue or not.
func (p *EngineBuilder) SetAllowRequest(validator func(*http.Request) error) *EngineBuilder {
	p.allowRequest = validator
	return p
}

// SetLoggerInfo set logger for INFO
func (p *EngineBuilder) SetLoggerInfo(logger func(format string, v ...interface{})) *EngineBuilder {
	p.l1 = logger
	return p
}

// SetLoggerWarn set logger for WARN
func (p *EngineBuilder) SetLoggerWarn(logger func(format string, v ...interface{})) *EngineBuilder {
	p.l2 = logger
	return p
}

// SetLoggerError set logger for ERROR
func (p *EngineBuilder) SetLoggerError(logger func(format string, v ...interface{})) *EngineBuilder {
	p.l3 = logger
	return p
}

// SetTransports define transport types allow.
func (p *EngineBuilder) SetTransports(transports ...TransportType) *EngineBuilder {
	p.allowTransports = transports
	return p
}

// SetGenerateID define the method of creating SocketID.
func (p *EngineBuilder) SetGenerateID(gen func(uint32) string) *EngineBuilder {
	p.gen = gen
	return p
}

// SetPath define the http router path for Engine.
func (p *EngineBuilder) SetPath(path string) *EngineBuilder {
	p.path = path
	return p
}

// SetCookie can control enable/disable of cookie.
func (p *EngineBuilder) SetCookie(enable bool) *EngineBuilder {
	p.options.cookie = enable
	return p
}

// SetCookiePath define the path of cookie.
func (p *EngineBuilder) SetCookiePath(path string) *EngineBuilder {
	if len(path) < 1 {
		panic(errors.New("invalid cookie path: path is blank"))
	}
	if strings.HasPrefix(path, "/") {
		panic(errors.New("cookie path must starts with '/'"))
	}
	p.options.cookiePath = path
	return p
}

// SetCookieHTTPOnly if set true HttpOnly io cookie cannot be accessed by client-side APIs,
// such as JavaScript. (true) This option has no effect
// if cookie or cookiePath is set to false.
func (p *EngineBuilder) SetCookieHTTPOnly(httpOnly bool) *EngineBuilder {
	p.options.cookieHTTPOnly = httpOnly
	return p
}

// SetAllowUpgrades define whether to allow transport upgrades. (default allow upgrades)
func (p *EngineBuilder) SetAllowUpgrades(enable bool) *EngineBuilder {
	p.options.allowUpgrades = enable
	return p
}

// SetPingInterval define ping time interval in millseconds for client. (default is 60 seconds)
func (p *EngineBuilder) SetPingInterval(interval time.Duration) *EngineBuilder {
	p.options.pingInterval = uint32(interval.Seconds())
	return p
}

// SetPingTimeout define ping timeout in millseconds for client. (default is 25 seconds)
func (p *EngineBuilder) SetPingTimeout(timeout time.Duration) *EngineBuilder {
	p.options.pingTimeout = uint32(timeout.Seconds())
	return p
}

// ForceAsyncHandle enable message handlers runing async. (default is sync)
func (p *EngineBuilder) ForceAsyncHandle() *EngineBuilder {
	p.options.handleAsync = true
	return p
}

// Build returns a new Engine.
func (p *EngineBuilder) Build() Engine {
	clone := func(origin engineOptions) engineOptions {
		return origin
	}(*p.options)
	sockets := socketMap{
		store: new(sync.Map),
	}
	eng := &engineImpl{
		logInfo:       p.l1,
		logWarn:       p.l2,
		logErr:        p.l3,
		sequence:      rand.Uint32(),
		onSockets:     make([]func(Socket), 0),
		options:       &clone,
		sockets:       &sockets,
		path:          p.path,
		sidGen:        p.gen,
		junkKiller:    make(chan struct{}),
		junkTicker:    nil,
		allowRequest:  p.allowRequest,
		checkProtocol: p.checkProtocol,
	}
	if len(p.allowTransports) < 1 {
		eng.allowTransports = defaultTransports
	} else {
		allows := make([]TransportType, 0)
		copy(allows, p.allowTransports)
		eng.allowTransports = allows
	}
	return eng
}

// NewEngineBuilder create a builder for Engine.
func NewEngineBuilder() *EngineBuilder {
	options := engineOptions{
		cookie:         false,
		cookiePath:     "/",
		cookieHTTPOnly: true,
		pingInterval:   defaultPingInterval,
		pingTimeout:    defaultPingTimeout,
		allowUpgrades:  true,
	}
	builder := EngineBuilder{
		path:    DefaultPath,
		options: &options,
		gen:     randomSessionID,
	}
	return &builder
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package context

import (
	"errors"
	"net/http"
	"net/url"
	"path"
	"strings"

	"github.com/issue9/mux/v3"
)

// HandlerFunc 路由项处理函数原型
type HandlerFunc func(*Context)

// Router 路由管理
type Router struct {
	srv *Server
	mux *mux.Mux

	root    string
	url     *url.URL
	filters []Filter
}

// Prefix 管理带有统一前缀的路由项
type Prefix struct {
	srv *Server
	mux *mux.Mux

	prefix  string
	filters []Filter
}

// Resource 以资源地址为对象的路由配置
type Resource struct {
	srv *Server
	mux *mux.Mux

	pattern string
	filters []Filter
}

func buildPrefix(srv *Server, mux *mux.Mux, prefix string, filter ...Filter) *Prefix {
	return &Prefix{
		srv: srv,
		mux: mux,

		prefix:  prefix,
		filters: filter,
	}
}

func buildRouter(srv *Server, mux *mux.Mux, u *url.URL, filter ...Filter) *Router {
	// 保证不以 / 结尾
	if len(u.Path) > 0 && u.Path[len(u.Path)-1] == '/' {
		u.Path = u.Path[:len(u.Path)-1]
	}

	return &Router{
		srv: srv,
		mux: mux,

		url:     u,
		root:    u.String(),
		filters: filter,
	}
}

// Router 返回操作路由项的实例
//
// 路由地址基于 root 的值，
// 且所有的路由都会自动应用通过 Server.AddFilters 和 Server.AddMiddlewares 添加的中间件。
func (srv *Server) Router() *Router {
	return srv.router
}

// Mux 返回 mux.Mux 实例
func (router *Router) Mux() *mux.Mux {
	return router.mux
}

// Path 生成路径部分的地址
func (router *Router) Path(p string) string {
	p = path.Join(router.url.Path, p)
	if p != "" && p[0] != '/' {
		p = "/" + p
	}

	return p
}

// URL 构建一条基于 Root 的完整 URL
func (router *Router) URL(p string) string {
	switch {
	case len(p) == 0:
		return router.root
	case p[0] == '/':
		// 由 buildRouter 保证 root 不能 / 结尾
		return router.root + p
	default:
		return router.root + "/" + p
	}
}

// NewRouter 构建基于 matcher 匹配的路由操作实例
//
// 路由地址不再基于 root 的值。
// 也不会应用通过 Server.AddFilters 添加的中间件，但是会应用 Server.AddMiddlewares 添加的中间件。
func (router *Router) NewRouter(name string, url *url.URL, matcher mux.Matcher, filter ...Filter) (*Router, bool) {
	m, ok := router.Mux().NewMux(name, matcher)
	if !ok {
		return nil, false
	}

	return buildRouter(router.srv, m, url, filter...), true
}

// Static 添加静态路由
//
// path 为路由地址，必须以命名参数结尾，比如 /assets/{path}，之后可以通过此值删除路由项；
// dir 为指向静态文件的路径；
//
// 如果要删除该静态路由，则可以将 path 传递给 Remove 进行删除。
//
// 比如在 Root 的值为 example.com/blog 时，
// 将参数指定为 /admin/{path} 和 ~/data/assets/admin
// 表示将 example.com/blog/admin/* 解析到 ~/data/assets/admin 目录之下。
func (router *Router) Static(path, dir string) error {
	path = router.Path(path)
	lastStart := strings.LastIndexByte(path, '{')
	if lastStart < 0 || len(path) == 0 || path[len(path)-1] != '}' || lastStart+2 == len(path) {
		return errors.New("path 必须是命名参数结尾：比如 /assets/{path}。")
	}

	h := http.StripPrefix(path[:lastStart], http.FileServer(http.Dir(dir)))
	return router.Mux().Handle(path, h, http.MethodGet)
}

// Resource 生成资源项
func (router *Router) Resource(pattern string, filter ...Filter) *Resource {
	filters := make([]Filter, 0, len(router.filters)+len(filter))
	filters = append(filters, router.filters...)
	filters = append(filters, filter...)

	return &Resource{
		srv: router.srv,
		mux: router.Mux(),

		pattern: router.root + pattern,
		filters: filters,
	}
}

// Prefix 返回特定前缀的路由设置对象
func (router *Router) Prefix(prefix string, filter ...Filter) *Prefix {
	filters := make([]Filter, 0, len(router.filters)+len(filter))
	filters = append(filters, router.filters...)
	filters = append(filters, filter...)
	return buildPrefix(router.srv, router.mux, router.root+prefix, filters...)
}

// Handle 添加路由请求项
func (router *Router) Handle(path string, h HandlerFunc, method ...string) error {
	filters := make([]Filter, 0, len(router.srv.filters)+len(router.srv.filters))
	filters = append(filters, router.srv.filters...) // p.srv 可以动态改动
	filters = append(filters, router.filters...)

	return router.Mux().HandleFunc(router.root+path, func(w http.ResponseWriter, r *http.Request) {
		FilterHandler(h, filters...)(router.srv.NewContext(w, r))
	}, method...)
}

// Handle 添加路由请求项
func (router *Router) handle(path string, h HandlerFunc, method ...string) *Router {
	if err := router.Handle(path, h, method...); err != nil {
		panic(err)
	}
	return router
}

// Get 添加 GET 请求处理项
func (router *Router) Get(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodGet)
}

// Post 添加 POST 请求处理项
func (router *Router) Post(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodPost)
}

// Put 添加 PUT 请求处理项
func (router *Router) Put(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodPut)
}

// Delete 添加 DELETE 请求处理项
func (router *Router) Delete(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodDelete)
}

// Options 添加 OPTIONS 请求处理项
//
// 忽略 Filter 类型的是间件，如果有需要，可以采用 Handle 处理 Options 请求。
func (router *Router) Options(path, allow string) *Router {
	router.Mux().Options(router.root+path, allow)
	return router
}

// Patch 添加 PATCH 请求处理项
func (router *Router) Patch(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodPatch)
}

// Remove 删除指定的路由项
func (router *Router) Remove(path string, method ...string) {
	router.Mux().Remove(router.root+path, method...)
}

// Resource 生成资源项
func (p *Prefix) Resource(pattern string, filter ...Filter) *Resource {
	filters := make([]Filter, 0, len(p.filters)+len(filter))
	filters = append(filters, p.filters...)
	filters = append(filters, filter...)

	return &Resource{
		srv: p.srv,
		mux: p.mux,

		pattern: p.prefix + pattern,
		filters: filters,
	}
}

// Prefix 返回特定前缀的路由设置对象
func (p *Prefix) Prefix(prefix string, filter ...Filter) *Prefix {
	filters := make([]Filter, 0, len(p.filters)+len(filter))
	filters = append(filters, p.filters...)
	filters = append(filters, filter...)
	return buildPrefix(p.srv, p.mux, p.prefix+prefix, filter...)
}

// Handle 添加路由请求项
func (p *Prefix) Handle(path string, h HandlerFunc, method ...string) error {
	filters := make([]Filter, 0, len(p.filters)+len(p.srv.filters))
	filters = append(filters, p.srv.filters...) // p.srv 可以动态改动
	filters = append(filters, p.filters...)

	return p.mux.HandleFunc(p.prefix+path, func(w http.ResponseWriter, r *http.Request) {
		FilterHandler(h, filters...)(p.srv.NewContext(w, r))
	}, method...)
}

// Handle 添加路由请求项
func (p *Prefix) handle(path string, h HandlerFunc, method ...string) *Prefix {
	if err := p.Handle(path, h, method...); err != nil {
		panic(err)
	}
	return p
}

// Get 添加 GET 请求处理项
func (p *Prefix) Get(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodGet)
}

// Post 添加 POST 请求处理项
func (p *Prefix) Post(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodPost)
}

// Put 添加 PUT 请求处理项
func (p *Prefix) Put(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodPut)
}

// Delete 添加 DELETE 请求处理项
func (p *Prefix) Delete(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodDelete)
}

// Options 添加 OPTIONS 请求处理项
//
// 忽略 Filter 类型的是间件，如果有需要，可以采用 Handle 处理 Options 请求。
func (p *Prefix) Options(path, allow string) *Prefix {
	p.mux.Options(p.prefix+path, allow)
	return p
}

// Patch 添加 PATCH 请求处理项
func (p *Prefix) Patch(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodPatch)
}

// Remove 删除指定的路由项
func (p *Prefix) Remove(path string, method ...string) {
	p.mux.Remove(p.prefix+path, method...)
}

// Handle 添加路由项
func (r *Resource) Handle(h HandlerFunc, method ...string) error {
	filters := make([]Filter, 0, len(r.filters)+len(r.srv.filters))
	filters = append(filters, r.srv.filters...)
	filters = append(filters, r.filters...)

	return r.mux.HandleFunc(r.pattern, func(w http.ResponseWriter, req *http.Request) {
		FilterHandler(h, filters...)(r.srv.NewContext(w, req))
	}, method...)
}

func (r *Resource) handle(h HandlerFunc, method ...string) *Resource {
	if err := r.Handle(FilterHandler(h, r.filters...), method...); err != nil {
		panic(err)
	}
	return r
}

// Get 指定一个 GET 请求
func (r *Resource) Get(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodGet)
}

// Post 指定个 POST 请求处理
func (r *Resource) Post(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodPost)
}

// Delete 指定个 Delete 请求处理
func (r *Resource) Delete(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodDelete)
}

// Put 指定个 Put 请求处理
func (r *Resource) Put(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodPut)
}

// Patch 指定个 Patch 请求处理
func (r *Resource) Patch(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodPatch)
}

// Remove 删除指定的路由项
func (r *Resource) Remove(method ...string) {
	r.mux.Remove(r.pattern, method...)
}

// Options 添加 OPTIONS 请求处理项
//
// 忽略 Filter 类型的是间件，如果有需要，可以采用 Handle 处理 Options 请求。
func (r *Resource) Options(allow string) *Resource {
	r.mux.Options(r.pattern, allow)
	return r
}
<commit_msg>fix(context): 修正前缀取值错误<commit_after>// SPDX-License-Identifier: MIT

package context

import (
	"errors"
	"net/http"
	"net/url"
	"path"
	"strings"

	"github.com/issue9/mux/v3"
)

// HandlerFunc 路由项处理函数原型
type HandlerFunc func(*Context)

// Router 路由管理
type Router struct {
	srv *Server
	mux *mux.Mux

	root    string
	url     *url.URL
	filters []Filter
}

// Prefix 管理带有统一前缀的路由项
type Prefix struct {
	srv *Server
	mux *mux.Mux

	prefix  string
	filters []Filter
}

// Resource 以资源地址为对象的路由配置
type Resource struct {
	srv *Server
	mux *mux.Mux

	pattern string
	filters []Filter
}

func buildPrefix(srv *Server, mux *mux.Mux, prefix string, filter ...Filter) *Prefix {
	return &Prefix{
		srv: srv,
		mux: mux,

		prefix:  prefix,
		filters: filter,
	}
}

func buildRouter(srv *Server, mux *mux.Mux, u *url.URL, filter ...Filter) *Router {
	// 保证不以 / 结尾
	if len(u.Path) > 0 && u.Path[len(u.Path)-1] == '/' {
		u.Path = u.Path[:len(u.Path)-1]
	}

	return &Router{
		srv: srv,
		mux: mux,

		url:     u,
		root:    u.String(),
		filters: filter,
	}
}

// Router 返回操作路由项的实例
//
// 路由地址基于 root 的值，
// 且所有的路由都会自动应用通过 Server.AddFilters 和 Server.AddMiddlewares 添加的中间件。
func (srv *Server) Router() *Router {
	return srv.router
}

// Mux 返回 mux.Mux 实例
func (router *Router) Mux() *mux.Mux {
	return router.mux
}

// Path 生成路径部分的地址
func (router *Router) Path(p string) string {
	p = path.Join(router.url.Path, p)
	if p != "" && p[0] != '/' {
		p = "/" + p
	}

	return p
}

// URL 构建一条基于 Root 的完整 URL
func (router *Router) URL(p string) string {
	switch {
	case len(p) == 0:
		return router.root
	case p[0] == '/':
		// 由 buildRouter 保证 root 不能 / 结尾
		return router.root + p
	default:
		return router.root + "/" + p
	}
}

// NewRouter 构建基于 matcher 匹配的路由操作实例
//
// 路由地址不再基于 root 的值。
// 也不会应用通过 Server.AddFilters 添加的中间件，但是会应用 Server.AddMiddlewares 添加的中间件。
func (router *Router) NewRouter(name string, url *url.URL, matcher mux.Matcher, filter ...Filter) (*Router, bool) {
	m, ok := router.Mux().NewMux(name, matcher)
	if !ok {
		return nil, false
	}

	return buildRouter(router.srv, m, url, filter...), true
}

// Static 添加静态路由
//
// path 为路由地址，必须以命名参数结尾，比如 /assets/{path}，之后可以通过此值删除路由项；
// dir 为指向静态文件的路径；
//
// 如果要删除该静态路由，则可以将 path 传递给 Remove 进行删除。
//
// 比如在 Root 的值为 example.com/blog 时，
// 将参数指定为 /admin/{path} 和 ~/data/assets/admin
// 表示将 example.com/blog/admin/* 解析到 ~/data/assets/admin 目录之下。
func (router *Router) Static(path, dir string) error {
	path = router.Path(path)
	lastStart := strings.LastIndexByte(path, '{')
	if lastStart < 0 || len(path) == 0 || path[len(path)-1] != '}' || lastStart+2 == len(path) {
		return errors.New("path 必须是命名参数结尾：比如 /assets/{path}。")
	}

	h := http.StripPrefix(path[:lastStart], http.FileServer(http.Dir(dir)))
	return router.Mux().Handle(path, h, http.MethodGet)
}

// Resource 生成资源项
func (router *Router) Resource(pattern string, filter ...Filter) *Resource {
	filters := make([]Filter, 0, len(router.filters)+len(filter))
	filters = append(filters, router.filters...)
	filters = append(filters, filter...)

	return &Resource{
		srv: router.srv,
		mux: router.Mux(),

		pattern: router.url.Path + pattern,
		filters: filters,
	}
}

// Prefix 返回特定前缀的路由设置对象
func (router *Router) Prefix(prefix string, filter ...Filter) *Prefix {
	filters := make([]Filter, 0, len(router.filters)+len(filter))
	filters = append(filters, router.filters...)
	filters = append(filters, filter...)
	return buildPrefix(router.srv, router.mux, router.url.Path+prefix, filters...)
}

// Handle 添加路由请求项
func (router *Router) Handle(path string, h HandlerFunc, method ...string) error {
	filters := make([]Filter, 0, len(router.srv.filters)+len(router.srv.filters))
	filters = append(filters, router.srv.filters...) // p.srv 可以动态改动
	filters = append(filters, router.filters...)

	return router.Mux().HandleFunc(router.url.Path+path, func(w http.ResponseWriter, r *http.Request) {
		FilterHandler(h, filters...)(router.srv.NewContext(w, r))
	}, method...)
}

// Handle 添加路由请求项
func (router *Router) handle(path string, h HandlerFunc, method ...string) *Router {
	if err := router.Handle(path, h, method...); err != nil {
		panic(err)
	}
	return router
}

// Get 添加 GET 请求处理项
func (router *Router) Get(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodGet)
}

// Post 添加 POST 请求处理项
func (router *Router) Post(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodPost)
}

// Put 添加 PUT 请求处理项
func (router *Router) Put(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodPut)
}

// Delete 添加 DELETE 请求处理项
func (router *Router) Delete(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodDelete)
}

// Options 添加 OPTIONS 请求处理项
//
// 忽略 Filter 类型的是间件，如果有需要，可以采用 Handle 处理 Options 请求。
func (router *Router) Options(path, allow string) *Router {
	router.Mux().Options(router.url.Path+path, allow)
	return router
}

// Patch 添加 PATCH 请求处理项
func (router *Router) Patch(path string, h HandlerFunc) *Router {
	return router.handle(path, h, http.MethodPatch)
}

// Remove 删除指定的路由项
func (router *Router) Remove(path string, method ...string) {
	router.Mux().Remove(router.url.Path+path, method...)
}

// Resource 生成资源项
func (p *Prefix) Resource(pattern string, filter ...Filter) *Resource {
	filters := make([]Filter, 0, len(p.filters)+len(filter))
	filters = append(filters, p.filters...)
	filters = append(filters, filter...)

	return &Resource{
		srv: p.srv,
		mux: p.mux,

		pattern: p.prefix + pattern,
		filters: filters,
	}
}

// Prefix 返回特定前缀的路由设置对象
func (p *Prefix) Prefix(prefix string, filter ...Filter) *Prefix {
	filters := make([]Filter, 0, len(p.filters)+len(filter))
	filters = append(filters, p.filters...)
	filters = append(filters, filter...)
	return buildPrefix(p.srv, p.mux, p.prefix+prefix, filter...)
}

// Handle 添加路由请求项
func (p *Prefix) Handle(path string, h HandlerFunc, method ...string) error {
	filters := make([]Filter, 0, len(p.filters)+len(p.srv.filters))
	filters = append(filters, p.srv.filters...) // p.srv 可以动态改动
	filters = append(filters, p.filters...)

	return p.mux.HandleFunc(p.prefix+path, func(w http.ResponseWriter, r *http.Request) {
		FilterHandler(h, filters...)(p.srv.NewContext(w, r))
	}, method...)
}

// Handle 添加路由请求项
func (p *Prefix) handle(path string, h HandlerFunc, method ...string) *Prefix {
	if err := p.Handle(path, h, method...); err != nil {
		panic(err)
	}
	return p
}

// Get 添加 GET 请求处理项
func (p *Prefix) Get(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodGet)
}

// Post 添加 POST 请求处理项
func (p *Prefix) Post(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodPost)
}

// Put 添加 PUT 请求处理项
func (p *Prefix) Put(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodPut)
}

// Delete 添加 DELETE 请求处理项
func (p *Prefix) Delete(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodDelete)
}

// Options 添加 OPTIONS 请求处理项
//
// 忽略 Filter 类型的是间件，如果有需要，可以采用 Handle 处理 Options 请求。
func (p *Prefix) Options(path, allow string) *Prefix {
	p.mux.Options(p.prefix+path, allow)
	return p
}

// Patch 添加 PATCH 请求处理项
func (p *Prefix) Patch(path string, h HandlerFunc) *Prefix {
	return p.handle(path, h, http.MethodPatch)
}

// Remove 删除指定的路由项
func (p *Prefix) Remove(path string, method ...string) {
	p.mux.Remove(p.prefix+path, method...)
}

// Handle 添加路由项
func (r *Resource) Handle(h HandlerFunc, method ...string) error {
	filters := make([]Filter, 0, len(r.filters)+len(r.srv.filters))
	filters = append(filters, r.srv.filters...)
	filters = append(filters, r.filters...)

	return r.mux.HandleFunc(r.pattern, func(w http.ResponseWriter, req *http.Request) {
		FilterHandler(h, filters...)(r.srv.NewContext(w, req))
	}, method...)
}

func (r *Resource) handle(h HandlerFunc, method ...string) *Resource {
	if err := r.Handle(FilterHandler(h, r.filters...), method...); err != nil {
		panic(err)
	}
	return r
}

// Get 指定一个 GET 请求
func (r *Resource) Get(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodGet)
}

// Post 指定个 POST 请求处理
func (r *Resource) Post(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodPost)
}

// Delete 指定个 Delete 请求处理
func (r *Resource) Delete(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodDelete)
}

// Put 指定个 Put 请求处理
func (r *Resource) Put(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodPut)
}

// Patch 指定个 Patch 请求处理
func (r *Resource) Patch(h HandlerFunc) *Resource {
	return r.handle(h, http.MethodPatch)
}

// Remove 删除指定的路由项
func (r *Resource) Remove(method ...string) {
	r.mux.Remove(r.pattern, method...)
}

// Options 添加 OPTIONS 请求处理项
//
// 忽略 Filter 类型的是间件，如果有需要，可以采用 Handle 处理 Options 请求。
func (r *Resource) Options(allow string) *Resource {
	r.mux.Options(r.pattern, allow)
	return r
}
<|endoftext|>


<commit_before>package echo

import (
	"context"
	"fmt"
	"net/http"
	"sync"
	"time"

	"github.com/admpub/events"
	"github.com/admpub/events/emitter"

	pkgCode "github.com/webx-top/echo/code"
	"github.com/webx-top/echo/engine"
	"github.com/webx-top/echo/logger"
	"github.com/webx-top/echo/param"
	"github.com/webx-top/poolx/bufferpool"
)

type xContext struct {
	Validator
	Translator
	events.Emitter
	transaction         *BaseTransaction
	sessioner           Sessioner
	cookier             Cookier
	request             engine.Request
	response            engine.Response
	path                string
	pnames              []string
	pvalues             []string
	hnames              []string // host
	hvalues             []string // host
	store               Store
	storeLock           sync.RWMutex
	internal            *param.SafeMap
	handler             Handler
	route               *Route
	rid                 int
	echo                *Echo
	funcs               map[string]interface{}
	renderer            Renderer
	sessionOptions      *SessionOptions
	withFormatExtension bool
	defaultExtension    string
	format              string
	code                int
	preResponseHook     []func() error
	dataEngine          Data
	accept              *Accepts
	auto                bool
	onHostFound         func(Context) (bool, error)
}

// NewContext creates a Context object.
func NewContext(req engine.Request, res engine.Response, e *Echo) Context {
	c := &xContext{
		Validator:   e.Validator,
		Translator:  DefaultNopTranslate,
		Emitter:     emitter.DefaultCondEmitter,
		transaction: DefaultNopTransaction,
		request:     req,
		response:    res,
		echo:        e,
		pvalues:     make([]string, *e.maxParam),
		internal:    param.NewMap(),
		store:       make(Store),
		handler:     NotFoundHandler,
		funcs:       make(map[string]interface{}),
		sessioner:   DefaultSession,
		onHostFound: e.onHostFound,
	}
	c.cookier = NewCookier(c)
	c.dataEngine = NewData(c)
	return c
}

func (c *xContext) StdContext() context.Context {
	return c.request.Context()
}

func (c *xContext) WithContext(ctx context.Context) *http.Request {
	return c.request.WithContext(ctx)
}

func (c *xContext) SetValue(key string, value interface{}) {
	c.request.SetValue(key, value)
}

func (c *xContext) Internal() *param.SafeMap {
	return c.internal
}

func (c *xContext) SetEmitter(emitter events.Emitter) {
	c.Emitter = emitter
}

func (c *xContext) Handler() Handler {
	return c.handler
}

func (c *xContext) Deadline() (deadline time.Time, ok bool) {
	return c.StdContext().Deadline()
}

func (c *xContext) Done() <-chan struct{} {
	return c.StdContext().Done()
}

func (c *xContext) Err() error {
	return c.StdContext().Err()
}

func (c *xContext) Value(key interface{}) interface{} {
	return c.StdContext().Value(key)
}

func (c *xContext) Handle(ctx Context) error {
	return c.handler.Handle(ctx)
}

func (c *xContext) Route() *Route {
	if c.route == nil {
		if c.rid < 0 || c.rid >= len(c.echo.router.routes) {
			c.route = defaultRoute
		} else {
			c.route = c.echo.router.routes[c.rid]
		}
	}
	return c.route
}

func (c *xContext) SetAuto(on bool) Context {
	c.auto = on
	return c
}

// Error invokes the registered HTTP error handler. Generally used by middleware.
func (c *xContext) Error(err error) {
	c.echo.httpErrorHandler(err, c)
}

func (c *xContext) NewError(code pkgCode.Code, msg string, args ...interface{}) *Error {
	if len(msg) > 0 {
		msg = c.T(msg, args...)
	}
	return NewError(msg, code)
}

func (c *xContext) NewErrorWith(err error, code pkgCode.Code, args ...interface{}) *Error {
	var msg string
	if len(args) > 0 {
		msg = param.AsString(args[0])
		if len(msg) > 0 {
			if len(args) > 1 {
				msg = c.T(msg, args[1:]...)
			} else {
				msg = c.T(msg)
			}
		}
	}
	return NewErrorWith(err, msg, code)
}

// Logger returns the `Logger` instance.
func (c *xContext) Logger() logger.Logger {
	return c.echo.logger
}

// Object returns the `context` object.
func (c *xContext) Object() *xContext {
	return c
}

// Echo returns the `Echo` instance.
func (c *xContext) Echo() *Echo {
	return c.echo
}

func (c *xContext) SetTranslator(t Translator) {
	c.Translator = t
}

func (c *xContext) SetDefaultExtension(ext string) {
	c.defaultExtension = ext
}

func (c *xContext) DefaultExtension() string {
	if c.withFormatExtension {
		return `.` + c.Format()
	}
	if len(c.defaultExtension) > 0 {
		return c.defaultExtension
	}
	return c.echo.defaultExtension
}

func (c *xContext) Reset(req engine.Request, res engine.Response) {
	c.Validator = c.echo.Validator
	c.Emitter = emitter.DefaultCondEmitter
	c.Translator = DefaultNopTranslate
	c.transaction = DefaultNopTransaction
	c.sessioner = DefaultSession
	c.cookier = NewCookier(c)
	c.request = req
	c.response = res
	c.internal = param.NewMap()
	c.store = make(Store)
	c.path = ""
	c.pnames = nil
	c.hnames = nil
	c.hvalues = nil
	c.funcs = make(map[string]interface{})
	c.renderer = nil
	c.handler = NotFoundHandler
	c.route = nil
	c.rid = -1
	c.sessionOptions = nil
	c.withFormatExtension = false
	c.defaultExtension = ""
	c.format = ""
	c.code = 0
	c.auto = false
	c.preResponseHook = nil
	c.accept = nil
	c.dataEngine = NewData(c)
	c.onHostFound = c.echo.onHostFound
	// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
	for i := 0; i < *c.echo.maxParam; i++ {
		c.pvalues[i] = ""
	}
}

func (c *xContext) GetFunc(key string) interface{} {
	return c.funcs[key]
}

func (c *xContext) SetFunc(key string, val interface{}) {
	c.funcs[key] = val
}

func (c *xContext) ResetFuncs(funcs map[string]interface{}) {
	c.funcs = funcs
}

func (c *xContext) Funcs() map[string]interface{} {
	return c.funcs
}

func (c *xContext) Renderer() Renderer {
	if c.renderer != nil {
		return c.renderer
	}
	return c.echo.renderer
}

func (c *xContext) Fetch(name string, data interface{}) (b []byte, err error) {
	if c.renderer == nil {
		if c.echo.renderer == nil {
			return nil, ErrRendererNotRegistered
		}
		c.renderer = c.echo.renderer
	}
	buf := bufferpool.Get()
	defer bufferpool.Release(buf)
	err = c.renderer.Render(buf, name, data, c)
	if err != nil {
		return
	}
	b = buf.Bytes()
	return
}

func (c *xContext) SetValidator(v Validator) {
	c.Validator = v
}

// SetRenderer registers an HTML template renderer.
func (c *xContext) SetRenderer(r Renderer) {
	c.renderer = r
}

func (c *xContext) SetSessioner(s Sessioner) {
	c.sessioner = s
}

func (c *xContext) Atop(v string) param.String {
	return param.String(v)
}

func (c *xContext) ToParamString(v string) param.String {
	return param.String(v)
}

func (c *xContext) ToStringSlice(v []string) param.StringSlice {
	return param.StringSlice(v)
}

func (c *xContext) SetFormat(format string) {
	c.format = format
}

func (c *xContext) WithFormatExtension(on bool) {
	c.withFormatExtension = on
}

func (c *xContext) SetCode(code int) {
	c.code = code
}

func (c *xContext) Code() int {
	return c.code
}

func (c *xContext) SetData(data Data) {
	c.dataEngine = data
}

func (c *xContext) Data() Data {
	return c.dataEngine
}

// MapData 映射数据到结构体
func (c *xContext) MapData(i interface{}, data map[string][]string, names ...string) error {
	var name string
	if len(names) > 0 {
		name = names[0]
	}
	return NamedStructMap(c.echo, i, data, name)
}

func (c *xContext) AddPreResponseHook(hook func() error) Context {
	if c.preResponseHook == nil {
		c.preResponseHook = []func() error{hook}
	} else {
		c.preResponseHook = append(c.preResponseHook, hook)
	}
	return c
}

func (c *xContext) SetPreResponseHook(hook ...func() error) Context {
	c.preResponseHook = hook
	return c
}

func (c *xContext) OnHostFound(onHostFound func(Context) (bool, error)) Context {
	c.onHostFound = onHostFound
	return c
}

func (c *xContext) FireHostFound() (bool, error) {
	if c.onHostFound == nil {
		return true, nil
	}
	return c.onHostFound(c)
}

func (c *xContext) preResponse() error {
	if c.preResponseHook == nil {
		c.cookier.Send()
		return nil
	}
	for _, hook := range c.preResponseHook {
		if err := hook(); err != nil {
			return err
		}
	}
	c.cookier.Send()
	return nil
}

func (c *xContext) PrintFuncs() {
	for key, fn := range c.Funcs() {
		fmt.Printf("[Template Func](%p) %-15s -> %s \n", fn, key, HandlerName(fn))
	}
}
<commit_msg>update<commit_after>package echo

import (
	"context"
	"fmt"
	"net/http"
	"sync"
	"time"

	"github.com/admpub/events"
	"github.com/admpub/events/emitter"

	pkgCode "github.com/webx-top/echo/code"
	"github.com/webx-top/echo/engine"
	"github.com/webx-top/echo/logger"
	"github.com/webx-top/echo/param"
	"github.com/webx-top/poolx/bufferpool"
)

type xContext struct {
	Validator
	Translator
	events.Emitter
	transaction         *BaseTransaction
	sessioner           Sessioner
	cookier             Cookier
	request             engine.Request
	response            engine.Response
	path                string
	pnames              []string
	pvalues             []string
	hnames              []string // host
	hvalues             []string // host
	store               Store
	storeLock           sync.RWMutex
	internal            *param.SafeMap
	handler             Handler
	route               *Route
	rid                 int
	echo                *Echo
	funcs               map[string]interface{}
	renderer            Renderer
	sessionOptions      *SessionOptions
	withFormatExtension bool
	defaultExtension    string
	format              string
	code                int
	preResponseHook     []func() error
	dataEngine          Data
	accept              *Accepts
	auto                bool
	onHostFound         func(Context) (bool, error)
}

// NewContext creates a Context object.
func NewContext(req engine.Request, res engine.Response, e *Echo) Context {
	c := &xContext{
		Validator:   e.Validator,
		Translator:  DefaultNopTranslate,
		Emitter:     emitter.DefaultCondEmitter,
		transaction: DefaultNopTransaction,
		request:     req,
		response:    res,
		echo:        e,
		pvalues:     make([]string, *e.maxParam),
		internal:    param.NewMap(),
		store:       make(Store),
		handler:     NotFoundHandler,
		funcs:       make(map[string]interface{}),
		sessioner:   DefaultSession,
		onHostFound: e.onHostFound,
	}
	c.cookier = NewCookier(c)
	c.dataEngine = NewData(c)
	return c
}

func (c *xContext) StdContext() context.Context {
	return c.request.Context()
}

func (c *xContext) WithContext(ctx context.Context) *http.Request {
	return c.request.WithContext(ctx)
}

func (c *xContext) SetValue(key string, value interface{}) {
	c.request.SetValue(key, value)
}

func (c *xContext) Internal() *param.SafeMap {
	return c.internal
}

func (c *xContext) SetEmitter(emitter events.Emitter) {
	c.Emitter = emitter
}

func (c *xContext) Handler() Handler {
	return c.handler
}

func (c *xContext) Deadline() (deadline time.Time, ok bool) {
	return c.StdContext().Deadline()
}

func (c *xContext) Done() <-chan struct{} {
	return c.StdContext().Done()
}

func (c *xContext) Err() error {
	return c.StdContext().Err()
}

func (c *xContext) Value(key interface{}) interface{} {
	return c.StdContext().Value(key)
}

func (c *xContext) Handle(ctx Context) error {
	return c.handler.Handle(ctx)
}

func (c *xContext) Route() *Route {
	if c.route == nil {
		if c.rid < 0 || c.rid >= len(c.echo.router.routes) {
			c.route = defaultRoute
		} else {
			c.route = c.echo.router.routes[c.rid]
		}
	}
	return c.route
}

func (c *xContext) SetAuto(on bool) Context {
	c.auto = on
	return c
}

// Error invokes the registered HTTP error handler. Generally used by middleware.
func (c *xContext) Error(err error) {
	c.echo.httpErrorHandler(err, c)
}

func (c *xContext) NewError(code pkgCode.Code, msg string, args ...interface{}) *Error {
	if len(msg) > 0 {
		msg = c.T(msg, args...)
	}
	return NewError(msg, code)
}

func (c *xContext) NewErrorWith(err error, code pkgCode.Code, args ...interface{}) *Error {
	var msg string
	if len(args) > 0 {
		msg = param.AsString(args[0])
		if len(msg) > 0 {
			if len(args) > 1 {
				msg = c.T(msg, args[1:]...)
			} else {
				msg = c.T(msg)
			}
		}
	}
	return NewErrorWith(err, msg, code)
}

// Logger returns the `Logger` instance.
func (c *xContext) Logger() logger.Logger {
	return c.echo.logger
}

// Object returns the `context` object.
func (c *xContext) Object() *xContext {
	return c
}

// Echo returns the `Echo` instance.
func (c *xContext) Echo() *Echo {
	return c.echo
}

func (c *xContext) SetTranslator(t Translator) {
	c.Translator = t
}

func (c *xContext) SetDefaultExtension(ext string) {
	c.defaultExtension = ext
}

func (c *xContext) DefaultExtension() string {
	if c.withFormatExtension {
		return `.` + c.Format()
	}
	if len(c.defaultExtension) > 0 {
		return c.defaultExtension
	}
	return c.echo.defaultExtension
}

func (c *xContext) Reset(req engine.Request, res engine.Response) {
	c.Validator = c.echo.Validator
	c.Emitter = emitter.DefaultCondEmitter
	c.Translator = DefaultNopTranslate
	c.transaction = DefaultNopTransaction
	c.sessioner = DefaultSession
	c.cookier = NewCookier(c)
	c.request = req
	c.response = res
	c.internal = param.NewMap()
	c.store = make(Store)
	c.path = ""
	c.pnames = nil
	c.hnames = nil
	c.hvalues = nil
	c.funcs = make(map[string]interface{})
	c.renderer = nil
	c.handler = NotFoundHandler
	c.route = nil
	c.rid = -1
	c.sessionOptions = nil
	c.withFormatExtension = false
	c.defaultExtension = ""
	c.format = ""
	c.code = 0
	c.auto = false
	c.preResponseHook = nil
	c.accept = nil
	c.dataEngine = NewData(c)
	c.onHostFound = c.echo.onHostFound
	// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
	for i := 0; i < *c.echo.maxParam; i++ {
		c.pvalues[i] = ""
	}
}

func (c *xContext) GetFunc(key string) interface{} {
	return c.funcs[key]
}

func (c *xContext) SetFunc(key string, val interface{}) {
	c.funcs[key] = val
}

func (c *xContext) ResetFuncs(funcs map[string]interface{}) {
	c.funcs = funcs
}

func (c *xContext) Funcs() map[string]interface{} {
	return c.funcs
}

func (c *xContext) Renderer() Renderer {
	if c.renderer != nil {
		return c.renderer
	}
	return c.echo.renderer
}

func (c *xContext) Fetch(name string, data interface{}) (b []byte, err error) {
	if c.renderer == nil {
		if c.echo.renderer == nil {
			return nil, ErrRendererNotRegistered
		}
		c.renderer = c.echo.renderer
	}
	buf := bufferpool.Get()
	defer bufferpool.Release(buf)
	err = c.renderer.Render(buf, name, data, c)
	if err != nil {
		return
	}
	b = buf.Bytes()[:]
	return
}

func (c *xContext) SetValidator(v Validator) {
	c.Validator = v
}

// SetRenderer registers an HTML template renderer.
func (c *xContext) SetRenderer(r Renderer) {
	c.renderer = r
}

func (c *xContext) SetSessioner(s Sessioner) {
	c.sessioner = s
}

func (c *xContext) Atop(v string) param.String {
	return param.String(v)
}

func (c *xContext) ToParamString(v string) param.String {
	return param.String(v)
}

func (c *xContext) ToStringSlice(v []string) param.StringSlice {
	return param.StringSlice(v)
}

func (c *xContext) SetFormat(format string) {
	c.format = format
}

func (c *xContext) WithFormatExtension(on bool) {
	c.withFormatExtension = on
}

func (c *xContext) SetCode(code int) {
	c.code = code
}

func (c *xContext) Code() int {
	return c.code
}

func (c *xContext) SetData(data Data) {
	c.dataEngine = data
}

func (c *xContext) Data() Data {
	return c.dataEngine
}

// MapData 映射数据到结构体
func (c *xContext) MapData(i interface{}, data map[string][]string, names ...string) error {
	var name string
	if len(names) > 0 {
		name = names[0]
	}
	return NamedStructMap(c.echo, i, data, name)
}

func (c *xContext) AddPreResponseHook(hook func() error) Context {
	if c.preResponseHook == nil {
		c.preResponseHook = []func() error{hook}
	} else {
		c.preResponseHook = append(c.preResponseHook, hook)
	}
	return c
}

func (c *xContext) SetPreResponseHook(hook ...func() error) Context {
	c.preResponseHook = hook
	return c
}

func (c *xContext) OnHostFound(onHostFound func(Context) (bool, error)) Context {
	c.onHostFound = onHostFound
	return c
}

func (c *xContext) FireHostFound() (bool, error) {
	if c.onHostFound == nil {
		return true, nil
	}
	return c.onHostFound(c)
}

func (c *xContext) preResponse() error {
	if c.preResponseHook == nil {
		c.cookier.Send()
		return nil
	}
	for _, hook := range c.preResponseHook {
		if err := hook(); err != nil {
			return err
		}
	}
	c.cookier.Send()
	return nil
}

func (c *xContext) PrintFuncs() {
	for key, fn := range c.Funcs() {
		fmt.Printf("[Template Func](%p) %-15s -> %s \n", fn, key, HandlerName(fn))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"os"

	"github.com/arschles/go-in-5-minutes/episode11/db"
	"github.com/arschles/go-in-5-minutes/episode11/handlers"
	"github.com/gorilla/mux"
)

func main() {
	conf, err := GetConfig()
	if err != nil {
		log.Printf("Error getting config [%s]", err)
		os.Exit(1)
	}

	router := mux.NewRouter()

	red := db.NewRedis()

	cah := handlers.NewCreateAppHandler(red)
	cah.RegisterRoute(router)

	gah := handlers.NewGetAppHandler(red)
	gah.RegisterRoute(router)

	dah := handlers.NewDeleteAppHandler(red)
	dah.RegisterRoute(router)

	portStr := fmt.Sprintf(":%d", conf.Port)
	log.Printf("Serving on %s", portStr)
	log.Fatal(http.ListenAndServe(portStr, router))
}
<commit_msg>fix build<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/arschles/go-in-5-minutes/episode11/db"
	"github.com/arschles/go-in-5-minutes/episode11/handlers"
	"github.com/gorilla/mux"
)

func main() {
	conf, err := GetConfig()
	if err != nil {
		log.Printf("Error getting config [%s]", err)
		os.Exit(1)
	}

	router := mux.NewRouter()

	red := db.NewRedis()

	cah := handlers.NewCreateAppHandler(red)
	cah.RegisterRoute(router)

	gah := handlers.NewGetAppHandler(red)
	gah.RegisterRoute(router)

	dah := handlers.NewDeleteAppHandler(red)
	dah.RegisterRoute(router)

	portStr := fmt.Sprintf(":%d", conf.Port)
	log.Printf("Serving on %s", portStr)
	log.Fatal(http.ListenAndServe(portStr, router))
}
<|endoftext|>