Commit 27efa3ed authored by AlexStocks's avatar AlexStocks

add wss-echo

parent bebfe7b7
...@@ -56,15 +56,11 @@ type ( ...@@ -56,15 +56,11 @@ type (
LocalHost string `default:"127.0.0.1"` LocalHost string `default:"127.0.0.1"`
// server // server
WSSEnable bool `default:"False"`
ServerHost string `default:"127.0.0.1"` ServerHost string `default:"127.0.0.1"`
ServerPort int `default:"10000"` ServerPort int `default:"10000"`
ServerPath string `default:"/echo"` ServerPath string `default:"/echo"`
ProfilePort int `default:"10086"` ProfilePort int `default:"10086"`
// cert
CertFile string
// session pool // session pool
ConnectionNum int `default:"16"` ConnectionNum int `default:"16"`
ConnectInterval string `default:"5s"` ConnectInterval string `default:"5s"`
......
...@@ -111,20 +111,11 @@ func newSession(session getty.Session) error { ...@@ -111,20 +111,11 @@ func newSession(session getty.Session) error {
} }
func initClient() { func initClient() {
if conf.WSSEnable { client.gettyClient = getty.NewClient(
client.gettyClient = getty.NewWSSClient( (int)(conf.ConnectionNum),
(int)(conf.ConnectionNum), conf.connectInterval,
conf.connectInterval, gxnet.WSHostAddress(conf.ServerHost, conf.ServerPort, conf.ServerPath),
gxnet.WSSHostAddress(conf.ServerHost, conf.ServerPort, conf.ServerPath), )
conf.CertFile,
)
} else {
client.gettyClient = getty.NewClient(
(int)(conf.ConnectionNum),
conf.connectInterval,
gxnet.WSHostAddress(conf.ServerHost, conf.ServerPort, conf.ServerPath),
)
}
client.gettyClient.RunEventLoop(newSession) client.gettyClient.RunEventLoop(newSession)
} }
......
...@@ -7,7 +7,6 @@ AppName = "ECHO-CLIENT" ...@@ -7,7 +7,6 @@ AppName = "ECHO-CLIENT"
LocalHost = "127.0.0.1" LocalHost = "127.0.0.1"
# server # server
WSSEnable = true
# ServerHost = "127.0.0.1" # ServerHost = "127.0.0.1"
# ServerHost = "192.168.8.3" # ServerHost = "192.168.8.3"
ServerHost = "localhost" ServerHost = "localhost"
...@@ -15,9 +14,6 @@ ServerPort = 10000 ...@@ -15,9 +14,6 @@ ServerPort = 10000
ServerPath = "/echo" ServerPath = "/echo"
ProfilePort = 10080 ProfilePort = 10080
# cert
CertFile = "./conf/cert/client.crt"
# connection pool # connection pool
# 连接池连接数目 # 连接池连接数目
ConnectionNum = 2 ConnectionNum = 2
......
...@@ -40,15 +40,7 @@ $(function() { ...@@ -40,15 +40,7 @@ $(function() {
var $chatPage = $('.chat.page'); // The chatroom page var $chatPage = $('.chat.page'); // The chatroom page
// var socket = new WebSocket('ws://192.168.35.1:10000/echo'); // var socket = new WebSocket('ws://192.168.35.1:10000/echo');
// var socket = new WebSocket('wss://' + serverAddress + '/echo', { var socket = new WebSocket('ws://' + serverAddress + '/echo')
// // protocolVersion: 8,
// // origin: 'https://' + serverAddress,
// rejectUnauthorized: false,
// });
// http://stackoverflow.com/questions/7580508/getting-chrome-to-accept-self-signed-localhost-certificate#comment33762412_15076602
// chrome://flags/#allow-insecure-localhost
var socket = new WebSocket('wss://' + serverAddress + '/echo')
// // Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'. In default it is 'blob'. // // Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'. In default it is 'blob'.
// socket.binaryType = 'arraybuffer'; // socket.binaryType = 'arraybuffer';
// socket.binaryType = '' // socket.binaryType = ''
......
...@@ -69,12 +69,6 @@ type ( ...@@ -69,12 +69,6 @@ type (
FailFastTimeout string `default:"5s"` FailFastTimeout string `default:"5s"`
failFastTimeout time.Duration failFastTimeout time.Duration
// cert
// generate_cert -host ikuernto.com
CertFile string
KeyFile string
CACert string
// session tcp parameters // session tcp parameters
GettySessionParam GettySessionParam `required:"true"` GettySessionParam GettySessionParam `required:"true"`
} }
......
...@@ -154,14 +154,8 @@ func initServer() { ...@@ -154,14 +154,8 @@ func initServer() {
panic(fmt.Sprintf("server.Listen(tcp, addr:%s) = error{%#v}", addr, err)) panic(fmt.Sprintf("server.Listen(tcp, addr:%s) = error{%#v}", addr, err))
} }
// run server server.RunWSEventLoop(newSession, pathList[idx])
if conf.CertFile != "" && conf.KeyFile != "" { log.Debug("server bind addr{ws://%s/%s} ok!", addr, pathList[idx])
server.RunWSSEventLoop(newSession, pathList[idx], conf.CertFile, conf.KeyFile, conf.CACert)
log.Debug("server bind addr{wss://%s/%s} ok!", addr, pathList[idx])
} else {
server.RunWSEventLoop(newSession, pathList[idx])
log.Debug("server bind addr{ws://%s/%s} ok!", addr, pathList[idx])
}
serverList = append(serverList, server) serverList = append(serverList, server)
} }
} }
......
...@@ -22,11 +22,6 @@ SessionNumber = 700 ...@@ -22,11 +22,6 @@ SessionNumber = 700
# app # app
FailFastTimeout = "3s" FailFastTimeout = "3s"
# cert
CertFile = "./conf/cert/server.crt"
KeyFile = "./conf/cert/server.key"
# CACert = "./conf/cert/ca.crt"
# tcp # tcp
[GettySessionParam] [GettySessionParam]
CompressEncoding = true CompressEncoding = true
......
# ws-echo #
---
*getty websocket code examples of Echo Example*
## LICENSE ##
---
> LICENCE : Apache License 2.0
## develop history ##
---
- 2017/04/27
> improvement
* enable wss client just using cert file;
* js-client can connect wss server by allow-insecure-localhost (chrome://flags/#allow-insecure-localhost)
- 2016/11/19
> 1 add client/app/config.go:GettySessionParam{CompressEncoding} to test compress websocket compression extension.
>
> 2 add server/app/config.go:GettySessionParam{CompressEncoding} to test compress websocket compression extension.
>
> 3 Pls attention that ie does not support weboscket compression extension while the latest chrome support it while echo/ws-echo/server enable websocket compress function. I have not test firefox and edge.
> As of version 19, Chrome will apparently compress WebSocket traffic automatically when the server supports it.
/******************************************************
# DESC : echo client
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 17:24
# FILE : client.go
******************************************************/
package main
import (
"math/rand"
"sync"
"time"
)
import (
"github.com/AlexStocks/getty"
log "github.com/AlexStocks/log4go"
)
var (
reqID uint32
src = rand.NewSource(time.Now().UnixNano())
)
func init() {
rand.Seed(time.Now().UnixNano())
}
////////////////////////////////////////////////////////////////////
// echo client
////////////////////////////////////////////////////////////////////
type EchoClient struct {
lock sync.RWMutex
sessions []*clientEchoSession
gettyClient *getty.Client
}
func (this *EchoClient) isAvailable() bool {
if this.selectSession() == nil {
return false
}
return true
}
func (this *EchoClient) close() {
client.lock.Lock()
if client.gettyClient != nil {
for _, s := range this.sessions {
log.Info("close client session{%s, last active:%s, request number:%d}",
s.session.Stat(), s.session.GetActive().String(), s.reqNum)
s.session.Close()
}
client.gettyClient.Close()
client.gettyClient = nil
client.sessions = client.sessions[:0]
}
client.lock.Unlock()
}
func (this *EchoClient) selectSession() getty.Session {
// get route server session
this.lock.RLock()
defer this.lock.RUnlock()
count := len(this.sessions)
if count == 0 {
log.Debug("client session array is nil...")
return nil
}
return this.sessions[rand.Int31n(int32(count))].session
}
func (this *EchoClient) addSession(session getty.Session) {
log.Debug("add session{%s}", session.Stat())
if session == nil {
return
}
this.lock.Lock()
this.sessions = append(this.sessions, &clientEchoSession{session: session})
this.lock.Unlock()
}
func (this *EchoClient) removeSession(session getty.Session) {
if session == nil {
return
}
this.lock.Lock()
for i, s := range this.sessions {
if s.session == session {
this.sessions = append(this.sessions[:i], this.sessions[i+1:]...)
log.Debug("delete session{%s}, its index{%d}", session.Stat(), i)
break
}
}
log.Info("after remove session{%s}, left session number:%d", session.Stat(), len(this.sessions))
this.lock.Unlock()
}
func (this *EchoClient) updateSession(session getty.Session) {
if session == nil {
return
}
this.lock.Lock()
for i, s := range this.sessions {
if s.session == session {
this.sessions[i].reqNum++
break
}
}
this.lock.Unlock()
}
func (this *EchoClient) getClientEchoSession(session getty.Session) (clientEchoSession, error) {
var (
err error
echoSession clientEchoSession
)
this.lock.Lock()
err = errSessionNotExist
for _, s := range this.sessions {
if s.session == session {
echoSession = *s
err = nil
break
}
}
this.lock.Unlock()
return echoSession, err
}
/******************************************************
# DESC : env var & configure
# MAINTAINER : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 16:53
# FILE : config.go
******************************************************/
package main
import (
"fmt"
"os"
"path"
"time"
)
import (
// "github.com/AlexStocks/gocolor"
log "github.com/AlexStocks/log4go"
config "github.com/koding/multiconfig"
)
const (
APP_CONF_FILE string = "APP_CONF_FILE"
APP_LOG_CONF_FILE string = "APP_LOG_CONF_FILE"
)
var (
conf *Config
)
type (
GettySessionParam struct {
CompressEncoding bool `default:"false"`
TcpNoDelay bool `default:"true"`
TcpKeepAlive bool `default:"true"`
TcpRBufSize int `default:"262144"`
TcpWBufSize int `default:"65536"`
PkgRQSize int `default:"1024"`
PkgWQSize int `default:"1024"`
TcpReadTimeout string `default:"1s"`
tcpReadTimeout time.Duration
TcpWriteTimeout string `default:"5s"`
tcpWriteTimeout time.Duration
WaitTimeout string `default:"7s"`
waitTimeout time.Duration
SessionName string `default:"echo-client"`
}
// Config holds supported types by the multiconfig package
Config struct {
// local
AppName string `default:"echo-client"`
LocalHost string `default:"127.0.0.1"`
// server
WSSEnable bool `default:"False"`
ServerHost string `default:"127.0.0.1"`
ServerPort int `default:"10000"`
ServerPath string `default:"/echo"`
ProfilePort int `default:"10086"`
// cert
CertFile string
// session pool
ConnectionNum int `default:"16"`
ConnectInterval string `default:"5s"`
connectInterval time.Duration
// heartbeat
HeartbeatPeriod string `default:"15s"`
heartbeatPeriod time.Duration
// session
SessionTimeout string `default:"60s"`
sessionTimeout time.Duration
// echo
EchoString string `default:"hello"`
EchoTimes int `default:"10"`
// app
FailFastTimeout string `default:"5s"`
failFastTimeout time.Duration
// session tcp parameters
GettySessionParam GettySessionParam `required:"true"`
}
)
func initConf() {
var (
err error
confFile string
)
// configure
confFile = os.Getenv(APP_CONF_FILE)
if confFile == "" {
panic(fmt.Sprintf("application configure file name is nil"))
return // I know it is of no usage. Just Err Protection.
}
if path.Ext(confFile) != ".toml" {
panic(fmt.Sprintf("application configure file name{%v} suffix must be .toml", confFile))
return
}
conf = new(Config)
config.MustLoadWithPath(confFile, conf)
conf.connectInterval, err = time.ParseDuration(conf.ConnectInterval)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(ConnectionInterval{%#v}) = error{%v}", conf.ConnectInterval, err))
return
}
conf.heartbeatPeriod, err = time.ParseDuration(conf.HeartbeatPeriod)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(HeartbeatPeroid{%#v}) = error{%v}", conf.HeartbeatPeriod, err))
return
}
conf.sessionTimeout, err = time.ParseDuration(conf.SessionTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.SessionTimeout, err))
return
}
conf.failFastTimeout, err = time.ParseDuration(conf.FailFastTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(FailFastTimeout{%#v}) = error{%v}", conf.FailFastTimeout, err))
return
}
conf.GettySessionParam.tcpReadTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpReadTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(TcpReadTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpReadTimeout, err))
return
}
conf.GettySessionParam.tcpWriteTimeout, err = time.ParseDuration(conf.GettySessionParam.TcpWriteTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(TcpWriteTimeout{%#v}) = error{%v}", conf.GettySessionParam.TcpWriteTimeout, err))
return
}
conf.GettySessionParam.waitTimeout, err = time.ParseDuration(conf.GettySessionParam.WaitTimeout)
if err != nil {
panic(fmt.Sprintf("time.ParseDuration(WaitTimeout{%#v}) = error{%v}", conf.GettySessionParam.WaitTimeout, err))
return
}
// gocolor.Info("config{%#v}\n", conf)
// log
confFile = os.Getenv(APP_LOG_CONF_FILE)
if confFile == "" {
panic(fmt.Sprintf("log configure file name is nil"))
return
}
if path.Ext(confFile) != ".xml" {
panic(fmt.Sprintf("log configure file name{%v} suffix must be .xml", confFile))
return
}
log.LoadConfiguration(confFile)
log.Info("config{%#v}", conf)
return
}
/******************************************************
# DESC : echo package
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-08-22 17:44
# FILE : echo.go
******************************************************/
package main
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"unsafe"
)
import (
log "github.com/AlexStocks/log4go"
)
////////////////////////////////////////////
// echo command
////////////////////////////////////////////
type echoCommand uint32
const (
echoCmd = iota
)
var echoCommandStrings = [...]string{
"echo",
}
func (c echoCommand) String() string {
return echoCommandStrings[c]
}
////////////////////////////////////////////
// EchoPkgHandler
////////////////////////////////////////////
const (
echoPkgMagic = 0x20160905
maxEchoStringLen = 0xff
)
var (
ErrNotEnoughSteam = errors.New("packet stream is not enough")
ErrTooLargePackage = errors.New("package length is exceed the echo package's legal maximum length.")
ErrIllegalMagic = errors.New("package magic is not right.")
)
var (
echoPkgHeaderLen int
)
func init() {
echoPkgHeaderLen = (int)((uint)(unsafe.Sizeof(EchoPkgHeader{})))
}
type EchoPkgHeader struct {
Magic uint32
LogID uint32 // log id
Sequence uint32 // request/response sequence
ServiceID uint32 // service id
Command uint32 // operation command code
Code int32 // error code
Len uint16 // body length
_ uint16
_ int32 // reserved, maybe used as package md5 checksum
}
type EchoPackage struct {
H EchoPkgHeader
B string
}
func (this EchoPackage) String() string {
return fmt.Sprintf("log id:%d, sequence:%d, command:%s, echo string:%s",
this.H.LogID, this.H.Sequence, (echoCommand(this.H.Command)).String(), this.B)
}
func (this EchoPackage) Marshal() (*bytes.Buffer, error) {
var (
err error
buf *bytes.Buffer
)
buf = &bytes.Buffer{}
err = binary.Write(buf, binary.LittleEndian, this.H)
if err != nil {
return nil, err
}
buf.WriteByte((byte)(len(this.B)))
buf.WriteString(this.B)
return buf, nil
}
func (this *EchoPackage) Unmarshal(buf *bytes.Buffer) (int, error) {
var (
err error
len byte
)
if buf.Len() < echoPkgHeaderLen {
return 0, ErrNotEnoughSteam
}
// header
err = binary.Read(buf, binary.LittleEndian, &(this.H))
if err != nil {
return 0, err
}
if this.H.Magic != echoPkgMagic {
log.Error("@this.H.Magic{%x}, right magic{%x}", this.H.Magic, echoPkgMagic)
return 0, ErrIllegalMagic
}
if buf.Len() < (int)(this.H.Len) {
return 0, ErrNotEnoughSteam
}
if maxEchoStringLen < this.H.Len-1 {
return 0, ErrTooLargePackage
}
len, err = buf.ReadByte()
if err != nil {
return 0, nil
}
this.B = (string)(buf.Next((int)(len)))
return (int)(this.H.Len) + echoPkgHeaderLen, nil
}
/******************************************************
# DESC : echo package handler
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-04 13:08
# FILE : handler.go
******************************************************/
package main
import (
"errors"
"time"
)
import (
"github.com/AlexStocks/getty"
log "github.com/AlexStocks/log4go"
)
var (
errSessionNotExist = errors.New("session not exist!")
)
////////////////////////////////////////////
// EchoMessageHandler
////////////////////////////////////////////
type clientEchoSession struct {
session getty.Session
reqNum int32
}
type EchoMessageHandler struct {
}
func newEchoMessageHandler() *EchoMessageHandler {
return &EchoMessageHandler{}
}
func (this *EchoMessageHandler) OnOpen(session getty.Session) error {
client.addSession(session)
return nil
}
func (this *EchoMessageHandler) OnError(session getty.Session, err error) {
log.Info("session{%s} got error{%v}, will be closed.", session.Stat(), err)
client.removeSession(session)
}
func (this *EchoMessageHandler) OnClose(session getty.Session) {
log.Info("session{%s} is closing......", session.Stat())
client.removeSession(session)
}
func (this *EchoMessageHandler) OnMessage(session getty.Session, pkg interface{}) {
p, ok := pkg.(*EchoPackage)
if !ok {
log.Error("illegal packge{%#v}", pkg)
return
}
log.Debug("get echo package{%s}", p)
client.updateSession(session)
}
func (this *EchoMessageHandler) OnCron(session getty.Session) {
clientEchoSession, err := client.getClientEchoSession(session)
if err != nil {
log.Error("client.getClientSession(session{%s}) = error{%#v}", session.Stat(), err)
return
}
if conf.sessionTimeout.Nanoseconds() < time.Since(session.GetActive()).Nanoseconds() {
log.Warn("session{%s} timeout{%s}, reqNum{%d}",
session.Stat(), time.Since(session.GetActive()).String(), clientEchoSession.reqNum)
client.removeSession(session)
return
}
}
/******************************************************
# DESC : echo client app
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 17:24
# FILE : main.go
******************************************************/
package main
import (
// "flag"
"fmt"
"net"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
// "strings"
"crypto/tls"
"sync/atomic"
"syscall"
"time"
)
import (
"github.com/AlexStocks/getty"
"github.com/AlexStocks/gocolor"
"github.com/AlexStocks/goext/net"
"github.com/AlexStocks/goext/time"
log "github.com/AlexStocks/log4go"
)
const (
pprofPath = "/debug/pprof/"
)
var (
client EchoClient
)
////////////////////////////////////////////////////////////////////
// main
////////////////////////////////////////////////////////////////////
func main() {
initConf()
initProfiling()
initClient()
gocolor.Info("%s starts successfull! its version=%s\n", conf.AppName, Version)
log.Info("%s starts successfull! its version=%s\n", conf.AppName, Version)
go test()
initSignal()
}
func initProfiling() {
var (
addr string
)
addr = gxnet.HostAddress(conf.LocalHost, conf.ProfilePort)
log.Info("App Profiling startup on address{%v}", addr+pprofPath)
go func() {
log.Info(http.ListenAndServe(addr, nil))
}()
}
func newSession(session getty.Session) error {
var (
flag1, flag2 bool
tcpConn *net.TCPConn
)
_, flag1 = session.Conn().(*tls.Conn)
tcpConn, flag2 = session.Conn().(*net.TCPConn)
if !flag1 && !flag2 {
panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp/tls connection\n", session.Stat(), session.Conn()))
}
if conf.GettySessionParam.CompressEncoding {
session.SetCompressType(getty.CompressZip)
}
// else {
// session.SetCompressType(getty.CompressNone)
//}
if flag2 {
tcpConn.SetNoDelay(conf.GettySessionParam.TcpNoDelay)
tcpConn.SetKeepAlive(conf.GettySessionParam.TcpKeepAlive)
tcpConn.SetReadBuffer(conf.GettySessionParam.TcpRBufSize)
tcpConn.SetWriteBuffer(conf.GettySessionParam.TcpWBufSize)
}
session.SetName(conf.GettySessionParam.SessionName)
session.SetPkgHandler(NewEchoPackageHandler())
session.SetEventListener(newEchoMessageHandler())
session.SetRQLen(conf.GettySessionParam.PkgRQSize)
session.SetWQLen(conf.GettySessionParam.PkgWQSize)
session.SetReadDeadline(conf.GettySessionParam.tcpReadTimeout)
session.SetWriteDeadline(conf.GettySessionParam.tcpWriteTimeout)
session.SetCronPeriod((int)(conf.heartbeatPeriod.Nanoseconds() / 1e6))
session.SetWaitTime(conf.GettySessionParam.waitTimeout)
log.Debug("client new session:%s\n", session.Stat())
return nil
}
func initClient() {
if conf.WSSEnable {
client.gettyClient = getty.NewWSSClient(
(int)(conf.ConnectionNum),
conf.connectInterval,
gxnet.WSSHostAddress(conf.ServerHost, conf.ServerPort, conf.ServerPath),
conf.CertFile,
)
} else {
client.gettyClient = getty.NewClient(
(int)(conf.ConnectionNum),
conf.connectInterval,
gxnet.WSHostAddress(conf.ServerHost, conf.ServerPort, conf.ServerPath),
)
}
client.gettyClient.RunEventLoop(newSession)
}
func uninitClient() {
client.close()
}
func initSignal() {
// signal.Notify的ch信道是阻塞的(signal.Notify不会阻塞发送信号), 需要设置缓冲
signals := make(chan os.Signal, 1)
// It is not possible to block SIGKILL or syscall.SIGSTOP
signal.Notify(signals, os.Interrupt, os.Kill, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
sig := <-signals
log.Info("get signal %s", sig.String())
switch sig {
case syscall.SIGHUP:
// reload()
default:
go time.AfterFunc(conf.failFastTimeout, func() {
// log.Warn("app exit now by force...")
// os.Exit(1)
log.Exit("app exit now by force...")
log.Close()
})
// 要么survialTimeout时间内执行完毕下面的逻辑然后程序退出,要么执行上面的超时函数程序强行退出
uninitClient()
// fmt.Println("app exit now...")
log.Exit("app exit now...")
log.Close()
return
}
}
}
func echo() {
var pkg EchoPackage
pkg.H.Magic = echoPkgMagic
pkg.H.LogID = (uint32)(src.Int63())
pkg.H.Sequence = atomic.AddUint32(&reqID, 1)
// pkg.H.ServiceID = 0
pkg.H.Command = echoCmd
pkg.B = conf.EchoString
pkg.H.Len = (uint16)(len(pkg.B)) + 1
if session := client.selectSession(); session != nil {
err := session.WritePkg(&pkg)
if err != nil {
log.Warn("session.WritePkg(session{%s}, pkg{%s}) = error{%v}", session.Stat(), pkg, err)
session.Close()
client.removeSession(session)
}
}
}
func test() {
for {
if client.isAvailable() {
break
}
time.Sleep(1e6)
}
var (
cost int64
counter gxtime.CountWatch
)
counter.Start()
for i := 0; i < conf.EchoTimes; i++ {
echo()
}
cost = counter.Count()
log.Info("after loop %d times, echo cost %d ms", conf.EchoTimes, cost/1e6)
gocolor.Info("after loop %d times, echo cost %d ms", conf.EchoTimes, cost/1e6)
}
/******************************************************
# DESC : echo stream parser
# AUTHOR : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-04 13:08
# FILE : readwriter.go
******************************************************/
package main
import (
"bytes"
"errors"
"time"
)
import (
"github.com/AlexStocks/getty"
log "github.com/AlexStocks/log4go"
)
type EchoPackageHandler struct {
}
func NewEchoPackageHandler() *EchoPackageHandler {
return &EchoPackageHandler{}
}
func (this *EchoPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) {
var (
err error
len int
pkg EchoPackage
buf *bytes.Buffer
)
buf = bytes.NewBuffer(data)
len, err = pkg.Unmarshal(buf)
if err != nil {
if err == ErrNotEnoughSteam {
return nil, 0, nil
}
return nil, 0, err
}
return &pkg, len, nil
}
func (this *EchoPackageHandler) Write(ss getty.Session, pkg interface{}) error {
var (
ok bool
err error
startTime time.Time
echoPkg *EchoPackage
buf *bytes.Buffer
)
startTime = time.Now()
if echoPkg, ok = pkg.(*EchoPackage); !ok {
log.Error("illegal pkg:%+v\n", pkg)
return errors.New("invalid echo package!")
}
buf, err = echoPkg.Marshal()
if err != nil {
log.Warn("binary.Write(echoPkg{%#v}) = err{%#v}", echoPkg, err)
return err
}
err = ss.WriteBytes(buf.Bytes())
log.Info("WriteEchoPkgTimeMs = %s", time.Since(startTime).String())
return err
}
/******************************************************
# DESC : echo client version
# MAINTAINER : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 11:23
# FILE : version.go
******************************************************/
package main
var (
Version = "0.4.04"
)
#!/usr/bin/env bash
# ******************************************************
# DESC : getty app devops script
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : LGPL V3
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-05-13 02:01
# FILE : load.sh
# ******************************************************
APP_NAME="APPLICATION_NAME"
APP_ARGS=""
PROJECT_HOME=""
OS_NAME=`uname`
if [[ ${OS_NAME} == "Linux" ]]; then
PROJECT_HOME=`pwd`
PROJECT_HOME=${PROJECT_HOME}"/"
fi
export APP_CONF_FILE=${PROJECT_HOME}"TARGET_CONF_FILE"
export APP_LOG_CONF_FILE=${PROJECT_HOME}"TARGET_LOG_CONF_FILE"
usage() {
echo "Usage: $0 start"
echo " $0 stop"
echo " $0 term"
echo " $0 restart"
echo " $0 list"
exit
}
start() {
APP_LOG_PATH=${PROJECT_HOME}"logs/"
mkdir -p ${APP_LOG_PATH}
APP_BIN=${PROJECT_HOME}sbin/${APP_NAME}
chmod u+x ${APP_BIN}
# CMD="nohup ${APP_BIN} ${APP_ARGS} >>${APP_NAME}.nohup.out 2>&1 &"
CMD="${APP_BIN}"
eval ${CMD}
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'`
if [[ ${OS_NAME} != "Linux" ]]; then
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'`
fi
if [ "${PID}" != "" ];
then
for p in ${PID}
do
echo "start ${APP_NAME} ( pid =" ${p} ")"
done
fi
}
stop() {
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'`
if [[ ${OS_NAME} != "Linux" ]]; then
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'`
fi
if [ "${PID}" != "" ];
then
for ps in ${PID}
do
echo "kill -SIGINT ${APP_NAME} ( pid =" ${ps} ")"
kill -2 ${ps}
done
fi
}
term() {
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $2}'`
if [[ ${OS_NAME} != "Linux" ]]; then
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{print $1}'`
fi
if [ "${PID}" != "" ];
then
for ps in ${PID}
do
echo "kill -9 ${APP_NAME} ( pid =" ${ps} ")"
kill -9 ${ps}
done
fi
}
list() {
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s\n", $1, $2, $9, $10)}'`
if [[ ${OS_NAME} != "Linux" ]]; then
PID=`ps aux | grep -w ${APP_NAME} | grep -v grep | awk '{printf("%s,%s,%s,%s,%s\n", $1, $4, $6, $7, $8)}'`
fi
if [ "${PID}" != "" ];
then
echo "list ${APP_NAME}"
if [[ ${OS_NAME} == "Linux" ]]; then
echo "index: user, pid, start, duration"
else
echo "index: PID, WINPID, UID, STIME, COMMAND"
fi
idx=0
for ps in ${PID}
do
echo "${idx}: ${ps}"
((idx ++))
done
fi
}
opt=$1
case C"$opt" in
Cstart)
start
;;
Cstop)
stop
;;
Cterm)
term
;;
Crestart)
term
start
;;
Clist)
list
;;
C*)
usage
;;
esac
# getty application configure script
# ******************************************************
# DESC : application environment variable
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:29
# FILE : app.properties
# ******************************************************
TARGET_EXEC_NAME="echo_client"
BUILD_PACKAGE="app"
TARGET_CONF_FILE="conf/config.toml"
TARGET_LOG_CONF_FILE="conf/log.xml"
#!/usr/bin/env bash
# ******************************************************
# DESC : build script
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:28
# FILE : build.sh
# ******************************************************
rm -rf target/
PROJECT_HOME=`pwd`
TARGET_FOLDER=${PROJECT_HOME}/target/${GOOS}
TARGET_SBIN_NAME=${TARGET_EXEC_NAME}
version=`cat app/version.go | grep Version | awk -F '=' '{print $2}' | awk -F '"' '{print $2}'`
if [[ ${GOOS} == "windows" ]]; then
TARGET_SBIN_NAME=${TARGET_SBIN_NAME}.exe
fi
TARGET_NAME=${TARGET_FOLDER}/${TARGET_SBIN_NAME}
if [[ $PROFILE = "test" ]]; then
# GFLAGS=-gcflags "-N -l" -race -x -v # -x会把go build的详细过程输出
# GFLAGS=-gcflags "-N -l" -race -v
# GFLAGS="-gcflags \"-N -l\" -v"
cd ${BUILD_PACKAGE} && go build -gcflags "-N -l" -x -v -i -o ${TARGET_NAME} && cd -
else
# -s去掉符号表(然后panic时候的stack trace就没有任何文件名/行号信息了,这个等价于普通C/C++程序被strip的效果),
# -w去掉DWARF调试信息,得到的程序就不能用gdb调试了。-s和-w也可以分开使用,一般来说如果不打算用gdb调试,
# -w基本没啥损失。-s的损失就有点大了。
cd ${BUILD_PACKAGE} && go build -ldflags "-w" -x -v -i -o ${TARGET_NAME} && cd -
fi
TAR_NAME=${TARGET_EXEC_NAME}-${version}-`date "+%Y%m%d-%H%M"`-${PROFILE}
mkdir -p ${TARGET_FOLDER}/${TAR_NAME}
SBIN_DIR=${TARGET_FOLDER}/${TAR_NAME}/sbin
BIN_DIR=${TARGET_FOLDER}/${TAR_NAME}
CONF_DIR=${TARGET_FOLDER}/${TAR_NAME}/conf
mkdir -p ${SBIN_DIR}
mkdir -p ${CONF_DIR}
mv ${TARGET_NAME} ${SBIN_DIR}
cp -r assembly/bin ${BIN_DIR}
# modify APPLICATION_NAME
if [ "$(uname)" == "Darwin" ]; then
sed -i "" "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/*
else
sed -i "s~APPLICATION_NAME~${TARGET_EXEC_NAME}~g" ${BIN_DIR}/bin/*
fi
# modify TARGET_CONF_FILE
if [ "$(uname)" == "Darwin" ]; then
sed -i "" "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/*
else
sed -i "s~TARGET_CONF_FILE~${TARGET_CONF_FILE}~g" ${BIN_DIR}/bin/*
fi
# modify TARGET_LOG_CONF_FILE
if [ "$(uname)" == "Darwin" ]; then
sed -i "" "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/*
else
sed -i "s~TARGET_LOG_CONF_FILE~${TARGET_LOG_CONF_FILE}~g" ${BIN_DIR}/bin/*
fi
cp -r profiles/${PROFILE}/* ${CONF_DIR}
cd ${TARGET_FOLDER}
tar czf ${TAR_NAME}.tar.gz ${TAR_NAME}/*
#!/usr/bin/env bash
# ******************************************************
# DESC : build script for test env
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : LGPL V3
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:34
# FILE : test.sh
# ******************************************************
set -e
export GOOS=linux
export GOARCH=amd64
PROFILE=test
PROJECT_HOME=`pwd`
if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then
. ${PROJECT_HOME}/assembly/common/app.properties
fi
if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then
. ${PROJECT_HOME}/assembly/common/build.sh
fi
#!/usr/bin/env bash
# ******************************************************
# DESC : build script for test env
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:34
# FILE : test.sh
# ******************************************************
set -e
export GOOS=darwin
export GOARCH=amd64
PROFILE=test
PROJECT_HOME=`pwd`
if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then
. ${PROJECT_HOME}/assembly/common/app.properties
fi
if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then
. ${PROJECT_HOME}/assembly/common/build.sh
fi
#!/usr/bin/env bash
# ******************************************************
# DESC : build script for test env
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:34
# FILE : test.sh
# ******************************************************
set -e
export GOOS=windows
export GOARCH=amd64
PROFILE=test
PROJECT_HOME=`pwd`
if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then
. ${PROJECT_HOME}/assembly/common/app.properties
fi
if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then
. ${PROJECT_HOME}/assembly/common/build.sh
fi
#!/usr/bin/env bash
# ******************************************************
# DESC : build script for test env
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:34
# FILE : test.sh
# ******************************************************
set -e
export GOOS=windows
export GOARCH=386
PROFILE=test
PROJECT_HOME=`pwd`
if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then
. ${PROJECT_HOME}/assembly/common/app.properties
fi
if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then
. ${PROJECT_HOME}/assembly/common/build.sh
fi
-----BEGIN CERTIFICATE-----
MIICHjCCAYegAwIBAgIQKpKqamBqmZ0hfp8sYb4uNDANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQC5Nxsk6WjeaYazRYiGxHZ5G3FXSlSjV7lZeebItdEPzO8kVPIGCSTy/M5X
Nnpp3uVDFXQub0/O5t9Y6wcuqpUGMOV+XL7MZqSZlodXm0XhNYzCAjZ+URNjTHGP
NXIqdDEG5Ba8SXMOfY6H97+QxugZoAMFZ+N83ggr12IYNO/FbQIDAQABo3MwcTAO
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
AwEB/zA5BgNVHREEMjAwgglsb2NhbGhvc3SCC2V4YW1wbGUuY29thwR/AAABhxAA
AAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4GBAE5dr9q7ORmKZ7yZqeSL
305armc13A7UxffUajeJFujpl2jOqnb5PuKJ7fn5HQKGB0qSq3IHsFua2WONXcTW
Vn4gS0k50IaDpW+yl+ArIo0QwbjPIAcFysX10p9dVO7A1uEpHbRDzefem6r9uVGk
i7dOLEoC8hkfk6nJsNEIEqu6
-----END CERTIFICATE-----
# toml configure file
# toml中key的首字母可以小写,但是对应的golang中的struct成员首字母必须大写
AppName = "ECHO-CLIENT"
# host
LocalHost = "127.0.0.1"
# server
WSSEnable = true
# ServerHost = "127.0.0.1"
# ServerHost = "192.168.8.3"
ServerHost = "localhost"
ServerPort = 10000
ServerPath = "/echo"
ProfilePort = 10080
# cert
CertFile = "./conf/cert/client.crt"
# connection pool
# 连接池连接数目
ConnectionNum = 2
# 当连接失败或者连接断开时,连接池中重连的间隔时间
ConnectInterval = "5s"
# session
# client与server之间连接的心跳周期
HeartbeatPeriod = "10s"
# client与server之间连接的超时时间
SessionTimeout = "20s"
# client
# client echo request string
# EchoString = "Hello, getty!"
# for compress test
EchoString = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
# 发送echo请求次数
EchoTimes = 10
# app fail fast
FailFastTimeout = "3s"
# tcp
[GettySessionParam]
CompressEncoding = true
TcpNoDelay = true
TcpKeepAlive = true
TcpRBufSize = 262144
TcpWBufSize = 65536
PkgRQSize = 512
PkgWQSize = 256
TcpReadTimeout = "1s"
TcpWriteTimeout = "5s"
WaitTimeout = "1s"
SessionName = "echo-client"
<logging>
<filter enabled="true">
<tag>stdout</tag>
<type>console</type>
<!-- level is (:?FINEST|FINE|DEBUG|TRACE|INFO|WARNING|ERROR) -->
<level>INFO</level>
</filter>
<filter enabled="false">
<tag>debug_file</tag>
<type>file</type>
<level>DEBUG</level>
<property name="filename">logs/debug.log</property>
<property name="format">[%D %T] [%L] [%S] %M</property>
<property name="rotate">true</property> <!-- true enables log rotation, otherwise append -->
<property name="maxsize">0M</property> <!-- \d+[KMG]? Suffixes are in terms of 2**10 -->
<property name="maxlines">0K</property> <!-- \d+[KMG]? Suffixes are in terms of thousands -->
<property name="daily">true</property> <!-- Automatically rotates when a log message is written after midnight -->
</filter>
<filter enabled="true">
<tag>info_file</tag>
<type>file</type>
<level>INFO</level>
<property name="filename">logs/info.log</property>
<!--
%T - Time (15:04:05 MST)
%t - Time (15:04)
%D - Date (2006/01/02)
%d - Date (01/02/06)
%L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)
%S - Source
%M - Message
It ignores unknown format strings (and removes them)
Recommended: "[%D %T] [%L] (%S) %M"
-->
<property name="format">[%D %T] [%L] [%S] %M</property>
<property name="rotate">true</property> <!-- true enables log rotation, otherwise append -->
<property name="maxsize">0M</property> <!-- \d+[KMG]? Suffixes are in terms of 2**10 -->
<property name="maxlines">0K</property> <!-- \d+[KMG]? Suffixes are in terms of thousands -->
<property name="daily">true</property> <!-- Automatically rotates when a log message is written after midnight -->
</filter>
<filter enabled="true">
<tag>warn_file</tag>
<type>file</type>
<level>WARNING</level>
<property name="filename">logs/warn.log</property>
<property name="format">[%D %T] [%L] [%S] %M</property>
<property name="rotate">true</property> <!-- true enables log rotation, otherwise append -->
<property name="maxsize">0M</property> <!-- \d+[KMG]? Suffixes are in terms of 2**10 -->
<property name="maxlines">0K</property> <!-- \d+[KMG]? Suffixes are in terms of thousands -->
<property name="daily">true</property> <!-- Automatically rotates when a log message is written after midnight -->
</filter>
<filter enabled="true">
<tag>error_file</tag>
<type>file</type>
<level>ERROR</level>
<property name="filename">logs/error.log</property>
<property name="format">[%D %T] [%L] [%S] %M</property>
<property name="rotate">true</property> <!-- true enables log rotation, otherwise append -->
<property name="maxsize">0M</property> <!-- \d+[KMG]? Suffixes are in terms of 2**10 -->
<property name="maxlines">0K</property> <!-- \d+[KMG]? Suffixes are in terms of thousands -->
<property name="daily">true</property> <!-- Automatically rotates when a log message is written after midnight -->
</filter>
</logging>
// var serverAddress = '192.168.8.3:10000';
var serverAddress = '127.0.0.1:10000';
// var serverAddress = 'localhost:10000';
This diff is collapsed.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Echo Example</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<ul class="pages">
<li class="chat page">
<div class="chatArea">
<ul class="messages"></ul>
</div>
<input class="inputMessage" placeholder=":img url"/>
</li>
</ul>
<script src="jquery-1.10.2.min.js"></script>
<script src="encoding.js"></script>
<script src="struct.min.js"></script>
<script src="addr.js"></script>
<script src="main.js"></script>
</body>
</html>
This diff is collapsed.
$(function() {
var FADE_TIME = 150; // ms
var TYPING_TIMER_LENGTH = 400; // ms
var COLORS = [
'#e21400', '#91580f', '#f8a700', '#f78b00',
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
];
var seq = 0;
var echoCommand = 0x00;
/**
* create struct
*
* @param {object} data object
* @param {number} default value[optional, default 0]
* @param {boolean} endian[optional, default true]
*/
var echoPkgHeader = new Struct ({
Magic:'uint32',
LogID:'uint32', // log id
Sequence: 'uint32', // request/response sequence
ServiceID:'uint32', // service id
Command:'uint32', // operation command code
Code :'int32', // error code
Len:'uint16', // body length
extra1:'uint16',
extra2:'int32' // reserved, maybe used as package md5 checksum
}, 0, true);
// Initialize varibles
var $window = $(window);
var $messages = $('.messages'); // Messages area
var $inputMessage = $('.inputMessage'); // Input message input box
var $chatPage = $('.chat.page'); // The chatroom page
// var socket = new WebSocket('ws://192.168.35.1:10000/echo');
// var socket = new WebSocket('wss://' + serverAddress + '/echo', {
// // protocolVersion: 8,
// // origin: 'https://' + serverAddress,
// rejectUnauthorized: false,
// });
// http://stackoverflow.com/questions/7580508/getting-chrome-to-accept-self-signed-localhost-certificate#comment33762412_15076602
// chrome://flags/#allow-insecure-localhost
var socket = new WebSocket('wss://' + serverAddress + '/echo')
// // Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'. In default it is 'blob'.
// socket.binaryType = 'arraybuffer';
// socket.binaryType = ''
function socketSend (o, silence) {
if (socket.readyState != socket.OPEN) {
if (!silence) {
addChatMessage({
Message: '!!Connection closed'
});
}
return
}
socket.send(JSON.stringify(o))
}
function marshalEchoPkg(header, msg) {
var tmp = new Uint8Array(header.byteLength + 1 + msg.length);
tmp.set(new Uint8Array(header), 0);
tmp[header.byteLength] = msg.length
var ma = new TextEncoder("utf-8").encode(msg);
tmp.set(ma, header.byteLength + 1);
return tmp;
// return tmp.buffer;
}
function AB2Str(buf) {
var decoder = new TextDecoder('utf-8');
var msg = decoder.decode(new DataView(buf));
return msg;
}
function unmarshalEchoPkg(data) {
var dataAB = new Uint8Array(data);
var hLen = echoPkgHeader.byteLength;
var rspHeaderData = dataAB.subarray(0, hLen);
var rspHeader = echoPkgHeader.read(rspHeaderData.buffer);
// console.log("echo rsp package header:" + rspHeader.Magic);
// console.log(rspHeader.Len)
// console.log('echo response{seq:' + rspHeader.Sequence + ', msg len:' + dataAB[hLen] + '}');
addChatMessage({Message:
'echo response{seq:' + rspHeader.Sequence +
', msg len:' + (dataAB[hLen]) +
', msg:' + AB2Str(new Uint8Array(dataAB.subarray(hLen + 1)).buffer) +
'}'
}
);
}
// Sends a chat message
function sendMessage () {
var message = $inputMessage.val();
// Prevent markup from being injected into the message
message = cleanInput(message);
// Prevent markup from being injected into the message
// if there is a non-empty message and a socket connection
if (message) {
$inputMessage.val('');
addChatMessage({
Message: message
});
var pkgHeaderArrayBuffer = echoPkgHeader.write({
Magic:0x20160905,
LogID:Math.round(Math.random() * 2147483647),
Command:echoCommand,
Sequence:seq++,
Code:0,
Len:message.length + 1,
extra1:0,
extra2:0
});
socket.send(marshalEchoPkg(pkgHeaderArrayBuffer, message));
}
}
//断开连接
function disconnect() {
if (socket != null) {
socket.close();
socket = null;
addChatMessage({
Message: '!!SYSTEM-WS-Close, connection disconnect'
})
}
}
// Log a message
function log (message, options) {
var $el = $('<li>').addClass('log').text(message);
addMessageElement($el, options);
}
var imgReg = /:img\s+(\S+)/
// Adds the visual chat message to the message list
function addChatMessage (data, options) {
// Don't fade the message in if there is an 'X was typing'
options = options || {};
var regRes = imgReg.exec(data.Message)
if (regRes != null) {
var $messageBodyDiv = $('<img src="' + regRes[1] + '">');
} else {
var $messageBodyDiv = $('<span class="messageBody">')
.text(data.Message);
}
var typingClass = data.Typing ? 'typing' : '';
var $messageDiv = $('<li class="message"/>')
.addClass(typingClass)
.append($messageBodyDiv);
addMessageElement($messageDiv, options);
}
// Adds a message element to the messages and scrolls to the bottom
// el - The element to add as a message
// options.fade - If the element should fade-in (default = true)
// options.prepend - If the element should prepend
// all other messages (default = false)
function addMessageElement (el, options) {
// console.log("@el:" + el)
// console.log("@options:" + options)
var $el = $(el);
// Setup default options
if (!options) {
options = {};
}
if (typeof options.fade === 'undefined') {
options.fade = true;
}
if (typeof options.prepend === 'undefined') {
options.prepend = false;
}
// Apply options
if (options.fade) {
$el.hide().fadeIn(FADE_TIME);
}
if (options.prepend) {
$messages.prepend($el);
} else {
$messages.append($el);
}
$messages[0].scrollTop = $messages[0].scrollHeight;
}
// Prevents input from having injected markup
function cleanInput (input) {
return $('<div/>').text(input).text();
}
$window.keydown(function (event) {
// When the client hits ENTER on their keyboard
if (event.which === 13) {
sendMessage();
}
});
// Click events
// Focus input when clicking on the message input's border
$inputMessage.click(function () {
$inputMessage.focus();
});
socket.onopen = function() {
console.log('websocket extensions:' + socket.extensions);
}
// Socket events
socket.onmessage = function(e) {
// e.data is blob
var fileReader = new FileReader(); // 用filereader来转blob为arraybuffer
fileReader.onload = function() {
var arrayBuffer = this.result; // 得到arraybuffer
var dv = new DataView(arrayBuffer);
unmarshalEchoPkg(dv.buffer)
};
fileReader.readAsArrayBuffer(e.data); // 此处读取blob
}
socket.onclose = function(e) {
// console.log("socket.onclose" + e.reason)
disconnect()
addChatMessage({
Message: e.reason + '!!SYSTEM-WS-Close, connection closed'
});
}
socket.onerror = function() {
addChatMessage({
Message: '!!SYSTEM-WS-Error, connection closed'
});
}
});
/*!
* C-Like Data Structure for JavaScript.
*
* @see Struct.js on GitHub <https://github.com/firejune/struct.js>
* @author Joon Kyoung <firejune@gmail.com>
* @license MIT
* @version 0.9.1
*
*/
(function(E,r){function A(a){return!!a&&a.constructor===C}function D(a){return!!a&&a.constructor===Error}function x(a){return!!a&&(a===Array||a.constructor===Array)}function F(a){return!!a&&(a===Object||a.constructor===Object)}function t(a){return!!a&&(a===String||a.constructor===String)}function w(a){return!!a&&(a===ArrayBuffer||a.constructor===ArrayBuffer)}function B(a){return a.charAt(0).toUpperCase()+a.slice(1)}function u(a,b){var c={},e=null,l;for(l in a)if(a.hasOwnProperty(l))if(e=a[l],F(e))c[l]=
u(e,b);else if(e=b(e,l,c)){c=e;break}return c}function G(a,b){for(var c in b)a.hasOwnProperty(c)&&(F(a[c])?a[c]=G(a[c],b[c]):a[c][1]=b[c]);return a}function H(a){var b={},c;for(c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}function J(a,b){return u(a,function(a,e,l){var k=x(a)&&a[0]||a,k=A(k)&&k||k.toLowerCase(),m=t(a)?b:a[1],g=x(a)&&3<=a.length&&a[2]||(t(m)||x(m)||w(m))&&m.length||w(m)&&m.byteLength||1;a=x(a)&&(t(g)||1<g)?t(g)&&a[3]===r&&!0||a[3]:!1;if(m===r||null===m)m="";l[e]=[k,m,g,a]})}function I(a){var b=
0;u(a,function(a){var e=a[0],l=a[1],k=a[2];a=a[3];var m=y[e],g="uint8";m===r&&A(e)&&(m=e.byteLength);if(t(k)||t(l))g=k,k=l&&l.length||0;w(l)&&(k=l.byteLength||0);!0===a&&(b+=y[g]);b+=k*m});return b}var C=function(a,b,c){this.endianness=c===r&&!0||c;this.defaultValue=b||0;this.struct=J(a,this.defaultValue);this.byteLength=I(this.struct);this.emptyBuffer=new ArrayBuffer(this.byteLength);this.constructor=C;this._debug=!1;this._struct={};this._debug&&console.log("STRUCT.CREATE","defaultValue:",this.defaultValue,
"byteLength:",this.byteLength,"endianness:",this.endianness,"struct:",this.struct)};C.prototype={update:function(a){this.struct=G(this.struct,a||{});this.byteLength=I(this.struct);this.emptyBuffer=new ArrayBuffer(this.byteLength);return H(this.struct)},read:function(a,b){if(a===r&&b===r)return u(this.struct,function(a,c,b){b[c]=a[1]});var c=this,e=this.endianness,l=a instanceof DataView&&a||new DataView(a);if(0===a.byteLength)return Error("Uncaught IndexSizeError: Buffer size was zero byte.");this.offset=
b||0;this._debug&&console.info("STRUCT.READ","byteLength:",a.byteLength,"readOffset:",this.offset);var k=u(this.struct,function(b,g,k){var d=[],q=b[0],h=b[1],p=b[2],r=b[3];b=y[q]||1;var f="uint8",v=0;t(p)&&(f=p,p=h.byteLength||h.length);if(a.byteLength<=c.offset)return Error("Uncaught IndexSizeError: Index or size was negative.");if(!0===r){try{p=l["get"+B(f)](c.offset,e)/b}catch(n){return console.error(n,c.offset,c.offset+b,a.byteLength),Error(n)}c.offset+=y[f];c._struct[g+"Size"]=[f,p*b];c._debug&&
console.log(g+"Size",c._struct[g+"Size"],c.offset)}if(w(h)){d=c.offset+p*b;if(a.byteLength<d)return Error("Uncaught IndexSizeError: Index or size was negative.");d=a.slice(c.offset,d);c.offset+=p*b}else for(;v<p;){A(q)?(b=a.slice(c.offset),d[v]=q.read(b),b=q.byteLength):d[v]=c.offset+b>a.byteLength?Error("Uncaught IndexSizeError: Index or size was negative."):l["get"+B(q)](c.offset,e);if(D(d[v]))return d[v];c.offset+=b;v++}if(w(h)||t(h)||x(h)||1<p){if(t(h)){h=d;p=[];d=h.length;for(b=0;b<d;)p[b]=String.fromCharCode(h[b]),
b++;h=p.join("")}else h=d;k[g]=h}else k[g]=d[0];c._struct[g]=[q,k[g]];c._debug&&console.log(g,c._struct[g],c.offset)});D(k)||(this.byteLength=this.offset,this.offset!=a.byteLength&&200<=k.type&&6!=this.offset&&console.warn("Incorrect buffer size readed",this.offset,a.byteLength,k));return D(k)&&k||H(k)},write:function(a){var b=this,c=0,e=this.endianness;a!==r&&this.update(a);var l=new DataView(this.emptyBuffer);this._debug&&console.info("STRUCT.WRITE","byteLength:",this.byteLength);u(this.struct,
function(a,m){var g=[],f=a[0],d=a[1],q=a[2],h=a[3],p=y[f],n="uint8",u=0;p===r&&A(f)&&(n=q,q=d.length,p=1);if(t(q)||t(d))n=q,q=d.length;w(d)&&(q=d.byteLength);!0===h&&(l["set"+B(n)](c,q*p,e),c+=y[n],b._struct[m+"Size"]=[n,q*p],b._debug&&console.log(m+"Size",b._struct[m+"Size"],c));b._struct[m]=[f,d];if(w(d)||t(d)||x(d)||1<q){w(d)&&(g=Array.prototype.slice.call(new (E[B(f)+"Array"])(d)));!d||d!==Function&&d.constructor!==Function||(g=[b.defaultValue]);x(d)&&(g=d);if(h=d&&t(d))for(var h=[],n=d.length,
v=0;v<n;)h[v]=d.charCodeAt(v),v++;g=h||g}else g=[d];for(;u<q;){if(A(f)){d=f.write(g[u]);d=new Uint8Array(d);for(h=0;h<d.length;)l.setUint8(c+h,d[h]),h++;c+=d.length}else l["set"+B(f)](c,g[u],e),c+=p;u++}b._debug&&console.log(m,b._struct[m],c)});c!=this.emptyBuffer.byteLength&&console.warn("Incorrect buffer size writed");return l.buffer}};var y={int8:1,uint8:1,int16:2,uint16:2,int32:4,uint32:4,float32:4,int64:8,uint64:8,float64:8};if(DataView.prototype.getUint64===r&&DataView.prototype.setUint64===
r&&DataView.prototype.getInt64===r&&DataView.prototype.setInt64===r){var f=function(a){return 0<=a&&31>a?1<<a:f[a]||(f[a]=Math.pow(2,a)-1)},n=function(a,b){this.lo=a;this.hi=b};n.prototype={valueOf:function(){return this.lo+f(32)*this.hi},toString:function(){return Number.prototype.toString.apply(this.valueOf(),arguments)}};n.fromNumber=function(a){var b=Math.floor(a/f(32));a-=b*f(32);return new n(a,b)};var z=function(){n.apply(this,arguments)};z.prototype="create"in Object?Object.create(n.prototype):
new n;z.prototype.valueOf=function(){return this.hi<f(31)?n.prototype.valueOf.apply(this,arguments):-(f(32)-this.lo+f(32)*(f(32)-1-this.hi))};z.fromNumber=function(a){var b;0<=a?(b=n.fromNumber(a),a=b.lo,b=b.hi):(b=Math.floor(a/f(32)),a-=b*f(32),b+=f(32));return new z(a,b)};DataView.prototype.getUint64=function(a,b){for(var c=b?[0,4]:[4,0],e=0;2>e;e++)c[e]=this.getUint32(a+c[e],b);return(new n(c[0],c[1])).valueOf()};DataView.prototype.setUint64=function(a,b,c){b=n.fromNumber(b);var e=c?{lo:0,hi:4}:
{lo:4,hi:0},f;for(f in e)this.setUint32(a+e[f],b[f],c)};DataView.prototype.getInt64=function(a,b){for(var c=b?[0,4]:[4,0],e=0;2>e;e++)c[e]=this.getUint32(a+c[e],b);return(new z(c[0],c[1])).valueOf()};DataView.prototype.setInt64=function(a,b){value=z.fromNumber(value);var c=b?{lo:0,hi:4}:{lo:4,hi:0},e;for(e in c)this.setUint32(a+c[e],value[e],b)}}E.Struct=C})(this);
\ No newline at end of file
/* Fix user-agent */
* {
box-sizing: border-box;
}
html {
font-weight: 300;
-webkit-font-smoothing: antialiased;
}
html, input {
font-family:
"HelveticaNeue-Light",
"Helvetica Neue Light",
"Helvetica Neue",
Helvetica,
Arial,
"Lucida Grande",
sans-serif;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
ul {
list-style: none;
word-wrap: break-word;
}
/* Pages */
.pages {
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
.page {
height: 100%;
position: absolute;
width: 100%;
}
/* Chat page */
/* Font */
.messages {
font-size: 150%;
}
.inputMessage {
font-size: 100%;
}
.log {
color: gray;
font-size: 70%;
margin: 5px;
text-align: center;
}
/* Messages */
.chatArea {
height: 100%;
padding-bottom: 60px;
}
.messages {
height: 100%;
margin: 0;
overflow-y: scroll;
padding: 10px 20px 10px 20px;
}
.message.typing .messageBody {
color: gray;
}
.username {
float: left;
font-weight: 700;
overflow: hidden;
padding-right: 15px;
text-align: right;
}
/* Input */
.inputMessage {
border: 10px solid #000;
bottom: 0;
height: 60px;
left: 0;
outline: none;
padding-left: 10px;
position: absolute;
right: 0;
width: 100%;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/******************************************************
# DESC : echo server version
# MAINTAINER : Alex Stocks
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-09-06 11:23
# FILE : version.go
******************************************************/
package main
var (
Version = "0.4.04"
)
This diff is collapsed.
# getty application configure script
# ******************************************************
# DESC : application environment variable
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : Apache License 2.0
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:29
# FILE : app.properties
# ******************************************************
TARGET_EXEC_NAME="echo_server"
BUILD_PACKAGE="app"
TARGET_CONF_FILE="conf/config.toml"
TARGET_LOG_CONF_FILE="conf/log.xml"
This diff is collapsed.
#!/usr/bin/env bash
# ******************************************************
# DESC : build script for test env
# AUTHOR : Alex Stocks
# VERSION : 1.0
# LICENCE : LGPL V3
# EMAIL : alexstocks@foxmail.com
# MOD : 2016-07-12 16:34
# FILE : test.sh
# ******************************************************
set -e
export GOOS=linux
export GOARCH=amd64
PROFILE=test
PROJECT_HOME=`pwd`
if [ -f "${PROJECT_HOME}/assembly/common/app.properties" ]; then
. ${PROJECT_HOME}/assembly/common/app.properties
fi
if [ -f "${PROJECT_HOME}/assembly/common/build.sh" ]; then
. ${PROJECT_HOME}/assembly/common/build.sh
fi
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env bash
openssl genrsa -out server.key 2048
openssl req -new -x509 -key server.key -out server.crt
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment