<commit_before>package melody

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

	"github.com/gorilla/websocket"
)

// Session wrapper around websocket connections.
type Session struct {
	Request    *http.Request
	Keys       map[string]interface{}
	conn       *websocket.Conn
	output     chan *envelope
	outputDone chan struct{}
	melody     *Melody
	open       bool
	rwmutex    *sync.RWMutex
}

func (s *Session) writeMessage(message *envelope) {
	if s.closed() {
		s.melody.errorHandler(s, ErrWriteClosed)
		return
	}

	select {
	case s.output <- message:
	default:
		s.melody.errorHandler(s, ErrMessageBufferFull)
	}
}

func (s *Session) writeRaw(message *envelope) error {
	if s.closed() {
		return ErrWriteClosed
	}

	s.conn.SetWriteDeadline(time.Now().Add(s.melody.Config.WriteWait))
	err := s.conn.WriteMessage(message.t, message.msg)

	if err != nil {
		return err
	}

	return nil
}

func (s *Session) closed() bool {
	s.rwmutex.RLock()
	defer s.rwmutex.RUnlock()

	return !s.open
}

func (s *Session) close() {
	s.rwmutex.Lock()
	open := s.open
	s.open = false
	s.rwmutex.Unlock()
	if open {
		s.conn.Close()
		close(s.outputDone)
	}
}

func (s *Session) ping() {
	s.writeRaw(&envelope{t: websocket.PingMessage, msg: []byte{}})
}

func (s *Session) writePump() {
	ticker := time.NewTicker(s.melody.Config.PingPeriod)
	defer ticker.Stop()

loop:
	for {
		select {
		case msg := <-s.output:
			err := s.writeRaw(msg)

			if err != nil {
				s.melody.errorHandler(s, err)
				break loop
			}

			if msg.t == websocket.CloseMessage {
				break loop
			}

			if msg.t == websocket.TextMessage {
				s.melody.messageSentHandler(s, msg.msg)
			}

			if msg.t == websocket.BinaryMessage {
				s.melody.messageSentHandlerBinary(s, msg.msg)
			}
		case <-ticker.C:
			s.ping()
		case _, ok := <-s.outputDone:
			if !ok {
				break loop
			}
		}
	}
}

func (s *Session) readPump() {
	s.conn.SetReadLimit(s.melody.Config.MaxMessageSize)
	s.conn.SetReadDeadline(time.Now().Add(s.melody.Config.PongWait))

	s.conn.SetPongHandler(func(string) error {
		s.conn.SetReadDeadline(time.Now().Add(s.melody.Config.PongWait))
		s.melody.pongHandler(s)
		return nil
	})

	if s.melody.closeHandler != nil {
		s.conn.SetCloseHandler(func(code int, text string) error {
			return s.melody.closeHandler(s, code, text)
		})
	}

	for {
		t, message, err := s.conn.ReadMessage()

		if err != nil {
			s.melody.errorHandler(s, err)
			break
		}

		if t == websocket.TextMessage {
			s.melody.messageHandler(s, message)
		}

		if t == websocket.BinaryMessage {
			s.melody.messageHandlerBinary(s, message)
		}
	}
}

// Write writes message to session.
func (s *Session) Write(msg []byte) error {
	if s.closed() {
		return ErrSessionClosed
	}

	s.writeMessage(&envelope{t: websocket.TextMessage, msg: msg})

	return nil
}

// WriteBinary writes a binary message to session.
func (s *Session) WriteBinary(msg []byte) error {
	if s.closed() {
		return ErrSessionClosed
	}

	s.writeMessage(&envelope{t: websocket.BinaryMessage, msg: msg})

	return nil
}

// Close closes session.
func (s *Session) Close() error {
	if s.closed() {
		return ErrSessionClosed
	}

	s.writeMessage(&envelope{t: websocket.CloseMessage, msg: []byte{}})

	return nil
}

// CloseWithMsg closes the session with the provided payload.
// Use the FormatCloseMessage function to format a proper close message payload.
func (s *Session) CloseWithMsg(msg []byte) error {
	if s.closed() {
		return ErrSessionClosed
	}

	s.writeMessage(&envelope{t: websocket.CloseMessage, msg: msg})

	return nil
}

// Set is used to store a new key/value pair exclusivelly for this session.
// It also lazy initializes s.Keys if it was not used previously.
func (s *Session) Set(key string, value interface{}) {
	if s.Keys == nil {
		s.Keys = make(map[string]interface{})
	}

	s.Keys[key] = value
}

// Get returns the value for the given key, ie: (value, true).
// If the value does not exists it returns (nil, false)
func (s *Session) Get(key string) (value interface{}, exists bool) {
	if s.Keys != nil {
		value, exists = s.Keys[key]
	}

	return
}

// MustGet returns the value for the given key if it exists, otherwise it panics.
func (s *Session) MustGet(key string) interface{} {
	if value, exists := s.Get(key); exists {
		return value
	}

	panic("Key \"" + key + "\" does not exist")
}

// IsClosed returns the status of the connection.
func (s *Session) IsClosed() bool {
	return s.closed()
}
<commit_msg>add local addr and remote addr for session<commit_after>package melody

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

	"github.com/gorilla/websocket"
)

// Session wrapper around websocket connections.
type Session struct {
	Request    *http.Request
	Keys       map[string]interface{}
	conn       *websocket.Conn
	output     chan *envelope
	outputDone chan struct{}
	melody     *Melody
	open       bool
	rwmutex    *sync.RWMutex
}

func (s *Session) writeMessage(message *envelope) {
	if s.closed() {
		s.melody.errorHandler(s, ErrWriteClosed)
		return
	}

	select {
	case s.output <- message:
	default:
		s.melody.errorHandler(s, ErrMessageBufferFull)
	}
}

func (s *Session) writeRaw(message *envelope) error {
	if s.closed() {
		return ErrWriteClosed
	}

	s.conn.SetWriteDeadline(time.Now().Add(s.melody.Config.WriteWait))
	err := s.conn.WriteMessage(message.t, message.msg)

	if err != nil {
		return err
	}

	return nil
}

func (s *Session) closed() bool {
	s.rwmutex.RLock()
	defer s.rwmutex.RUnlock()

	return !s.open
}

func (s *Session) close() {
	s.rwmutex.Lock()
	open := s.open
	s.open = false
	s.rwmutex.Unlock()
	if open {
		s.conn.Close()
		close(s.outputDone)
	}
}

func (s *Session) ping() {
	s.writeRaw(&envelope{t: websocket.PingMessage, msg: []byte{}})
}

func (s *Session) writePump() {
	ticker := time.NewTicker(s.melody.Config.PingPeriod)
	defer ticker.Stop()

loop:
	for {
		select {
		case msg := <-s.output:
			err := s.writeRaw(msg)

			if err != nil {
				s.melody.errorHandler(s, err)
				break loop
			}

			if msg.t == websocket.CloseMessage {
				break loop
			}

			if msg.t == websocket.TextMessage {
				s.melody.messageSentHandler(s, msg.msg)
			}

			if msg.t == websocket.BinaryMessage {
				s.melody.messageSentHandlerBinary(s, msg.msg)
			}
		case <-ticker.C:
			s.ping()
		case _, ok := <-s.outputDone:
			if !ok {
				break loop
			}
		}
	}
}

func (s *Session) readPump() {
	s.conn.SetReadLimit(s.melody.Config.MaxMessageSize)
	s.conn.SetReadDeadline(time.Now().Add(s.melody.Config.PongWait))

	s.conn.SetPongHandler(func(string) error {
		s.conn.SetReadDeadline(time.Now().Add(s.melody.Config.PongWait))
		s.melody.pongHandler(s)
		return nil
	})

	if s.melody.closeHandler != nil {
		s.conn.SetCloseHandler(func(code int, text string) error {
			return s.melody.closeHandler(s, code, text)
		})
	}

	for {
		t, message, err := s.conn.ReadMessage()

		if err != nil {
			s.melody.errorHandler(s, err)
			break
		}

		if t == websocket.TextMessage {
			s.melody.messageHandler(s, message)
		}

		if t == websocket.BinaryMessage {
			s.melody.messageHandlerBinary(s, message)
		}
	}
}

// Write writes message to session.
func (s *Session) Write(msg []byte) error {
	if s.closed() {
		return ErrSessionClosed
	}

	s.writeMessage(&envelope{t: websocket.TextMessage, msg: msg})

	return nil
}

// WriteBinary writes a binary message to session.
func (s *Session) WriteBinary(msg []byte) error {
	if s.closed() {
		return ErrSessionClosed
	}

	s.writeMessage(&envelope{t: websocket.BinaryMessage, msg: msg})

	return nil
}

// Close closes session.
func (s *Session) Close() error {
	if s.closed() {
		return ErrSessionClosed
	}

	s.writeMessage(&envelope{t: websocket.CloseMessage, msg: []byte{}})

	return nil
}

// CloseWithMsg closes the session with the provided payload.
// Use the FormatCloseMessage function to format a proper close message payload.
func (s *Session) CloseWithMsg(msg []byte) error {
	if s.closed() {
		return ErrSessionClosed
	}

	s.writeMessage(&envelope{t: websocket.CloseMessage, msg: msg})

	return nil
}

// Set is used to store a new key/value pair exclusivelly for this session.
// It also lazy initializes s.Keys if it was not used previously.
func (s *Session) Set(key string, value interface{}) {
	if s.Keys == nil {
		s.Keys = make(map[string]interface{})
	}

	s.Keys[key] = value
}

// Get returns the value for the given key, ie: (value, true).
// If the value does not exists it returns (nil, false)
func (s *Session) Get(key string) (value interface{}, exists bool) {
	if s.Keys != nil {
		value, exists = s.Keys[key]
	}

	return
}

// MustGet returns the value for the given key if it exists, otherwise it panics.
func (s *Session) MustGet(key string) interface{} {
	if value, exists := s.Get(key); exists {
		return value
	}

	panic("Key \"" + key + "\" does not exist")
}

// IsClosed returns the status of the connection.
func (s *Session) IsClosed() bool {
	return s.closed()
}

// LocalAddr returns the local addr of the connection.
func (s *session) LocalAddr() net.Addr {
	return conn.LocalAddr()
}

// RemoteAddr returns the remote addr of the connection.
func (s *session) RemoteAddr() net.Addr {
	return conn.RemoteAddr()
}
<|endoftext|>


<commit_before>package Golf

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"sync"
	"time"
)

const sessionIDLength = 64
const sessionExpireTime = 3600
const gcTimeInterval = 30

// SessionManager manages a map of sessions.
type SessionManager interface {
	sessionID() (string, error)
	NewSession() (Session, error)
	Session(string) (Session, error)
	GarbageCollection()
}

// MemorySessionManager is a implementation of Session Manager, which stores data in memory.
type MemorySessionManager struct {
	sessions map[string]*MemorySession
	lock     sync.RWMutex
}

// NewMemorySessionManager creates a new session manager.
func NewMemorySessionManager() *MemorySessionManager {
	mgr := new(MemorySessionManager)
	mgr.sessions = make(map[string]*MemorySession)
	mgr.GarbageCollection()
	return mgr
}

func (mgr *MemorySessionManager) sessionID() (string, error) {
	b := make([]byte, sessionIDLength)
	n, err := rand.Read(b)
	if n != len(b) || err != nil {
		return "", fmt.Errorf("Could not successfully read from the system CSPRNG.")
	}
	return hex.EncodeToString(b), nil
}

// Session gets the session instance by indicating a session id.
func (mgr *MemorySessionManager) Session(sid string) (Session, error) {
	mgr.lock.RLock()
	if s, ok := mgr.sessions[sid]; ok {
		s.createdAt = time.Now()
		mgr.lock.RUnlock()
		return s, nil
	}
	mgr.lock.RUnlock()
	return nil, fmt.Errorf("Can not retrieve session with id %s.", sid)
}

// NewSession creates and returns a new session.
func (mgr *MemorySessionManager) NewSession() (Session, error) {
	sid, err := mgr.sessionID()
	if err != nil {
		return nil, err
	}
	s := MemorySession{sid: sid, data: make(map[string]interface{}), createdAt: time.Now()}
	mgr.lock.Lock()
	mgr.sessions[sid] = &s
	mgr.lock.Unlock()
	return &s, nil
}

// GarbageCollection recycles expired sessions, delete them from the session manager.
func (mgr *MemorySessionManager) GarbageCollection() {
	for k, v := range mgr.sessions {
		if v.isExpired() {
			delete(mgr.sessions, k)
		}
	}
	time.AfterFunc(time.Duration(gcTimeInterval)*time.Second, mgr.GarbageCollection)
}

// Session is an interface for session instance, a session instance contains
// data needed.
type Session interface {
	Set(key string, value interface{}) error
	Get(key string) (interface{}, error)
	Delete(key string) error
	SessionID() string
	isExpired() bool
}

// MemorySession is an memory based implementaion of Session.
type MemorySession struct {
	sid       string
	data      map[string]interface{}
	createdAt time.Time
}

func (s *MemorySession) isExpired() bool {
	return (s.createdAt.Unix() + sessionExpireTime) <= time.Now().Unix()
}

// Set method sets the key value pair in the session.
func (s *MemorySession) Set(key string, value interface{}) error {
	s.data[key] = value
	return nil
}

// Get method gets the value by given a key in the session.
func (s *MemorySession) Get(key string) (interface{}, error) {
	if value, ok := s.data[key]; ok {
		return value, nil
	}
	return nil, fmt.Errorf("key %q in session (id %s) not found", key, s.sid)
}

// Delete method deletes the value by given a key in the session.
func (s *MemorySession) Delete(key string) error {
	delete(s.data, key)
	return nil
}

// SessionID returns the current ID of the session.
func (s *MemorySession) SessionID() string {
	return s.sid
}
<commit_msg>[feat] Add MemorySessionManager.Count<commit_after>package Golf

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"sync"
	"time"
)

const sessionIDLength = 64
const sessionExpireTime = 3600
const gcTimeInterval = 30

// SessionManager manages a map of sessions.
type SessionManager interface {
	sessionID() (string, error)
	NewSession() (Session, error)
	Session(string) (Session, error)
	GarbageCollection()
}

// MemorySessionManager is a implementation of Session Manager, which stores data in memory.
type MemorySessionManager struct {
	sessions map[string]*MemorySession
	lock     sync.RWMutex
}

// NewMemorySessionManager creates a new session manager.
func NewMemorySessionManager() *MemorySessionManager {
	mgr := new(MemorySessionManager)
	mgr.sessions = make(map[string]*MemorySession)
	mgr.GarbageCollection()
	return mgr
}

func (mgr *MemorySessionManager) sessionID() (string, error) {
	b := make([]byte, sessionIDLength)
	n, err := rand.Read(b)
	if n != len(b) || err != nil {
		return "", fmt.Errorf("Could not successfully read from the system CSPRNG.")
	}
	return hex.EncodeToString(b), nil
}

// Session gets the session instance by indicating a session id.
func (mgr *MemorySessionManager) Session(sid string) (Session, error) {
	mgr.lock.RLock()
	if s, ok := mgr.sessions[sid]; ok {
		s.createdAt = time.Now()
		mgr.lock.RUnlock()
		return s, nil
	}
	mgr.lock.RUnlock()
	return nil, fmt.Errorf("Can not retrieve session with id %s.", sid)
}

// NewSession creates and returns a new session.
func (mgr *MemorySessionManager) NewSession() (Session, error) {
	sid, err := mgr.sessionID()
	if err != nil {
		return nil, err
	}
	s := MemorySession{sid: sid, data: make(map[string]interface{}), createdAt: time.Now()}
	mgr.lock.Lock()
	mgr.sessions[sid] = &s
	mgr.lock.Unlock()
	return &s, nil
}

// GarbageCollection recycles expired sessions, delete them from the session manager.
func (mgr *MemorySessionManager) GarbageCollection() {
	for k, v := range mgr.sessions {
		if v.isExpired() {
			delete(mgr.sessions, k)
		}
	}
	time.AfterFunc(time.Duration(gcTimeInterval)*time.Second, mgr.GarbageCollection)
}

func (mgr *MemorySessionManager) Count() int {
	return len(mgr.sessions)
}

// Session is an interface for session instance, a session instance contains
// data needed.
type Session interface {
	Set(key string, value interface{}) error
	Get(key string) (interface{}, error)
	Delete(key string) error
	SessionID() string
	isExpired() bool
}

// MemorySession is an memory based implementaion of Session.
type MemorySession struct {
	sid       string
	data      map[string]interface{}
	createdAt time.Time
}

func (s *MemorySession) isExpired() bool {
	return (s.createdAt.Unix() + sessionExpireTime) <= time.Now().Unix()
}

// Set method sets the key value pair in the session.
func (s *MemorySession) Set(key string, value interface{}) error {
	s.data[key] = value
	return nil
}

// Get method gets the value by given a key in the session.
func (s *MemorySession) Get(key string) (interface{}, error) {
	if value, ok := s.data[key]; ok {
		return value, nil
	}
	return nil, fmt.Errorf("key %q in session (id %s) not found", key, s.sid)
}

// Delete method deletes the value by given a key in the session.
func (s *MemorySession) Delete(key string) error {
	delete(s.data, key)
	return nil
}

// SessionID returns the current ID of the session.
func (s *MemorySession) SessionID() string {
	return s.sid
}
<|endoftext|>


<commit_before>package jwp

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
)

type Client struct {
	Server       string
	SessionID    string
	Capabilities ApiCapabilities
}

func (s *Client) NewSession(dc *ApiDesiredCapabilities) (err error) {
	var (
		req  *http.Request
		res  *http.Response
		body []byte
		buf  = new(bytes.Buffer)
		resp ApiSession
	)

	if dc.DesiredCapabilities.Proxy.ProxyType == "" {
		dc.DesiredCapabilities.Proxy.ProxyType = "dirct"
	}

	err = json.NewEncoder(buf).Encode(dc)
	if err != nil {
		return
	}

	req, err = http.NewRequest("POST", s.Server+"/session", buf)
	if err != nil {
		return
	}

	res, err = http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}

	if err = CheckHTTPStatus(res, body); err != nil {
		return
	}

	json.Unmarshal(body, &resp)
	if err = CheckStatus(resp.Status); err != nil {
		return
	}

	s.SessionID = resp.SessionId
	s.Capabilities = resp.Value
	return
}

func (s *Client) ListSessions() (resp *ApiSessions, err error) {
	var (
		req  *http.Request
		res  *http.Response
		body []byte
	)

	req, err = http.NewRequest("GET", s.Server+"/sessions", nil)
	if err != nil {
		return
	}

	res, err = http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}

	if err = CheckHTTPStatus(res, body); err != nil {
		return
	}

	json.Unmarshal(body, &resp)
	if err = CheckStatus(resp.Status); err != nil {
		return
	}
	return
}

func (s *Client) GetCapabilities() (caps *ApiCapabilities, err error) {
	var (
		req  *http.Request
		res  *http.Response
		body []byte
		resp ApiSession
	)

	req, err = http.NewRequest("GET", s.Server+"/session/"+s.SessionID, nil)
	if err != nil {
		return
	}

	res, err = http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}

	if err = CheckHTTPStatus(res, body); err != nil {
		return
	}

	json.Unmarshal(body, &resp)
	if err = CheckStatus(resp.Status); err != nil {
		return
	}

	s.Capabilities = resp.Value
	caps = &s.Capabilities

	return
}

func (c *Client) Delete() (err error) {
	var (
		req  *http.Request
		res  *http.Response
		body []byte
		resp ApiMeta
	)

	req, err = http.NewRequest("DELETE", c.Server+"/session/"+c.SessionID, nil)
	if err != nil {
		return
	}

	res, err = http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}

	if err = CheckHTTPStatus(res, body); err != nil {
		return
	}

	err = json.Unmarshal(body, &resp)
	if err != nil {
		return
	}
	if err = CheckStatus(resp.Status); err != nil {
		return
	}
	return
}
<commit_msg>clear.<commit_after>package jwp

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
)

type Client struct {
	Server       string
	SessionID    string
	Capabilities ApiCapabilities
}

func (s *Client) NewSession(dc *ApiDesiredCapabilities) (err error) {
	var (
		req  *http.Request
		res  *http.Response
		body []byte
		buf  = new(bytes.Buffer)
		resp ApiSession
	)

	if dc.DesiredCapabilities.Proxy.ProxyType == "" {
		dc.DesiredCapabilities.Proxy.ProxyType = "dirct"
	}

	err = json.NewEncoder(buf).Encode(dc)
	if err != nil {
		return
	}

	req, err = http.NewRequest("POST", s.Server+"/session", buf)
	if err != nil {
		return
	}

	res, err = http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}

	if err = CheckHTTPStatus(res, body); err != nil {
		return
	}

	json.Unmarshal(body, &resp)
	if err = CheckStatus(resp.Status); err != nil {
		return
	}

	s.SessionID = resp.SessionId
	s.Capabilities = resp.Value
	return
}

func (s *Client) ListSessions() (resp *ApiSessions, err error) {
	var (
		req  *http.Request
		res  *http.Response
		body []byte
	)

	req, err = http.NewRequest("GET", s.Server+"/sessions", nil)
	if err != nil {
		return
	}

	res, err = http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}

	if err = CheckHTTPStatus(res, body); err != nil {
		return
	}

	json.Unmarshal(body, &resp)
	if err = CheckStatus(resp.Status); err != nil {
		return
	}
	return
}

func (c *Client) GetCapabilities() (caps *ApiCapabilities, err error) {
	var (
		req  *http.Request
		res  *http.Response
		body []byte
		resp ApiSession
	)

	req, err = http.NewRequest("GET", c.Server+"/session/"+c.SessionID, nil)
	if err != nil {
		return
	}

	res, err = http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}

	if err = CheckHTTPStatus(res, body); err != nil {
		return
	}

	json.Unmarshal(body, &resp)
	if err = CheckStatus(resp.Status); err != nil {
		return
	}

	c.Capabilities = resp.Value
	caps = &c.Capabilities

	return
}

func (c *Client) Delete() (err error) {
	var (
		req  *http.Request
		res  *http.Response
		body []byte
		resp ApiMeta
	)

	req, err = http.NewRequest("DELETE", c.Server+"/session/"+c.SessionID, nil)
	if err != nil {
		return
	}

	res, err = http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}

	if err = CheckHTTPStatus(res, body); err != nil {
		return
	}

	err = json.Unmarshal(body, &resp)
	if err != nil {
		return
	}
	if err = CheckStatus(resp.Status); err != nil {
		return
	}
	return
}
<|endoftext|>


<commit_before>package ssh

import (
	"bytes"
	"fmt"
	"net"

	"github.com/anmitsu/go-shlex"
	gossh "golang.org/x/crypto/ssh"
)

// Session provides access to information about an SSH session and methods
// to read and write to the SSH channel with an embedded Channel interface from
// cypto/ssh.
//
// When Command() returns an empty slice, the user requested a shell. Otherwise
// the user is performing an exec with those command arguments.
//
// TODO: Signals
type Session interface {
	gossh.Channel

	// User returns the username used when establishing the SSH connection.
	User() string

	// RemoteAddr returns the net.Addr of the client side of the connection.
	RemoteAddr() net.Addr

	// Environ returns a copy of strings representing the environment set by the
	// user for this session, in the form "key=value".
	Environ() []string

	// Exit sends an exit status and then closes the session.
	Exit(code int) error

	// Command returns a shell parsed slice of arguments that were provided by the
	// user. Shell parsing splits the command string according to POSIX shell rules,
	// which considers quoting not just whitespace.
	Command() []string

	// PublicKey returns the PublicKey used to authenticate. If a public key was not
	// used it will return nil.
	PublicKey() PublicKey

	// Pty returns PTY information, a channel of window size changes, and a boolean
	// of whether or not a PTY was accepted for this session.
	Pty() (Pty, <-chan Window, bool)

	// TODO: Signals(c chan<- Signal)
}

type session struct {
	gossh.Channel
	conn    *gossh.ServerConn
	handler Handler
	handled bool
	pty     *Pty
	winch   chan Window
	env     []string
	ptyCb   PtyCallback
	cmd     []string
}

func (sess *session) Write(p []byte) (n int, err error) {
	if sess.pty != nil {
		// normalize \n to \r\n when pty is accepted
		p = bytes.Replace(p, []byte{'\n'}, []byte{'\r', '\n'}, -1)
		p = bytes.Replace(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1)
	}
	return sess.Channel.Write(p)
}

func (sess *session) PublicKey() PublicKey {
	if sess.conn.Permissions == nil {
		return nil
	}
	s, ok := sess.conn.Permissions.Extensions["_publickey"]
	if !ok {
		return nil
	}
	key, err := ParsePublicKey([]byte(s))
	if err != nil {
		return nil
	}
	return key
}

func (sess *session) Exit(code int) error {
	status := struct{ Status uint32 }{uint32(code)}
	_, err := sess.SendRequest("exit-status", false, gossh.Marshal(&status))
	if err != nil {
		return err
	}
	return sess.Close()
}

func (sess *session) User() string {
	return sess.conn.User()
}

func (sess *session) RemoteAddr() net.Addr {
	return sess.conn.RemoteAddr()
}

func (sess *session) Environ() []string {
	return append([]string(nil), sess.env...)
}

func (sess *session) Command() []string {
	return append([]string(nil), sess.cmd...)
}

func (sess *session) Pty() (Pty, <-chan Window, bool) {
	if sess.pty != nil {
		return *sess.pty, sess.winch, true
	}
	return Pty{}, sess.winch, false
}

func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
	for req := range reqs {
		var width, height int
		var ok bool
		switch req.Type {
		case "shell", "exec":
			if sess.handled {
				req.Reply(false, nil)
				continue
			}
			var payload = struct{ Value string }{}
			gossh.Unmarshal(req.Payload, &payload)
			sess.cmd, _ = shlex.Split(payload.Value, true)
			go func() {
				sess.handler(sess)
				sess.Exit(0)
			}()
			sess.handled = true
			req.Reply(true, nil)
		case "env":
			if sess.handled {
				req.Reply(false, nil)
				continue
			}
			var kv = struct{ Key, Value string }{}
			gossh.Unmarshal(req.Payload, &kv)
			sess.env = append(sess.env, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
		case "pty-req":
			if sess.handled {
				req.Reply(false, nil)
				continue
			}
			if sess.ptyCb != nil {
				ok := sess.ptyCb(sess.conn.User(), &Permissions{sess.conn.Permissions})
				if !ok {
					req.Reply(false, nil)
					continue
				}
			}
			width, height, ok = parsePtyRequest(req.Payload)
			if ok {
				sess.pty = &Pty{Window{width, height}}
				sess.winch = make(chan Window)
				req.Reply(true, nil)
			}
		case "window-change":
			if sess.pty == nil {
				req.Reply(false, nil)
				continue
			}
			width, height, ok = parseWinchRequest(req.Payload)
			if ok {
				sess.pty.Window = Window{width, height}
				sess.winch <- sess.pty.Window
			}
		}
	}
}
<commit_msg>Send the exec/shell reply before starting the session<commit_after>package ssh

import (
	"bytes"
	"fmt"
	"net"

	"github.com/anmitsu/go-shlex"
	gossh "golang.org/x/crypto/ssh"
)

// Session provides access to information about an SSH session and methods
// to read and write to the SSH channel with an embedded Channel interface from
// cypto/ssh.
//
// When Command() returns an empty slice, the user requested a shell. Otherwise
// the user is performing an exec with those command arguments.
//
// TODO: Signals
type Session interface {
	gossh.Channel

	// User returns the username used when establishing the SSH connection.
	User() string

	// RemoteAddr returns the net.Addr of the client side of the connection.
	RemoteAddr() net.Addr

	// Environ returns a copy of strings representing the environment set by the
	// user for this session, in the form "key=value".
	Environ() []string

	// Exit sends an exit status and then closes the session.
	Exit(code int) error

	// Command returns a shell parsed slice of arguments that were provided by the
	// user. Shell parsing splits the command string according to POSIX shell rules,
	// which considers quoting not just whitespace.
	Command() []string

	// PublicKey returns the PublicKey used to authenticate. If a public key was not
	// used it will return nil.
	PublicKey() PublicKey

	// Pty returns PTY information, a channel of window size changes, and a boolean
	// of whether or not a PTY was accepted for this session.
	Pty() (Pty, <-chan Window, bool)

	// TODO: Signals(c chan<- Signal)
}

type session struct {
	gossh.Channel
	conn    *gossh.ServerConn
	handler Handler
	handled bool
	pty     *Pty
	winch   chan Window
	env     []string
	ptyCb   PtyCallback
	cmd     []string
}

func (sess *session) Write(p []byte) (n int, err error) {
	if sess.pty != nil {
		// normalize \n to \r\n when pty is accepted
		p = bytes.Replace(p, []byte{'\n'}, []byte{'\r', '\n'}, -1)
		p = bytes.Replace(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1)
	}
	return sess.Channel.Write(p)
}

func (sess *session) PublicKey() PublicKey {
	if sess.conn.Permissions == nil {
		return nil
	}
	s, ok := sess.conn.Permissions.Extensions["_publickey"]
	if !ok {
		return nil
	}
	key, err := ParsePublicKey([]byte(s))
	if err != nil {
		return nil
	}
	return key
}

func (sess *session) Exit(code int) error {
	status := struct{ Status uint32 }{uint32(code)}
	_, err := sess.SendRequest("exit-status", false, gossh.Marshal(&status))
	if err != nil {
		return err
	}
	return sess.Close()
}

func (sess *session) User() string {
	return sess.conn.User()
}

func (sess *session) RemoteAddr() net.Addr {
	return sess.conn.RemoteAddr()
}

func (sess *session) Environ() []string {
	return append([]string(nil), sess.env...)
}

func (sess *session) Command() []string {
	return append([]string(nil), sess.cmd...)
}

func (sess *session) Pty() (Pty, <-chan Window, bool) {
	if sess.pty != nil {
		return *sess.pty, sess.winch, true
	}
	return Pty{}, sess.winch, false
}

func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
	for req := range reqs {
		var width, height int
		var ok bool
		switch req.Type {
		case "shell", "exec":
			if sess.handled {
				req.Reply(false, nil)
				continue
			}
			sess.handled = true
			req.Reply(true, nil)

			var payload = struct{ Value string }{}
			gossh.Unmarshal(req.Payload, &payload)
			sess.cmd, _ = shlex.Split(payload.Value, true)
			go func() {
				sess.handler(sess)
				sess.Exit(0)
			}()
		case "env":
			if sess.handled {
				req.Reply(false, nil)
				continue
			}
			var kv = struct{ Key, Value string }{}
			gossh.Unmarshal(req.Payload, &kv)
			sess.env = append(sess.env, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
		case "pty-req":
			if sess.handled {
				req.Reply(false, nil)
				continue
			}
			if sess.ptyCb != nil {
				ok := sess.ptyCb(sess.conn.User(), &Permissions{sess.conn.Permissions})
				if !ok {
					req.Reply(false, nil)
					continue
				}
			}
			width, height, ok = parsePtyRequest(req.Payload)
			if ok {
				sess.pty = &Pty{Window{width, height}}
				sess.winch = make(chan Window)
				req.Reply(true, nil)
			}
		case "window-change":
			if sess.pty == nil {
				req.Reply(false, nil)
				continue
			}
			width, height, ok = parseWinchRequest(req.Payload)
			if ok {
				sess.pty.Window = Window{width, height}
				sess.winch <- sess.pty.Window
			}
		}
	}
}
<|endoftext|>


<commit_before>package rethinkdb

import (
	"crypto/tls"
	"sync"
	"time"

	"golang.org/x/net/context"
	p "gopkg.in/rethinkdb/rethinkdb-go.v5/ql2"
)

// A Session represents a connection to a RethinkDB cluster and should be used
// when executing queries.
type Session struct {
	hosts []Host
	opts  *ConnectOpts

	mu      sync.RWMutex
	cluster *Cluster
	closed  bool
}

// ConnectOpts is used to specify optional arguments when connecting to a cluster.
type ConnectOpts struct {
	// Address holds the address of the server initially used when creating the
	// session. Only used if Addresses is empty
	Address string `rethinkdb:"address,omitempty"`
	// Addresses holds the addresses of the servers initially used when creating
	// the session.
	Addresses []string `rethinkdb:"addresses,omitempty"`
	// Database is the default database name used when executing queries, this
	// value is only used if the query does not contain any DB term
	Database string `rethinkdb:"database,omitempty"`
	// Username holds the username used for authentication, if blank (and the v1
	// handshake protocol is being used) then the admin user is used
	Username string `rethinkdb:"username,omitempty"`
	// Password holds the password used for authentication (only used when using
	// the v1 handshake protocol)
	Password string `rethinkdb:"password,omitempty"`
	// AuthKey is used for authentication when using the v0.4 handshake protocol
	// This field is no deprecated
	AuthKey string `rethinkdb:"authkey,omitempty"`
	// Timeout is the time the driver waits when creating new connections, to
	// configure the timeout used when executing queries use WriteTimeout and
	// ReadTimeout
	Timeout time.Duration `rethinkdb:"timeout,omitempty"`
	// WriteTimeout is the amount of time the driver will wait when sending the
	// query to the server
	WriteTimeout time.Duration `rethinkdb:"write_timeout,omitempty"`
	// ReadTimeout is the amount of time the driver will wait for a response from
	// the server when executing queries.
	ReadTimeout time.Duration `rethinkdb:"read_timeout,omitempty"`
	// KeepAlivePeriod is the keep alive period used by the connection, by default
	// this is 30s. It is not possible to disable keep alive messages
	KeepAlivePeriod time.Duration `rethinkdb:"keep_alive_timeout,omitempty"`
	// TLSConfig holds the TLS configuration and can be used when connecting
	// to a RethinkDB server protected by SSL
	TLSConfig *tls.Config `rethinkdb:"tlsconfig,omitempty"`
	// HandshakeVersion is used to specify which handshake version should be
	// used, this currently defaults to v1 which is used by RethinkDB 2.3 and
	// later. If you are using an older version then you can set the handshake
	// version to 0.4
	HandshakeVersion HandshakeVersion `rethinkdb:"handshake_version,omitempty"`
	// UseJSONNumber indicates whether the cursors running in this session should
	// use json.Number instead of float64 while unmarshaling documents with
	// interface{}. The default is `false`.
	UseJSONNumber bool
	// NumRetries is the number of times a query is retried if a connection
	// error is detected, queries are not retried if RethinkDB returns a
	// runtime error.
	NumRetries int

	// InitialCap is used by the internal connection pool and is used to
	// configure how many connections are created for each host when the
	// session is created. If zero then no connections are created until
	// the first query is executed.
	InitialCap int `rethinkdb:"initial_cap,omitempty"`
	// MaxOpen is used by the internal connection pool and is used to configure
	// the maximum number of connections held in the pool. If all available
	// connections are being used then the driver will open new connections as
	// needed however they will not be returned to the pool. By default the
	// maximum number of connections is 2
	MaxOpen int `rethinkdb:"max_open,omitempty"`

	// Below options are for cluster discovery, please note there is a high
	// probability of these changing as the API is still being worked on.

	// DiscoverHosts is used to enable host discovery, when true the driver
	// will attempt to discover any new nodes added to the cluster and then
	// start sending queries to these new nodes.
	DiscoverHosts bool `rethinkdb:"discover_hosts,omitempty"`
	// HostDecayDuration is used by the go-hostpool package to calculate a weighted
	// score when selecting a host. By default a value of 5 minutes is used.
	HostDecayDuration time.Duration

	// UseOpentracing is used to enable creating opentracing-go spans for queries.
	// Each span is created as child of span from the context in `RunOpts`.
	// This span lasts from point the query created to the point when cursor closed.
	UseOpentracing bool

	// Deprecated: This function is no longer used due to changes in the
	// way hosts are selected.
	NodeRefreshInterval time.Duration `rethinkdb:"node_refresh_interval,omitempty"`
	// Deprecated: Use InitialCap instead
	MaxIdle int `rethinkdb:"max_idle,omitempty"`
}

func (o ConnectOpts) toMap() map[string]interface{} {
	return optArgsToMap(o)
}

// Connect creates a new database session. To view the available connection
// options see ConnectOpts.
//
// By default maxIdle and maxOpen are set to 1: passing values greater
// than the default (e.g. MaxIdle: "10", MaxOpen: "20") will provide a
// pool of re-usable connections.
//
// Basic connection example:
//
// 	session, err := r.Connect(r.ConnectOpts{
// 		Host: "localhost:28015",
// 		Database: "test",
// 		AuthKey:  "14daak1cad13dj",
// 	})
//
// Cluster connection example:
//
// 	session, err := r.Connect(r.ConnectOpts{
// 		Hosts: []string{"localhost:28015", "localhost:28016"},
// 		Database: "test",
// 		AuthKey:  "14daak1cad13dj",
// 	})
func Connect(opts ConnectOpts) (*Session, error) {
	var addresses = opts.Addresses
	if len(addresses) == 0 {
		addresses = []string{opts.Address}
	}

	hosts := make([]Host, len(addresses))
	for i, address := range addresses {
		hostname, port := splitAddress(address)
		hosts[i] = NewHost(hostname, port)
	}
	if len(hosts) <= 0 {
		return nil, ErrNoHosts
	}

	// Connect
	s := &Session{
		hosts: hosts,
		opts:  &opts,
	}

	err := s.Reconnect()
	if err != nil {
		// note: s.Reconnect() will initialize cluster information which
		// will cause the .IsConnected() method to be caught in a loop
		return &Session{
			hosts: hosts,
			opts:  &opts,
		}, err
	}

	return s, nil
}

// CloseOpts allows calls to the Close function to be configured.
type CloseOpts struct {
	NoReplyWait bool `rethinkdb:"noreplyWait,omitempty"`
}

func (o CloseOpts) toMap() map[string]interface{} {
	return optArgsToMap(o)
}

// IsConnected returns true if session has a valid connection.
func (s *Session) IsConnected() bool {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.cluster == nil || s.closed {
		return false
	}
	return s.cluster.IsConnected()
}

// Reconnect closes and re-opens a session.
func (s *Session) Reconnect(optArgs ...CloseOpts) error {
	var err error

	if err = s.Close(optArgs...); err != nil {
		return err
	}

	s.mu.Lock()
	s.cluster, err = NewCluster(s.hosts, s.opts)
	if err != nil {
		s.mu.Unlock()
		return err
	}

	s.closed = false
	s.mu.Unlock()

	return nil
}

// Close closes the session
func (s *Session) Close(optArgs ...CloseOpts) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return nil
	}

	if len(optArgs) >= 1 {
		if optArgs[0].NoReplyWait {
			s.mu.Unlock()
			s.NoReplyWait()
			s.mu.Lock()
		}
	}

	if s.cluster != nil {
		return s.cluster.Close()
	}
	s.cluster = nil
	s.closed = true

	return nil
}

// SetInitialPoolCap sets the initial capacity of the connection pool.
func (s *Session) SetInitialPoolCap(n int) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.opts.InitialCap = n
	s.cluster.SetInitialPoolCap(n)
}

// SetMaxIdleConns sets the maximum number of connections in the idle
// connection pool.
func (s *Session) SetMaxIdleConns(n int) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.opts.MaxIdle = n
	s.cluster.SetMaxIdleConns(n)
}

// SetMaxOpenConns sets the maximum number of open connections to the database.
func (s *Session) SetMaxOpenConns(n int) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.opts.MaxOpen = n
	s.cluster.SetMaxOpenConns(n)
}

// NoReplyWait ensures that previous queries with the noreply flag have been
// processed by the server. Note that this guarantee only applies to queries
// run on the given connection
func (s *Session) NoReplyWait() error {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return ErrConnectionClosed
	}

	return s.cluster.Exec(nil, Query{ // nil = connection opts' defaults
		Type: p.Query_NOREPLY_WAIT,
	})
}

// Use changes the default database used
func (s *Session) Use(database string) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.opts.Database = database
}

// Database returns the selected database set by Use
func (s *Session) Database() string {
	s.mu.RLock()
	defer s.mu.RUnlock()

	return s.opts.Database
}

// Query executes a ReQL query using the session to connect to the database
func (s *Session) Query(ctx context.Context, q Query) (*Cursor, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return nil, ErrConnectionClosed
	}

	return s.cluster.Query(ctx, q)
}

// Exec executes a ReQL query using the session to connect to the database
func (s *Session) Exec(ctx context.Context, q Query) error {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return ErrConnectionClosed
	}

	return s.cluster.Exec(ctx, q)
}

// Server returns the server name and server UUID being used by a connection.
func (s *Session) Server() (ServerResponse, error) {
	return s.cluster.Server()
}

// SetHosts resets the hosts used when connecting to the RethinkDB cluster
func (s *Session) SetHosts(hosts []Host) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.hosts = hosts
}

func (s *Session) newQuery(t Term, opts map[string]interface{}) (Query, error) {
	return newQuery(t, opts, s.opts)
}
<commit_msg>fix Connect doc<commit_after>package rethinkdb

import (
	"crypto/tls"
	"sync"
	"time"

	"golang.org/x/net/context"
	p "gopkg.in/rethinkdb/rethinkdb-go.v5/ql2"
)

// A Session represents a connection to a RethinkDB cluster and should be used
// when executing queries.
type Session struct {
	hosts []Host
	opts  *ConnectOpts

	mu      sync.RWMutex
	cluster *Cluster
	closed  bool
}

// ConnectOpts is used to specify optional arguments when connecting to a cluster.
type ConnectOpts struct {
	// Address holds the address of the server initially used when creating the
	// session. Only used if Addresses is empty
	Address string `rethinkdb:"address,omitempty"`
	// Addresses holds the addresses of the servers initially used when creating
	// the session.
	Addresses []string `rethinkdb:"addresses,omitempty"`
	// Database is the default database name used when executing queries, this
	// value is only used if the query does not contain any DB term
	Database string `rethinkdb:"database,omitempty"`
	// Username holds the username used for authentication, if blank (and the v1
	// handshake protocol is being used) then the admin user is used
	Username string `rethinkdb:"username,omitempty"`
	// Password holds the password used for authentication (only used when using
	// the v1 handshake protocol)
	Password string `rethinkdb:"password,omitempty"`
	// AuthKey is used for authentication when using the v0.4 handshake protocol
	// This field is no deprecated
	AuthKey string `rethinkdb:"authkey,omitempty"`
	// Timeout is the time the driver waits when creating new connections, to
	// configure the timeout used when executing queries use WriteTimeout and
	// ReadTimeout
	Timeout time.Duration `rethinkdb:"timeout,omitempty"`
	// WriteTimeout is the amount of time the driver will wait when sending the
	// query to the server
	WriteTimeout time.Duration `rethinkdb:"write_timeout,omitempty"`
	// ReadTimeout is the amount of time the driver will wait for a response from
	// the server when executing queries.
	ReadTimeout time.Duration `rethinkdb:"read_timeout,omitempty"`
	// KeepAlivePeriod is the keep alive period used by the connection, by default
	// this is 30s. It is not possible to disable keep alive messages
	KeepAlivePeriod time.Duration `rethinkdb:"keep_alive_timeout,omitempty"`
	// TLSConfig holds the TLS configuration and can be used when connecting
	// to a RethinkDB server protected by SSL
	TLSConfig *tls.Config `rethinkdb:"tlsconfig,omitempty"`
	// HandshakeVersion is used to specify which handshake version should be
	// used, this currently defaults to v1 which is used by RethinkDB 2.3 and
	// later. If you are using an older version then you can set the handshake
	// version to 0.4
	HandshakeVersion HandshakeVersion `rethinkdb:"handshake_version,omitempty"`
	// UseJSONNumber indicates whether the cursors running in this session should
	// use json.Number instead of float64 while unmarshaling documents with
	// interface{}. The default is `false`.
	UseJSONNumber bool
	// NumRetries is the number of times a query is retried if a connection
	// error is detected, queries are not retried if RethinkDB returns a
	// runtime error.
	NumRetries int

	// InitialCap is used by the internal connection pool and is used to
	// configure how many connections are created for each host when the
	// session is created. If zero then no connections are created until
	// the first query is executed.
	InitialCap int `rethinkdb:"initial_cap,omitempty"`
	// MaxOpen is used by the internal connection pool and is used to configure
	// the maximum number of connections held in the pool. If all available
	// connections are being used then the driver will open new connections as
	// needed however they will not be returned to the pool. By default the
	// maximum number of connections is 2
	MaxOpen int `rethinkdb:"max_open,omitempty"`

	// Below options are for cluster discovery, please note there is a high
	// probability of these changing as the API is still being worked on.

	// DiscoverHosts is used to enable host discovery, when true the driver
	// will attempt to discover any new nodes added to the cluster and then
	// start sending queries to these new nodes.
	DiscoverHosts bool `rethinkdb:"discover_hosts,omitempty"`
	// HostDecayDuration is used by the go-hostpool package to calculate a weighted
	// score when selecting a host. By default a value of 5 minutes is used.
	HostDecayDuration time.Duration

	// UseOpentracing is used to enable creating opentracing-go spans for queries.
	// Each span is created as child of span from the context in `RunOpts`.
	// This span lasts from point the query created to the point when cursor closed.
	UseOpentracing bool

	// Deprecated: This function is no longer used due to changes in the
	// way hosts are selected.
	NodeRefreshInterval time.Duration `rethinkdb:"node_refresh_interval,omitempty"`
	// Deprecated: Use InitialCap instead
	MaxIdle int `rethinkdb:"max_idle,omitempty"`
}

func (o ConnectOpts) toMap() map[string]interface{} {
	return optArgsToMap(o)
}

// Connect creates a new database session. To view the available connection
// options see ConnectOpts.
//
// By default maxIdle and maxOpen are set to 1: passing values greater
// than the default (e.g. MaxIdle: "10", MaxOpen: "20") will provide a
// pool of re-usable connections.
//
// Basic connection example:
//
// 	session, err := r.Connect(r.ConnectOpts{
// 		Address: "localhost:28015",
// 		Database: "test",
// 		AuthKey:  "14daak1cad13dj",
// 	})
//
// Cluster connection example:
//
// 	session, err := r.Connect(r.ConnectOpts{
// 		Addresses: []string{"localhost:28015", "localhost:28016"},
// 		Database: "test",
// 		AuthKey:  "14daak1cad13dj",
// 	})
func Connect(opts ConnectOpts) (*Session, error) {
	var addresses = opts.Addresses
	if len(addresses) == 0 {
		addresses = []string{opts.Address}
	}

	hosts := make([]Host, len(addresses))
	for i, address := range addresses {
		hostname, port := splitAddress(address)
		hosts[i] = NewHost(hostname, port)
	}
	if len(hosts) <= 0 {
		return nil, ErrNoHosts
	}

	// Connect
	s := &Session{
		hosts: hosts,
		opts:  &opts,
	}

	err := s.Reconnect()
	if err != nil {
		// note: s.Reconnect() will initialize cluster information which
		// will cause the .IsConnected() method to be caught in a loop
		return &Session{
			hosts: hosts,
			opts:  &opts,
		}, err
	}

	return s, nil
}

// CloseOpts allows calls to the Close function to be configured.
type CloseOpts struct {
	NoReplyWait bool `rethinkdb:"noreplyWait,omitempty"`
}

func (o CloseOpts) toMap() map[string]interface{} {
	return optArgsToMap(o)
}

// IsConnected returns true if session has a valid connection.
func (s *Session) IsConnected() bool {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.cluster == nil || s.closed {
		return false
	}
	return s.cluster.IsConnected()
}

// Reconnect closes and re-opens a session.
func (s *Session) Reconnect(optArgs ...CloseOpts) error {
	var err error

	if err = s.Close(optArgs...); err != nil {
		return err
	}

	s.mu.Lock()
	s.cluster, err = NewCluster(s.hosts, s.opts)
	if err != nil {
		s.mu.Unlock()
		return err
	}

	s.closed = false
	s.mu.Unlock()

	return nil
}

// Close closes the session
func (s *Session) Close(optArgs ...CloseOpts) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return nil
	}

	if len(optArgs) >= 1 {
		if optArgs[0].NoReplyWait {
			s.mu.Unlock()
			s.NoReplyWait()
			s.mu.Lock()
		}
	}

	if s.cluster != nil {
		return s.cluster.Close()
	}
	s.cluster = nil
	s.closed = true

	return nil
}

// SetInitialPoolCap sets the initial capacity of the connection pool.
func (s *Session) SetInitialPoolCap(n int) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.opts.InitialCap = n
	s.cluster.SetInitialPoolCap(n)
}

// SetMaxIdleConns sets the maximum number of connections in the idle
// connection pool.
func (s *Session) SetMaxIdleConns(n int) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.opts.MaxIdle = n
	s.cluster.SetMaxIdleConns(n)
}

// SetMaxOpenConns sets the maximum number of open connections to the database.
func (s *Session) SetMaxOpenConns(n int) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.opts.MaxOpen = n
	s.cluster.SetMaxOpenConns(n)
}

// NoReplyWait ensures that previous queries with the noreply flag have been
// processed by the server. Note that this guarantee only applies to queries
// run on the given connection
func (s *Session) NoReplyWait() error {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return ErrConnectionClosed
	}

	return s.cluster.Exec(nil, Query{ // nil = connection opts' defaults
		Type: p.Query_NOREPLY_WAIT,
	})
}

// Use changes the default database used
func (s *Session) Use(database string) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.opts.Database = database
}

// Database returns the selected database set by Use
func (s *Session) Database() string {
	s.mu.RLock()
	defer s.mu.RUnlock()

	return s.opts.Database
}

// Query executes a ReQL query using the session to connect to the database
func (s *Session) Query(ctx context.Context, q Query) (*Cursor, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return nil, ErrConnectionClosed
	}

	return s.cluster.Query(ctx, q)
}

// Exec executes a ReQL query using the session to connect to the database
func (s *Session) Exec(ctx context.Context, q Query) error {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return ErrConnectionClosed
	}

	return s.cluster.Exec(ctx, q)
}

// Server returns the server name and server UUID being used by a connection.
func (s *Session) Server() (ServerResponse, error) {
	return s.cluster.Server()
}

// SetHosts resets the hosts used when connecting to the RethinkDB cluster
func (s *Session) SetHosts(hosts []Host) {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.hosts = hosts
}

func (s *Session) newQuery(t Term, opts map[string]interface{}) (Query, error) {
	return newQuery(t, opts, s.opts)
}
<|endoftext|>


<commit_before>package rethinkgo

import (
	"code.google.com/p/goprotobuf/proto"
	"errors"
	"fmt"
	"net"
	p "github.com/christopherhesse/rethinkgo/ql2"
	"sync"
	"sync/atomic"
	"time"
)

// maximum number of connections to a server to keep laying around, should add
// a function to change this if there is any demand
var maxIdleConnections int = 5

// Session represents a connection to a server, use it to run queries against a
// database, with either sess.Run(query) or query.Run(session).  It is safe to
// use from multiple goroutines.
type Session struct {
	// current query identifier, just needs to be unique for each query, so we
	// can match queries with responses, e.g. 4782371
	token int64
	// address of server, e.g. "localhost:28015"
	address string
	// database to use if no database is specified in query, e.g. "test"
	database string
	// maximum duration of a single query
	timeout time.Duration

	// protects idleConns and closed, because this lock is here, the session
	// should not be copied according to the "sync" module
	mutex     sync.Mutex
	idleConns []*connection
	closed    bool
}

// SetMaxIdleConnections sets the maximum number of connections that will sit
// around in the connection pool at a time.
//
// Example usage:
//
//   SetMaxIdleConnections(100)
func SetMaxIdleConnections(connections int) {
	maxIdleConnections = connections
}

// Connect creates a new database session.
//
// Example usage:
//
//  sess, err := r.Connect("localhost:28015", "test")
func Connect(address, database string) (*Session, error) {
	s := &Session{address: address, database: database, closed: true}

	err := s.Reconnect()

	if err != nil {
		return nil, err
	}

	return s, nil
}

// Reconnect closes and re-opens a session.
//[
// Example usage:
//
//  err := sess.Reconnect()
func (s *Session) Reconnect() error {
	if err := s.Close(); err != nil {
		return err
	}

	s.mutex.Lock()
	s.closed = false
	s.mutex.Unlock()

	// create a connection to make sure the server works, then immediately put it
	// in the idle connection pool
	conn, err := s.getConn()
	if err != nil {
		return err
	}

	s.putConn(conn)

	return nil
}

// Close closes the session, freeing any associated resources.
//
// Example usage:
//
//  err := sess.Close()
func (s *Session) Close() error {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if s.closed {
		return nil
	}

	var lastError error
	for _, conn := range s.idleConns {
		err := conn.Close()
		if err != nil {
			lastError = err
		}
	}
	s.idleConns = nil
	s.closed = true

	return lastError
}

// SetTimeout causes any future queries that are run on this session to timeout
// after the given duration, returning a timeout error.  Set to zero to disable.
//
// The timeout is global to all queries run on a single Session and does not
// apply to queries currently in progress.  The timeout does not cover the
// time taken to connect to the server in the case that there is no idle
// connection available.
//
// If a timeout occurs, the individual connection handling that query will be
// closed instead of being returned to the connection pool.
func (s *Session) SetTimeout(timeout time.Duration) {
	s.timeout = timeout
}

// return a connection from the free connections list if available, otherwise,
// create a new connection
func (s *Session) getConn() (*connection, error) {
	s.mutex.Lock()
	if s.closed {
		s.mutex.Unlock()
		return nil, errors.New("rethinkdb: session is closed")
	}
	if n := len(s.idleConns); n > 0 {
		// grab from end of slice so that underlying array does not need to be
		// resized when appending idle connections later
		conn := s.idleConns[n-1]
		s.idleConns = s.idleConns[:n-1]
		s.mutex.Unlock()
		return conn, nil
	}
	s.mutex.Unlock()

	conn, err := serverConnect(s.address)
	if err != nil {
		return nil, err
	}

	return conn, nil
}

// return a connection to the free list, or close it if we already have enough
func (s *Session) putConn(conn *connection) {
	s.mutex.Lock()
	if len(s.idleConns) < maxIdleConnections {
		s.idleConns = append(s.idleConns, conn)
		s.mutex.Unlock()
		return
	}
	s.mutex.Unlock()

	conn.Close()
}

// Use changes the default database for a connection.  This is the database that
// will be used when a query is created without an explicit database.  This
// should not be used if the session is shared between goroutines, confusion
// would result.
//
// Example usage:
//
//  sess.Use("dave")
//  rows := r.Table("employees").Run(session) // uses database "dave"
func (s *Session) Use(database string) {
	s.database = database
}

// getToken generates the next query token, used to number requests and match
// responses with requests.
func (s *Session) getToken() int64 {
	return atomic.AddInt64(&s.token, 1)
}

// Run executes a query directly on a specific session and returns an iterator
// that moves through the resulting JSON rows with rows.Next() and
// rows.Scan(&dest). See the documentation for the Rows object for other
// options.
//
// Example usage:
//
//  rows := session.Run(query)
//  for rows.Next() {
//      var row map[string]interface{}
//      rows.Scan(&row)
//      fmt.Println("row:", row)
//  }
//  if rows.Err() {
//      ...
//  }
func (s *Session) Run(query Exp) *Rows {
	queryProto, err := s.getContext().buildProtobuf(query)
	if err != nil {
		return &Rows{lasterr: err}
	}

	queryProto.Token = proto.Int64(s.getToken())

	conn, err := s.getConn()
	if err != nil {
		return &Rows{lasterr: err}
	}

	buffer, responseType, err := conn.executeQuery(queryProto, s.timeout)
	if err != nil {
		// see if we got a timeout error, close the connection if we did, since
		// the connection may not be idle for quite some time and we don't
		// want to try multiplexing queries over a rethinkdb connection
		netErr, ok := err.(net.Error)
		if ok && netErr.Timeout() {
			conn.Close()
		} else {
			s.putConn(conn)
		}
		return &Rows{lasterr: err}
	}

	if responseType != p.Response_SUCCESS_PARTIAL {
		// if we have a success stream response, the connection needs to be tied to
		// the iterator, since the iterator can only get more results from the same
		// connection it was originally started on
		s.putConn(conn)
	}

	switch responseType {
	case p.Response_SUCCESS_ATOM:
		// single document (or json) response, return an iterator anyway for
		// consistency of types
		return &Rows{
			buffer:       buffer,
			complete:     true,
			responseType: responseType,
		}
	case p.Response_SUCCESS_PARTIAL:
		// beginning of stream of rows, there are more results available from the
		// server than the ones we just received, so save the connection we used in
		// case the user wants more
		return &Rows{
			session:      s,
			conn:         conn,
			buffer:       buffer,
			complete:     false,
			token:        queryProto.GetToken(),
			responseType: responseType,
		}
	case p.Response_SUCCESS_SEQUENCE:
		// end of a stream of rows, since we got this on the initial query this means
		// that we got a stream response, but the number of results was less than the
		// number required to break the response into chunks. we can just return all
		// the results in one go, as this is the only response
		return &Rows{
			buffer:       buffer,
			complete:     true,
			responseType: responseType,
		}
	}
	return &Rows{lasterr: fmt.Errorf("rethinkdb: Unexpected response type from server: %v", responseType)}
}

func (s *Session) getContext() context {
	return context{databaseName: s.database, atomic: true}
}

// Run runs a query using the given session, there is one Run()
// method for each type of query.
func (e Exp) Run(session *Session) *Rows {
	return session.Run(e)
}
<commit_msg>Update comment<commit_after>package rethinkgo

import (
	"code.google.com/p/goprotobuf/proto"
	"errors"
	"fmt"
	"net"
	p "github.com/christopherhesse/rethinkgo/ql2"
	"sync"
	"sync/atomic"
	"time"
)

// maximum number of connections to a server to keep laying around
// rethinkgo will do some connection pooling for you and save some connections
// so that it doesn't have to setup new connections each time
// if you don't want to use rethinkgo's connection pooling, make a new session
// for each goroutine
var maxIdleConnections int = 5

// Session represents a connection to a server, use it to run queries against a
// database, with either sess.Run(query) or query.Run(session).  It is safe to
// use from multiple goroutines.
type Session struct {
	// current query identifier, just needs to be unique for each query, so we
	// can match queries with responses, e.g. 4782371
	token int64
	// address of server, e.g. "localhost:28015"
	address string
	// database to use if no database is specified in query, e.g. "test"
	database string
	// maximum duration of a single query
	timeout time.Duration

	// protects idleConns and closed, because this lock is here, the session
	// should not be copied according to the "sync" module
	mutex     sync.Mutex
	idleConns []*connection
	closed    bool
}

// SetMaxIdleConnections sets the maximum number of connections that will sit
// around in the connection pool at a time.
//
// Example usage:
//
//   SetMaxIdleConnections(100)
func SetMaxIdleConnections(connections int) {
	maxIdleConnections = connections
}

// Connect creates a new database session.
//
// Example usage:
//
//  sess, err := r.Connect("localhost:28015", "test")
func Connect(address, database string) (*Session, error) {
	s := &Session{address: address, database: database, closed: true}

	err := s.Reconnect()

	if err != nil {
		return nil, err
	}

	return s, nil
}

// Reconnect closes and re-opens a session.
//[
// Example usage:
//
//  err := sess.Reconnect()
func (s *Session) Reconnect() error {
	if err := s.Close(); err != nil {
		return err
	}

	s.mutex.Lock()
	s.closed = false
	s.mutex.Unlock()

	// create a connection to make sure the server works, then immediately put it
	// in the idle connection pool
	conn, err := s.getConn()
	if err != nil {
		return err
	}

	s.putConn(conn)

	return nil
}

// Close closes the session, freeing any associated resources.
//
// Example usage:
//
//  err := sess.Close()
func (s *Session) Close() error {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if s.closed {
		return nil
	}

	var lastError error
	for _, conn := range s.idleConns {
		err := conn.Close()
		if err != nil {
			lastError = err
		}
	}
	s.idleConns = nil
	s.closed = true

	return lastError
}

// SetTimeout causes any future queries that are run on this session to timeout
// after the given duration, returning a timeout error.  Set to zero to disable.
//
// The timeout is global to all queries run on a single Session and does not
// apply to queries currently in progress.  The timeout does not cover the
// time taken to connect to the server in the case that there is no idle
// connection available.
//
// If a timeout occurs, the individual connection handling that query will be
// closed instead of being returned to the connection pool.
func (s *Session) SetTimeout(timeout time.Duration) {
	s.timeout = timeout
}

// return a connection from the free connections list if available, otherwise,
// create a new connection
func (s *Session) getConn() (*connection, error) {
	s.mutex.Lock()
	if s.closed {
		s.mutex.Unlock()
		return nil, errors.New("rethinkdb: session is closed")
	}
	if n := len(s.idleConns); n > 0 {
		// grab from end of slice so that underlying array does not need to be
		// resized when appending idle connections later
		conn := s.idleConns[n-1]
		s.idleConns = s.idleConns[:n-1]
		s.mutex.Unlock()
		return conn, nil
	}
	s.mutex.Unlock()

	conn, err := serverConnect(s.address)
	if err != nil {
		return nil, err
	}

	return conn, nil
}

// return a connection to the free list, or close it if we already have enough
func (s *Session) putConn(conn *connection) {
	s.mutex.Lock()
	if len(s.idleConns) < maxIdleConnections {
		s.idleConns = append(s.idleConns, conn)
		s.mutex.Unlock()
		return
	}
	s.mutex.Unlock()

	conn.Close()
}

// Use changes the default database for a connection.  This is the database that
// will be used when a query is created without an explicit database.  This
// should not be used if the session is shared between goroutines, confusion
// would result.
//
// Example usage:
//
//  sess.Use("dave")
//  rows := r.Table("employees").Run(session) // uses database "dave"
func (s *Session) Use(database string) {
	s.database = database
}

// getToken generates the next query token, used to number requests and match
// responses with requests.
func (s *Session) getToken() int64 {
	return atomic.AddInt64(&s.token, 1)
}

// Run executes a query directly on a specific session and returns an iterator
// that moves through the resulting JSON rows with rows.Next() and
// rows.Scan(&dest). See the documentation for the Rows object for other
// options.
//
// Example usage:
//
//  rows := session.Run(query)
//  for rows.Next() {
//      var row map[string]interface{}
//      rows.Scan(&row)
//      fmt.Println("row:", row)
//  }
//  if rows.Err() {
//      ...
//  }
func (s *Session) Run(query Exp) *Rows {
	queryProto, err := s.getContext().buildProtobuf(query)
	if err != nil {
		return &Rows{lasterr: err}
	}

	queryProto.Token = proto.Int64(s.getToken())

	conn, err := s.getConn()
	if err != nil {
		return &Rows{lasterr: err}
	}

	buffer, responseType, err := conn.executeQuery(queryProto, s.timeout)
	if err != nil {
		// see if we got a timeout error, close the connection if we did, since
		// the connection may not be idle for quite some time and we don't
		// want to try multiplexing queries over a rethinkdb connection
		netErr, ok := err.(net.Error)
		if ok && netErr.Timeout() {
			conn.Close()
		} else {
			s.putConn(conn)
		}
		return &Rows{lasterr: err}
	}

	if responseType != p.Response_SUCCESS_PARTIAL {
		// if we have a success stream response, the connection needs to be tied to
		// the iterator, since the iterator can only get more results from the same
		// connection it was originally started on
		s.putConn(conn)
	}

	switch responseType {
	case p.Response_SUCCESS_ATOM:
		// single document (or json) response, return an iterator anyway for
		// consistency of types
		return &Rows{
			buffer:       buffer,
			complete:     true,
			responseType: responseType,
		}
	case p.Response_SUCCESS_PARTIAL:
		// beginning of stream of rows, there are more results available from the
		// server than the ones we just received, so save the connection we used in
		// case the user wants more
		return &Rows{
			session:      s,
			conn:         conn,
			buffer:       buffer,
			complete:     false,
			token:        queryProto.GetToken(),
			responseType: responseType,
		}
	case p.Response_SUCCESS_SEQUENCE:
		// end of a stream of rows, since we got this on the initial query this means
		// that we got a stream response, but the number of results was less than the
		// number required to break the response into chunks. we can just return all
		// the results in one go, as this is the only response
		return &Rows{
			buffer:       buffer,
			complete:     true,
			responseType: responseType,
		}
	}
	return &Rows{lasterr: fmt.Errorf("rethinkdb: Unexpected response type from server: %v", responseType)}
}

func (s *Session) getContext() context {
	return context{databaseName: s.database, atomic: true}
}

// Run runs a query using the given session, there is one Run()
// method for each type of query.
func (e Exp) Run(session *Session) *Rows {
	return session.Run(e)
}
<|endoftext|>


<commit_before>package goparse

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strings"

	"github.com/parnurzeal/gorequest"
)

const (
	headerAppID        = "X-Parse-Application-Id" // Parse Application ID
	headerMasterKey    = "X-Parse-Master-Key"     // Parse Master Key
	headerAPIKey       = "X-Parse-REST-API-Key"   // Parse REST API Key
	headerSessionToken = "X-Parse-Session-Token"  // Parse Session Token

	pathMe = "/users/me"
)

// ParseSession is the client which has SessionToken as user authentication.
type ParseSession struct {
	client       *ParseClient
	SessionToken string
}

const (
	// Error code referrense at parse.com
	// http://www.parse.com/docs/dotnet/api/html/T_Parse_ParseException_ErrorCode.htm

	// errCodeObjectNotFound is object not found
	errCodeObjectNotFound = 101
)

var (
	// ErrObjectNotFound Error code indicating the specified object doesn't exist.
	ErrObjectNotFound = errors.New("object not found")
)

// Create a request which is set headers for Parse API
func (s *ParseSession) initRequest(req *gorequest.SuperAgent, useMaster bool) {
	if useMaster {
		req.
			Set(headerAppID, s.client.ApplicationID).
			Set(headerMasterKey, s.client.MasterKey).
			Timeout(s.client.TimeOut)
	} else {
		req.
			Set(headerAppID, s.client.ApplicationID).
			Set(headerAPIKey, s.client.RESTAPIKey).
			Timeout(s.client.TimeOut)
	}

	if s.SessionToken != "" {
		req.Set(headerSessionToken, s.SessionToken)
	}
}

func (s *ParseSession) get(path string, useMaster bool) *gorequest.SuperAgent {
	req := gorequest.New().Get(s.client.URL + path)
	s.initRequest(req, useMaster)
	return req
}

func (s *ParseSession) post(path string, useMaster bool) *gorequest.SuperAgent {
	req := gorequest.New().Post(s.client.URL + path)
	s.initRequest(req, useMaster)
	return req
}

func (s *ParseSession) put(path string, useMaster bool) *gorequest.SuperAgent {
	req := gorequest.New().Put(s.client.URL + path)
	s.initRequest(req, useMaster)
	return req
}

func (s *ParseSession) del(path string, useMaster bool) *gorequest.SuperAgent {
	req := gorequest.New().Delete(s.client.URL + path)
	s.initRequest(req, useMaster)
	return req
}

// Signup new user
func (s *ParseSession) Signup(data Signup) (user User, err error) {
	return user, do(s.post("/users", false).Send(data), &user)
}

// Login with data
func (s *ParseSession) Login(username string, password string) (user User, err error) {

	// Query values
	vals := url.Values{
		"username": []string{username},
		"password": []string{password},
	}

	// Create a user
	err = do(s.get("/login", false).Query(vals.Encode()), &user)

	if user.SessionToken != "" {
		s.SessionToken = user.SessionToken
	}

	return user, err
}

// GetUser gets user information
func (s *ParseSession) GetUser(userObjectID string) (user User, err error) {
	if userObjectID == "" {
		return user, errors.New("userObjectID must not be empty")
	}
	return user, do(s.get("/users/"+userObjectID, false), &user)
}

// GetUserByMaster gets user information
func (s *ParseSession) GetUserByMaster(userObjectID string) (user User, err error) {
	if userObjectID == "" {
		return user, errors.New("userObjectID must not be empty")
	}
	return user, do(s.get("/users/"+userObjectID, true), &user)
}

// GetMe gets self user information
func (s *ParseSession) GetMe() (user User, err error) {
	err = s.GetMeInto(&user)
	return user, err
}

// GetMeInto gets self user information into provided object
func (s *ParseSession) GetMeInto(user interface{}) error {
	if user == nil {
		return errors.New("user must not be nil")
	}
	return do(s.get("/users/me", false), user)
}

// DeleteUser deletes user by ID
func (s *ParseSession) DeleteUser(userID string) error {
	return do(s.del("/users/"+userID, false), nil)
}

// UploadInstallation stores the subscription data for installations
func (s *ParseSession) UploadInstallation(data Installation, result interface{}) error {
	return do(s.post("/installations", false).Send(data), result)
}

// PushNotification sends push-notifiaction each device via parse
func (s *ParseSession) PushNotification(query map[string]interface{}, data interface{}) error {
	body := PushNotificationQuery{
		Where: query,
		Data:  data,
	}
	return do(s.post("/push", false).Send(body), nil)
}

// Execute a parse request
func do(req *gorequest.SuperAgent, data interface{}) error {

	res, body, errs := req.End()
	if errs != nil {
		return fmt.Errorf("%v", errs)
	}
	if res.StatusCode < 200 || res.StatusCode >= 300 {
		// parse as error model
		reserr := new(Error)
		err := json.NewDecoder(strings.NewReader(body)).Decode(reserr)
		if err != nil {
			return err
		}
		return reserr
	}
	if data == nil {
		return nil
	}
	return json.NewDecoder(strings.NewReader(body)).Decode(data)
}

// NewClass creates a new class from the session
func (s *ParseSession) NewClass(className string) *ParseClass {
	return &ParseClass{
		Session:   s,
		Name:      className,
		ClassURL:  "/classes/" + className,
		UseMaster: false,
	}
}

// IsObjectNotFound check the error "not found" or not
func IsObjectNotFound(err error) bool {
	v, ok := err.(*Error)
	return ok && v.Code == errCodeObjectNotFound
}
<commit_msg>Modified signup param<commit_after>package goparse

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strings"

	"github.com/parnurzeal/gorequest"
)

const (
	headerAppID        = "X-Parse-Application-Id" // Parse Application ID
	headerMasterKey    = "X-Parse-Master-Key"     // Parse Master Key
	headerAPIKey       = "X-Parse-REST-API-Key"   // Parse REST API Key
	headerSessionToken = "X-Parse-Session-Token"  // Parse Session Token

	pathMe = "/users/me"
)

// ParseSession is the client which has SessionToken as user authentication.
type ParseSession struct {
	client       *ParseClient
	SessionToken string
}

const (
	// Error code referrense at parse.com
	// http://www.parse.com/docs/dotnet/api/html/T_Parse_ParseException_ErrorCode.htm

	// errCodeObjectNotFound is object not found
	errCodeObjectNotFound = 101
)

var (
	// ErrObjectNotFound Error code indicating the specified object doesn't exist.
	ErrObjectNotFound = errors.New("object not found")
)

// Create a request which is set headers for Parse API
func (s *ParseSession) initRequest(req *gorequest.SuperAgent, useMaster bool) {
	if useMaster {
		req.
			Set(headerAppID, s.client.ApplicationID).
			Set(headerMasterKey, s.client.MasterKey).
			Timeout(s.client.TimeOut)
	} else {
		req.
			Set(headerAppID, s.client.ApplicationID).
			Set(headerAPIKey, s.client.RESTAPIKey).
			Timeout(s.client.TimeOut)
	}

	if s.SessionToken != "" {
		req.Set(headerSessionToken, s.SessionToken)
	}
}

func (s *ParseSession) get(path string, useMaster bool) *gorequest.SuperAgent {
	req := gorequest.New().Get(s.client.URL + path)
	s.initRequest(req, useMaster)
	return req
}

func (s *ParseSession) post(path string, useMaster bool) *gorequest.SuperAgent {
	req := gorequest.New().Post(s.client.URL + path)
	s.initRequest(req, useMaster)
	return req
}

func (s *ParseSession) put(path string, useMaster bool) *gorequest.SuperAgent {
	req := gorequest.New().Put(s.client.URL + path)
	s.initRequest(req, useMaster)
	return req
}

func (s *ParseSession) del(path string, useMaster bool) *gorequest.SuperAgent {
	req := gorequest.New().Delete(s.client.URL + path)
	s.initRequest(req, useMaster)
	return req
}

// Signup new user
func (s *ParseSession) Signup(data interface{}) (user User, err error) {
	return user, do(s.post("/users", false).Send(data), &user)
}

// Login with data
func (s *ParseSession) Login(username string, password string) (user User, err error) {

	// Query values
	vals := url.Values{
		"username": []string{username},
		"password": []string{password},
	}

	// Create a user
	err = do(s.get("/login", false).Query(vals.Encode()), &user)

	if user.SessionToken != "" {
		s.SessionToken = user.SessionToken
	}

	return user, err
}

// GetUser gets user information
func (s *ParseSession) GetUser(userObjectID string) (user User, err error) {
	if userObjectID == "" {
		return user, errors.New("userObjectID must not be empty")
	}
	return user, do(s.get("/users/"+userObjectID, false), &user)
}

// GetUserByMaster gets user information
func (s *ParseSession) GetUserByMaster(userObjectID string) (user User, err error) {
	if userObjectID == "" {
		return user, errors.New("userObjectID must not be empty")
	}
	return user, do(s.get("/users/"+userObjectID, true), &user)
}

// GetMe gets self user information
func (s *ParseSession) GetMe() (user User, err error) {
	err = s.GetMeInto(&user)
	return user, err
}

// GetMeInto gets self user information into provided object
func (s *ParseSession) GetMeInto(user interface{}) error {
	if user == nil {
		return errors.New("user must not be nil")
	}
	return do(s.get("/users/me", false), user)
}

// DeleteUser deletes user by ID
func (s *ParseSession) DeleteUser(userID string) error {
	return do(s.del("/users/"+userID, false), nil)
}

// UploadInstallation stores the subscription data for installations
func (s *ParseSession) UploadInstallation(data Installation, result interface{}) error {
	return do(s.post("/installations", false).Send(data), result)
}

// PushNotification sends push-notifiaction each device via parse
func (s *ParseSession) PushNotification(query map[string]interface{}, data interface{}) error {
	body := PushNotificationQuery{
		Where: query,
		Data:  data,
	}
	return do(s.post("/push", false).Send(body), nil)
}

// Execute a parse request
func do(req *gorequest.SuperAgent, data interface{}) error {

	res, body, errs := req.End()
	if errs != nil {
		return fmt.Errorf("%v", errs)
	}
	if res.StatusCode < 200 || res.StatusCode >= 300 {
		// parse as error model
		reserr := new(Error)
		err := json.NewDecoder(strings.NewReader(body)).Decode(reserr)
		if err != nil {
			return err
		}
		return reserr
	}
	if data == nil {
		return nil
	}
	return json.NewDecoder(strings.NewReader(body)).Decode(data)
}

// NewClass creates a new class from the session
func (s *ParseSession) NewClass(className string) *ParseClass {
	return &ParseClass{
		Session:   s,
		Name:      className,
		ClassURL:  "/classes/" + className,
		UseMaster: false,
	}
}

// IsObjectNotFound check the error "not found" or not
func IsObjectNotFound(err error) bool {
	v, ok := err.(*Error)
	return ok && v.Code == errCodeObjectNotFound
}
<|endoftext|>


<commit_before>package capnp

// Struct is a pointer to a struct.
type Struct struct {
	seg   *Segment
	off   Address
	size  ObjectSize
	flags structFlags
}

// NewStruct creates a new struct, preferring placement in s.
func NewStruct(s *Segment, sz ObjectSize) (Struct, error) {
	if !sz.isValid() {
		return Struct{}, errObjectSize
	}
	sz.DataSize = sz.DataSize.padToWord()
	seg, addr, err := alloc(s, sz.totalSize())
	if err != nil {
		return Struct{}, err
	}
	return Struct{
		seg:  seg,
		off:  addr,
		size: sz,
	}, nil
}

// NewRootStruct creates a new struct, preferring placement in s, then sets the
// message's root to the new struct.
func NewRootStruct(s *Segment, sz ObjectSize) (Struct, error) {
	st, err := NewStruct(s, sz)
	if err != nil {
		return st, err
	}
	if err := s.msg.SetRoot(st); err != nil {
		return st, err
	}
	return st, nil
}

// ToStruct attempts to convert p into a struct.  If p is not a valid
// struct, then it returns an invalid Struct.
func ToStruct(p Pointer) Struct {
	if !IsValid(p) {
		return Struct{}
	}
	s, ok := p.underlying().(Struct)
	if !ok {
		return Struct{}
	}
	return s
}

// ToStructDefault attempts to convert p into a struct, reading the
// default value from def if p is not a struct.
func ToStructDefault(p Pointer, def []byte) (Struct, error) {
	fallback := func() (Struct, error) {
		if def == nil {
			return Struct{}, nil
		}
		defp, err := unmarshalDefault(def)
		if err != nil {
			return Struct{}, err
		}
		return ToStruct(defp), nil
	}
	if !IsValid(p) {
		return fallback()
	}
	s, ok := p.underlying().(Struct)
	if !ok {
		return fallback()
	}
	return s, nil
}

// Segment returns the segment this pointer came from.
func (p Struct) Segment() *Segment {
	return p.seg
}

// Address returns the address the pointer references.
func (p Struct) Address() Address {
	return p.off
}

// HasData reports whether the struct has a non-zero size.
func (p Struct) HasData() bool {
	return !p.size.isZero()
}

// value returns a raw struct pointer.
func (p Struct) value(paddr Address) rawPointer {
	off := makePointerOffset(paddr, p.off)
	return rawStructPointer(off, p.size)
}

func (p Struct) underlying() Pointer {
	return p
}

// Pointer returns the i'th pointer in the struct.
func (p Struct) Pointer(i uint16) (Pointer, error) {
	if p.seg == nil || i >= p.size.PointerCount {
		return nil, nil
	}
	return p.seg.readPtr(p.pointerAddress(i))
}

// SetPointer sets the i'th pointer in the struct to src.
func (p Struct) SetPointer(i uint16, src Pointer) error {
	if p.seg == nil || i >= p.size.PointerCount {
		panic(errOutOfBounds)
	}
	return p.seg.writePtr(copyContext{}, p.pointerAddress(i), src)
}

func (p Struct) pointerAddress(i uint16) Address {
	ptrStart := p.off.addSize(p.size.DataSize)
	return ptrStart.element(int32(i), wordSize)
}

// bitInData reports whether bit is inside p's data section.
func (p Struct) bitInData(bit BitOffset) bool {
	return p.seg != nil && bit < BitOffset(p.size.DataSize*8)
}

// Bit returns the bit that is n bits from the start of the struct.
func (p Struct) Bit(n BitOffset) bool {
	if !p.bitInData(n) {
		return false
	}
	addr := p.off.addOffset(n.offset())
	return p.seg.readUint8(addr)&n.mask() != 0
}

// SetBit sets the bit that is n bits from the start of the struct to v.
func (p Struct) SetBit(n BitOffset, v bool) {
	if !p.bitInData(n) {
		panic(errOutOfBounds)
	}
	addr := p.off.addOffset(n.offset())
	b := p.seg.readUint8(addr)
	if v {
		b |= n.mask()
	} else {
		b &^= n.mask()
	}
	p.seg.writeUint8(addr, b)
}

func (p Struct) dataAddress(off DataOffset, sz Size) (addr Address, ok bool) {
	if p.seg == nil || Size(off)+sz > p.size.DataSize {
		return 0, false
	}
	return p.off.addOffset(off), true
}

// Uint8 returns an 8-bit integer from the struct's data section.
func (p Struct) Uint8(off DataOffset) uint8 {
	addr, ok := p.dataAddress(off, 1)
	if !ok {
		return 0
	}
	return p.seg.readUint8(addr)
}

// Uint16 returns a 16-bit integer from the struct's data section.
func (p Struct) Uint16(off DataOffset) uint16 {
	addr, ok := p.dataAddress(off, 2)
	if !ok {
		return 0
	}
	return p.seg.readUint16(addr)
}

// Uint32 returns a 32-bit integer from the struct's data section.
func (p Struct) Uint32(off DataOffset) uint32 {
	addr, ok := p.dataAddress(off, 4)
	if !ok {
		return 0
	}
	return p.seg.readUint32(addr)
}

// Uint64 returns a 64-bit integer from the struct's data section.
func (p Struct) Uint64(off DataOffset) uint64 {
	addr, ok := p.dataAddress(off, 8)
	if !ok {
		return 0
	}
	return p.seg.readUint64(addr)
}

// SetUint8 sets the 8-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint8(off DataOffset, v uint8) {
	addr, ok := p.dataAddress(off, 1)
	if !ok {
		panic(errOutOfBounds)
	}
	p.seg.writeUint8(addr, v)
}

// SetUint16 sets the 16-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint16(off DataOffset, v uint16) {
	addr, ok := p.dataAddress(off, 2)
	if !ok {
		panic(errOutOfBounds)
	}
	p.seg.writeUint16(addr, v)
}

// SetUint32 sets the 32-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint32(off DataOffset, v uint32) {
	addr, ok := p.dataAddress(off, 4)
	if !ok {
		panic(errOutOfBounds)
	}
	p.seg.writeUint32(addr, v)
}

// SetUint64 sets the 64-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint64(off DataOffset, v uint64) {
	addr, ok := p.dataAddress(off, 8)
	if !ok {
		panic(errOutOfBounds)
	}
	p.seg.writeUint64(addr, v)
}

func isEmptyStruct(src Pointer) bool {
	s := ToStruct(src)
	if IsValid(s) {
		return false
	}
	return s.size.isZero() && s.flags == 0
}

// structFlags is a bitmask of flags for a pointer.
type structFlags uint8

// Pointer flags.
const (
	isListMember structFlags = 1 << iota
)

// copyStruct makes a deep copy of src into dst.
func copyStruct(cc copyContext, dst, src Struct) error {
	if dst.seg == nil {
		return nil
	}

	// Q: how does version handling happen here, when the
	//    destination toData[] slice can be bigger or smaller
	//    than the source data slice, which is in
	//    src.seg.Data[src.off:src.off+src.size.DataSize] ?
	//
	// A: Newer fields only come *after* old fields. Note that
	//    copy only copies min(len(src), len(dst)) size,
	//    and then we manually zero the rest in the for loop
	//    that writes toData[j] = 0.
	//

	// data section:
	srcData := src.seg.slice(src.off, src.size.DataSize)
	dstData := dst.seg.slice(dst.off, dst.size.DataSize)
	copyCount := copy(dstData, srcData)
	dstData = dstData[copyCount:]
	for j := range dstData {
		dstData[j] = 0
	}

	// ptrs section:

	// version handling: we ignore any extra-newer-pointers in src,
	// i.e. the case when srcPtrSize > dstPtrSize, by only
	// running j over the size of dstPtrSize, the destination size.
	srcPtrSect := src.off.addSize(src.size.DataSize)
	dstPtrSect := dst.off.addSize(dst.size.DataSize)
	numSrcPtrs := src.size.PointerCount
	numDstPtrs := dst.size.PointerCount
	for j := uint16(0); j < numSrcPtrs && j < numDstPtrs; j++ {
		srcAddr := srcPtrSect.element(int32(j), wordSize)
		dstAddr := dstPtrSect.element(int32(j), wordSize)
		m, err := src.seg.readPtr(srcAddr)
		if err != nil {
			return err
		}
		err = dst.seg.writePtr(cc.incDepth(), dstAddr, m)
		if err != nil {
			return err
		}
	}
	for j := numSrcPtrs; j < numDstPtrs; j++ {
		// destination p is a newer version than source so these extra new pointer fields in p must be zeroed.
		addr := dstPtrSect.element(int32(j), wordSize)
		dst.seg.writeRawPointer(addr, 0)
	}
	// Nothing more here: so any other pointers in srcPtrSize beyond
	// those in dstPtrSize are ignored and discarded.

	return nil
}
<commit_msg>capnp: remove unused isEmptyStruct function<commit_after>package capnp

// Struct is a pointer to a struct.
type Struct struct {
	seg   *Segment
	off   Address
	size  ObjectSize
	flags structFlags
}

// NewStruct creates a new struct, preferring placement in s.
func NewStruct(s *Segment, sz ObjectSize) (Struct, error) {
	if !sz.isValid() {
		return Struct{}, errObjectSize
	}
	sz.DataSize = sz.DataSize.padToWord()
	seg, addr, err := alloc(s, sz.totalSize())
	if err != nil {
		return Struct{}, err
	}
	return Struct{
		seg:  seg,
		off:  addr,
		size: sz,
	}, nil
}

// NewRootStruct creates a new struct, preferring placement in s, then sets the
// message's root to the new struct.
func NewRootStruct(s *Segment, sz ObjectSize) (Struct, error) {
	st, err := NewStruct(s, sz)
	if err != nil {
		return st, err
	}
	if err := s.msg.SetRoot(st); err != nil {
		return st, err
	}
	return st, nil
}

// ToStruct attempts to convert p into a struct.  If p is not a valid
// struct, then it returns an invalid Struct.
func ToStruct(p Pointer) Struct {
	if !IsValid(p) {
		return Struct{}
	}
	s, ok := p.underlying().(Struct)
	if !ok {
		return Struct{}
	}
	return s
}

// ToStructDefault attempts to convert p into a struct, reading the
// default value from def if p is not a struct.
func ToStructDefault(p Pointer, def []byte) (Struct, error) {
	fallback := func() (Struct, error) {
		if def == nil {
			return Struct{}, nil
		}
		defp, err := unmarshalDefault(def)
		if err != nil {
			return Struct{}, err
		}
		return ToStruct(defp), nil
	}
	if !IsValid(p) {
		return fallback()
	}
	s, ok := p.underlying().(Struct)
	if !ok {
		return fallback()
	}
	return s, nil
}

// Segment returns the segment this pointer came from.
func (p Struct) Segment() *Segment {
	return p.seg
}

// Address returns the address the pointer references.
func (p Struct) Address() Address {
	return p.off
}

// HasData reports whether the struct has a non-zero size.
func (p Struct) HasData() bool {
	return !p.size.isZero()
}

// value returns a raw struct pointer.
func (p Struct) value(paddr Address) rawPointer {
	off := makePointerOffset(paddr, p.off)
	return rawStructPointer(off, p.size)
}

func (p Struct) underlying() Pointer {
	return p
}

// Pointer returns the i'th pointer in the struct.
func (p Struct) Pointer(i uint16) (Pointer, error) {
	if p.seg == nil || i >= p.size.PointerCount {
		return nil, nil
	}
	return p.seg.readPtr(p.pointerAddress(i))
}

// SetPointer sets the i'th pointer in the struct to src.
func (p Struct) SetPointer(i uint16, src Pointer) error {
	if p.seg == nil || i >= p.size.PointerCount {
		panic(errOutOfBounds)
	}
	return p.seg.writePtr(copyContext{}, p.pointerAddress(i), src)
}

func (p Struct) pointerAddress(i uint16) Address {
	ptrStart := p.off.addSize(p.size.DataSize)
	return ptrStart.element(int32(i), wordSize)
}

// bitInData reports whether bit is inside p's data section.
func (p Struct) bitInData(bit BitOffset) bool {
	return p.seg != nil && bit < BitOffset(p.size.DataSize*8)
}

// Bit returns the bit that is n bits from the start of the struct.
func (p Struct) Bit(n BitOffset) bool {
	if !p.bitInData(n) {
		return false
	}
	addr := p.off.addOffset(n.offset())
	return p.seg.readUint8(addr)&n.mask() != 0
}

// SetBit sets the bit that is n bits from the start of the struct to v.
func (p Struct) SetBit(n BitOffset, v bool) {
	if !p.bitInData(n) {
		panic(errOutOfBounds)
	}
	addr := p.off.addOffset(n.offset())
	b := p.seg.readUint8(addr)
	if v {
		b |= n.mask()
	} else {
		b &^= n.mask()
	}
	p.seg.writeUint8(addr, b)
}

func (p Struct) dataAddress(off DataOffset, sz Size) (addr Address, ok bool) {
	if p.seg == nil || Size(off)+sz > p.size.DataSize {
		return 0, false
	}
	return p.off.addOffset(off), true
}

// Uint8 returns an 8-bit integer from the struct's data section.
func (p Struct) Uint8(off DataOffset) uint8 {
	addr, ok := p.dataAddress(off, 1)
	if !ok {
		return 0
	}
	return p.seg.readUint8(addr)
}

// Uint16 returns a 16-bit integer from the struct's data section.
func (p Struct) Uint16(off DataOffset) uint16 {
	addr, ok := p.dataAddress(off, 2)
	if !ok {
		return 0
	}
	return p.seg.readUint16(addr)
}

// Uint32 returns a 32-bit integer from the struct's data section.
func (p Struct) Uint32(off DataOffset) uint32 {
	addr, ok := p.dataAddress(off, 4)
	if !ok {
		return 0
	}
	return p.seg.readUint32(addr)
}

// Uint64 returns a 64-bit integer from the struct's data section.
func (p Struct) Uint64(off DataOffset) uint64 {
	addr, ok := p.dataAddress(off, 8)
	if !ok {
		return 0
	}
	return p.seg.readUint64(addr)
}

// SetUint8 sets the 8-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint8(off DataOffset, v uint8) {
	addr, ok := p.dataAddress(off, 1)
	if !ok {
		panic(errOutOfBounds)
	}
	p.seg.writeUint8(addr, v)
}

// SetUint16 sets the 16-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint16(off DataOffset, v uint16) {
	addr, ok := p.dataAddress(off, 2)
	if !ok {
		panic(errOutOfBounds)
	}
	p.seg.writeUint16(addr, v)
}

// SetUint32 sets the 32-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint32(off DataOffset, v uint32) {
	addr, ok := p.dataAddress(off, 4)
	if !ok {
		panic(errOutOfBounds)
	}
	p.seg.writeUint32(addr, v)
}

// SetUint64 sets the 64-bit integer that is off bytes from the start of the struct to v.
func (p Struct) SetUint64(off DataOffset, v uint64) {
	addr, ok := p.dataAddress(off, 8)
	if !ok {
		panic(errOutOfBounds)
	}
	p.seg.writeUint64(addr, v)
}

// structFlags is a bitmask of flags for a pointer.
type structFlags uint8

// Pointer flags.
const (
	isListMember structFlags = 1 << iota
)

// copyStruct makes a deep copy of src into dst.
func copyStruct(cc copyContext, dst, src Struct) error {
	if dst.seg == nil {
		return nil
	}

	// Q: how does version handling happen here, when the
	//    destination toData[] slice can be bigger or smaller
	//    than the source data slice, which is in
	//    src.seg.Data[src.off:src.off+src.size.DataSize] ?
	//
	// A: Newer fields only come *after* old fields. Note that
	//    copy only copies min(len(src), len(dst)) size,
	//    and then we manually zero the rest in the for loop
	//    that writes toData[j] = 0.
	//

	// data section:
	srcData := src.seg.slice(src.off, src.size.DataSize)
	dstData := dst.seg.slice(dst.off, dst.size.DataSize)
	copyCount := copy(dstData, srcData)
	dstData = dstData[copyCount:]
	for j := range dstData {
		dstData[j] = 0
	}

	// ptrs section:

	// version handling: we ignore any extra-newer-pointers in src,
	// i.e. the case when srcPtrSize > dstPtrSize, by only
	// running j over the size of dstPtrSize, the destination size.
	srcPtrSect := src.off.addSize(src.size.DataSize)
	dstPtrSect := dst.off.addSize(dst.size.DataSize)
	numSrcPtrs := src.size.PointerCount
	numDstPtrs := dst.size.PointerCount
	for j := uint16(0); j < numSrcPtrs && j < numDstPtrs; j++ {
		srcAddr := srcPtrSect.element(int32(j), wordSize)
		dstAddr := dstPtrSect.element(int32(j), wordSize)
		m, err := src.seg.readPtr(srcAddr)
		if err != nil {
			return err
		}
		err = dst.seg.writePtr(cc.incDepth(), dstAddr, m)
		if err != nil {
			return err
		}
	}
	for j := numSrcPtrs; j < numDstPtrs; j++ {
		// destination p is a newer version than source so these extra new pointer fields in p must be zeroed.
		addr := dstPtrSect.element(int32(j), wordSize)
		dst.seg.writeRawPointer(addr, 0)
	}
	// Nothing more here: so any other pointers in srcPtrSize beyond
	// those in dstPtrSize are ignored and discarded.

	return nil
}
<|endoftext|>


<commit_before>package gsocket

import (
	"log"
	"net"
)

// Session 代表一个连接会话
type Session struct {
	connection net.Conn
	sendBuffer chan []byte
	terminated bool
}

// NewSession 生成一个新的Session
func newSession(conn net.Conn) (session *Session) {
	session = &Session{
		connection: conn,
		sendBuffer: make(chan []byte, 10),
		terminated: false,
	}

	return session
}

// RemoteAddr 返回客户端的地址和端口
func (session *Session) RemoteAddr() string {
	return session.connection.RemoteAddr().String()
}

// Close 关闭Session
func (session *Session) Close() {
	close(session.sendBuffer)
	session.connection.Close()
}

func (session *Session) recvThread(server *TCPServer) {
	defer server.wg.Done()
	buffer := make([]byte, 4096)
	for {
		n, err := session.connection.Read(buffer)
		if err != nil {
			break
		}

		//session.RecvedPackets = append(session.RecvedPackets, buffer[:n]...)
		if server.userHandler.handlerRecv != nil {
			server.userHandler.OnRecv(session, buffer[:n])
		}
	}

	log.Printf("session %s recvThread Exit", session.RemoteAddr())
}

func (session *Session) sendThread(server *TCPServer) {
	defer server.wg.Done()

	for {
		packet, ok := <-session.sendBuffer
		if !ok {
			// 意味着道通已经空了，并且已被关闭
			break
		}
		_, err := session.connection.Write(packet)
		if err != nil {
			break
		}
	}

	log.Printf("session %s sendThread Exit", session.RemoteAddr())
}

// Send 发送数据
func (session *Session) Send(packet []byte) {
	session.sendBuffer <- packet
}
<commit_msg>修正调用handlerRecv的BUG<commit_after>package gsocket

import (
	"log"
	"net"
)

// Session 代表一个连接会话
type Session struct {
	connection net.Conn
	sendBuffer chan []byte
	terminated bool
}

// NewSession 生成一个新的Session
func newSession(conn net.Conn) (session *Session) {
	session = &Session{
		connection: conn,
		sendBuffer: make(chan []byte, 10),
		terminated: false,
	}

	return session
}

// RemoteAddr 返回客户端的地址和端口
func (session *Session) RemoteAddr() string {
	return session.connection.RemoteAddr().String()
}

// Close 关闭Session
func (session *Session) Close() {
	close(session.sendBuffer)
	session.connection.Close()
}

func (session *Session) recvThread(server *TCPServer) {
	defer server.wg.Done()
	buffer := make([]byte, 4096)
	for {
		n, err := session.connection.Read(buffer)
		if err != nil {
			break
		}

		//session.RecvedPackets = append(session.RecvedPackets, buffer[:n]...)
		if server.userHandler.handlerRecv != nil {
			server.userHandler.handlerRecv(session, buffer[:n])
		}
	}

	log.Printf("session %s recvThread Exit", session.RemoteAddr())
}

func (session *Session) sendThread(server *TCPServer) {
	defer server.wg.Done()

	for {
		packet, ok := <-session.sendBuffer
		if !ok {
			// 意味着道通已经空了，并且已被关闭
			break
		}
		_, err := session.connection.Write(packet)
		if err != nil {
			break
		}
	}

	log.Printf("session %s sendThread Exit", session.RemoteAddr())
}

// Send 发送数据
func (session *Session) Send(packet []byte) {
	session.sendBuffer <- packet
}
<|endoftext|>