[
  {
    "path": ".github/workflows/go.yml",
    "content": "name: Go\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Set up Go 1.x\n      uses: actions/setup-go@v2\n      with:\n        go-version: ^1.13\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v2\n\n    - name: Get dependencies\n      run: |\n        go get -v -t -d ./...\n        if [ -f Gopkg.toml ]; then\n            curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh\n            dep ensure\n        fi\n\n    - name: Build\n      run: go build -v ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "# IDE ignore\n.idea/\n*.ipr\n*.iml\n*.iws\n.vscode/\n\n# temp ignore\n*.log\n*.cache\n*.diff\n*.exe\n*.exe~\n*.patch\n*.tmp\n*.swp\n\n# system ignore\n.DS_Store\nThumbs.db\n\n# build\n/cmd/comet/comet\n/cmd/logic/logic\n/cmd/job/job\n/target\n/configs\n/dist\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "#### goim\n\n##### Version 2.0.0\n> 1.router has been changed to redis  \n> 2.Support node with redis online heartbeat maintenance  \n> 3.Support for gRPC and Discovery services  \n> 4.Support node connection number and weight scheduling  \n> 5.Support node scheduling by region  \n> 6.Support instruction subscription  \n> 7.Support the current connection room switch  \n> 8.Support multiple room types ({type}://{room_id})  \n> 9.Support sending messages by device_id  \n> 10.Support for room message aggregation  \n> 11.Supports IPv6  \n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Terry.Mao\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "Makefile",
    "content": "# Go parameters\nGOCMD=GO111MODULE=on go\nGOBUILD=$(GOCMD) build\nGOTEST=$(GOCMD) test\n\nall: test build\nbuild:\n\trm -rf target/\n\tmkdir target/\n\tcp cmd/comet/comet-example.toml target/comet.toml\n\tcp cmd/logic/logic-example.toml target/logic.toml\n\tcp cmd/job/job-example.toml target/job.toml\n\t$(GOBUILD) -o target/comet cmd/comet/main.go\n\t$(GOBUILD) -o target/logic cmd/logic/main.go\n\t$(GOBUILD) -o target/job cmd/job/main.go\n\ntest:\n\t$(GOTEST) -v ./...\n\nclean:\n\trm -rf target/\n\nrun:\n\tnohup target/logic -conf=target/logic.toml -region=sh -zone=sh001 -deploy.env=dev -weight=10 2>&1 > target/logic.log &\n\tnohup target/comet -conf=target/comet.toml -region=sh -zone=sh001 -deploy.env=dev -weight=10 -addrs=127.0.0.1 -debug=true 2>&1 > target/comet.log &\n\tnohup target/job -conf=target/job.toml -region=sh -zone=sh001 -deploy.env=dev 2>&1 > target/job.log &\n\nstop:\n\tpkill -f target/logic\n\tpkill -f target/job\n\tpkill -f target/comet\n"
  },
  {
    "path": "README.md",
    "content": "goim v2.0\n==============\n\n[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/)\n[![Build Status](https://github.com/Terry-Mao/goim/workflows/Go/badge.svg)](https://github.com/Terry-Mao/goim/actions)\n[![GoDoc](https://godoc.org/github.com/go-kratos/kratos?status.svg)](https://pkg.go.dev/github.com/Terry-Mao/goim)\n[![Go Report Card](https://goreportcard.com/badge/github.com/Terry-Mao/goim)](https://goreportcard.com/report/github.com/Terry-Mao/goim)\n\ngoim is an im server writen in golang.\n\n## Features\n * Light weight\n * High performance\n * Pure Golang\n * Supports single push, multiple push and broadcasting\n * Supports one key to multiple subscribers (Configurable maximum subscribers count)\n * Supports heartbeats (Application heartbeats, TCP, KeepAlive, HTTP long pulling)\n * Supports authentication (Unauthenticated user can't subscribe)\n * Supports multiple protocols (WebSocket，TCP，HTTP）\n * Scalable architecture (Unlimited dynamic job and logic modules)\n * Asynchronous push notification based on Kafka\n\n## Architecture\n![arch](./docs/arch.png)\n\n## Quick Start\n\n### Build\n```\n    make build\n```\n\n### Run\n```\n    make run\n    make stop\n\n    // or\n    nohup target/logic -conf=target/logic.toml -region=sh -zone=sh001 -deploy.env=dev -weight=10 2>&1 > target/logic.log &\n    nohup target/comet -conf=target/comet.toml -region=sh -zone=sh001 -deploy.env=dev -weight=10 -addrs=127.0.0.1 2>&1 > target/logic.log &\n    nohup target/job -conf=target/job.toml -region=sh -zone=sh001 -deploy.env=dev 2>&1 > target/logic.log &\n\n```\n### Environment\n```\n    env:\n    export REGION=sh\n    export ZONE=sh001\n    export DEPLOY_ENV=dev\n\n    supervisor:\n    environment=REGION=sh,ZONE=sh001,DEPLOY_ENV=dev\n\n    go flag:\n    -region=sh -zone=sh001 deploy.env=dev\n```\n### Configuration\nYou can view the comments in target/comet.toml,logic.toml,job.toml to understand the meaning of the config.\n\n### Dependencies\n[Discovery](https://github.com/bilibili/discovery)\n\n[Kafka](https://kafka.apache.org/quickstart)\n\n## Document\n[Protocol](./docs/protocol.png)\n\n[English](./README_en.md)\n\n[中文](./README_cn.md)\n\n## Examples\nWebsocket: [Websocket Client Demo](https://github.com/Terry-Mao/goim/tree/master/examples/javascript)\n\nAndroid: [Android](https://github.com/roamdy/goim-sdk)\n\niOS: [iOS](https://github.com/roamdy/goim-oc-sdk)\n\n## Benchmark\n![benchmark](./docs/benchmark.jpg)\n\n### Benchmark Server\n| CPU | Memory | OS | Instance |\n| :---- | :---- | :---- | :---- |\n| Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz  | DDR3 32GB | Debian GNU/Linux 8 | 1 |\n\n### Benchmark Case\n* Online: 1,000,000\n* Duration: 15min\n* Push Speed: 40/s (broadcast room)\n* Push Message: {\"test\":1}\n* Received calc mode: 1s per times, total 30 times\n\n### Benchmark Resource\n* CPU: 2000%~2300%\n* Memory: 14GB\n* GC Pause: 504ms\n* Network: Incoming(450MBit/s), Outgoing(4.39GBit/s)\n\n### Benchmark Result\n* Received: 35,900,000/s\n\n[中文](./docs/benchmark_cn.md)\n\n[English](./docs/benchmark_en.md)\n\n## LICENSE\ngoim is is distributed under the terms of the MIT License.\n"
  },
  {
    "path": "README_cn.md",
    "content": "goim v2.0\n==============\n`Terry-Mao/goim` 是一个支持集群的im及实时推送服务。\n\n---------------------------------------\n  * [特性](#特性)\n  * [安装](#安装)\n  * [配置](#配置)\n  * [例子](#例子)\n  * [文档](#文档)\n  * [集群](#集群)\n  * [更多](#更多)\n\n---------------------------------------\n\n## 特性\n * 轻量级\n * 高性能\n * 纯Golang实现\n * 支持单个、多个、单房间以及广播消息推送\n * 支持单个Key多个订阅者（可限制订阅者最大人数）\n * 心跳支持（应用心跳和tcp、keepalive）\n * 支持安全验证（未授权用户不能订阅）\n * 多协议支持（websocket，tcp）\n * 可拓扑的架构（job、logic模块可动态无限扩展）\n * 基于Kafka做异步消息推送\n\n## 安装\n### 一、安装依赖\n```sh\n$ yum -y install java-1.7.0-openjdk\n```\n\n### 二、安装Kafka消息队列服务\n\nkafka在官网已经描述的非常详细，在这里就不过多说明，安装、启动请查看[这里](http://kafka.apache.org/documentation.html#quickstart).\n\n### 三、搭建golang环境\n1.下载源码(根据自己的系统下载对应的[安装包](http://golang.org/dl/))\n```sh\n$ cd /data/programfiles\n$ wget -c --no-check-certificate https://storage.googleapis.com/golang/go1.5.2.linux-amd64.tar.gz\n$ tar -xvf go1.5.2.linux-amd64.tar.gz -C /usr/local\n```\n2.配置GO环境变量\n(这里我加在/etc/profile.d/golang.sh)\n```sh\n$ vi /etc/profile.d/golang.sh\n# 将以下环境变量添加到profile最后面\nexport GOROOT=/usr/local/go\nexport PATH=$PATH:$GOROOT/bin\nexport GOPATH=/data/apps/go\n$ source /etc/profile\n```\n\n### 四、部署goim\n1.下载goim及依赖包\n```sh\n$ yum install hg\n$ go get -u github.com/Terry-Mao/goim\n$ mv $GOPATH/src/github.com/Terry-Mao/goim $GOPATH/src/goim\n$ cd $GOPATH/src/goim\n$ go get ./...\n```\n\n2.安装router、logic、comet、job模块(配置文件请依据实际机器环境配置)\n```sh\n$ cd $GOPATH/src/goim/router\n$ go install\n$ cp router-example.conf $GOPATH/bin/router.conf\n$ cp router-log.xml $GOPATH/bin/\n$ cd ../logic/\n$ go install\n$ cp logic-example.conf $GOPATH/bin/logic.conf\n$ cp logic-log.xml $GOPATH/bin/\n$ cd ../comet/\n$ go install\n$ cp comet-example.conf $GOPATH/bin/comet.conf\n$ cp comet-log.xml $GOPATH/bin/\n$ cd ../logic/job/\n$ go install\n$ cp job-example.conf $GOPATH/bin/job.conf\n$ cp job-log.xml $GOPATH/bin/\n```\n到此所有的环境都搭建完成！\n\n### 五、启动goim\n```sh\n$ cd /$GOPATH/bin\n$ nohup $GOPATH/bin/router -c $GOPATH/bin/router.conf 2>&1 > /data/logs/goim/panic-router.log &\n$ nohup $GOPATH/bin/logic -c $GOPATH/bin/logic.conf 2>&1 > /data/logs/goim/panic-logic.log &\n$ nohup $GOPATH/bin/comet -c $GOPATH/bin/comet.conf 2>&1 > /data/logs/goim/panic-comet.log &\n$ nohup $GOPATH/bin/job -c $GOPATH/bin/job.conf 2>&1 > /data/logs/goim/panic-job.log &\n```\n如果启动失败，默认配置可通过查看panic-xxx.log日志文件来排查各个模块问题.\n\n### 六、测试\n\n推送协议可查看[push http协议文档](./docs/push.md)\n\n## 配置\n\nTODO\n\n## 例子\n\nWebsocket: [Websocket Client Demo](https://github.com/Terry-Mao/goim/tree/master/examples/javascript)\n\nAndroid: [Android](https://github.com/roamdy/goim-sdk)\n\niOS: [iOS](https://github.com/roamdy/goim-oc-sdk)\n\n## 文档\n[push http协议文档](./docs/push.md)推送接口\n\n## 集群\n\n### comet\n\ncomet 属于接入层，非常容易扩展，直接开启多个comet节点，修改配置文件中的base节点下的server.id修改成不同值（注意一定要保证不同的comet进程值唯一），前端接入可以使用LVS 或者 DNS来转发\n\n### logic\n\nlogic 属于无状态的逻辑层，可以随意增加节点，使用nginx upstream来扩展http接口，内部rpc部分，可以使用LVS四层转发\n\n### kafka\n\nkafka 可以使用多broker，或者多partition来扩展队列\n\n### router\n\nrouter 属于有状态节点，logic可以使用一致性hash配置节点，增加多个router节点（目前还不支持动态扩容），提前预估好在线和压力情况\n\n### job\n\njob 根据kafka的partition来扩展多job工作方式，具体可以参考下kafka的partition负载\n\n## 更多\nTODO\n"
  },
  {
    "path": "README_en.md",
    "content": "goim\n==============\n`Terry-Mao/goim` is a IM and push notification server cluster.\n\n---------------------------------------\n  * [Features](#features)\n  * [Installing](#installing)\n  * [Configurations](#configurations)\n  * [Examples](#examples)\n  * [Documents](#documents)\n  * [More](#more)\n\n---------------------------------------\n\n## Features\n * Light weight\n * High performance\n * Pure Golang\n * Supports single push, multiple push, room push and broadcasting\n * Supports one key to multiple subscribers (Configurable maximum subscribers count)\n * Supports heartbeats (Application heartbeats, TCP, KeepAlive)\n * Supports authentication (Unauthenticated user can't subscribe)\n * Supports multiple protocols (WebSocket，TCP）\n * Scalable architecture (Unlimited dynamic job and logic modules)\n * Asynchronous push notification based on Kafka\n\n## Installing\n### Dependencies\n```sh\n$ yum -y install java-1.7.0-openjdk\n```\n\n### Install Kafka\n\nPlease follow the official quick start [here](http://kafka.apache.org/documentation.html#quickstart).\n\n### Install Golang environment\n\nPlease follow the official quick start [here](https://golang.org/doc/install).\n\n### Deploy goim\n1.Download goim\n```sh\n$ yum install git\n$ cd $GOPATH/src\n$ git clone https://github.com/Terry-Mao/goim.git\n$ cd $GOPATH/src/goim\n$ go get ./...\n```\n\n2.Install router、logic、comet、job modules(You might need to change the configuration files based on your servers)\n```sh\n$ cd $GOPATH/src/goim/router\n$ go install\n$ cp router-example.conf $GOPATH/bin/router.conf\n$ cp router-log.xml $GOPATH/bin/\n$ cd ../logic/\n$ go install\n$ cp logic-example.conf $GOPATH/bin/logic.conf\n$ cp logic-log.xml $GOPATH/bin/\n$ cd ../comet/\n$ go install\n$ cp comet-example.conf $GOPATH/bin/comet.conf\n$ cp comet-log.xml $GOPATH/bin/\n$ cd ../logic/job/\n$ go install\n$ cp job-example.conf $GOPATH/bin/job.conf\n$ cp job-log.xml $GOPATH/bin/\n```\n\nEverything is DONE!\n\n### Run goim\nYou may need to change the log files location.\n```sh\n$ cd /$GOPATH/bin\n$ nohup $GOPATH/bin/router -c $GOPATH/bin/router.conf 2>&1 > /data/logs/goim/panic-router.log &\n$ nohup $GOPATH/bin/logic -c $GOPATH/bin/logic.conf 2>&1 > /data/logs/goim/panic-logic.log &\n$ nohup $GOPATH/bin/comet -c $GOPATH/bin/comet.conf 2>&1 > /data/logs/goim/panic-comet.log &\n$ nohup $GOPATH/bin/job -c $GOPATH/bin/job.conf 2>&1 > /data/logs/goim/panic-job.log &\n```\n\nIf it fails, please check the logs for debugging.\n\n### Testing\n\nCheck the push protocols here[push HTTP protocols](./docs/push.md)\n\n## Configurations\nTODO\n\n## Examples\nWebsocket: [Websocket Client Demo](https://github.com/Terry-Mao/goim/tree/master/examples/javascript)\n\nAndroid: [Android SDK](https://github.com/roamdy/goim-sdk)\n\niOS: [iOS](https://github.com/roamdy/goim-oc-sdk)\n\n## Documents\n[push HTTP protocols](./docs/en/push.md)\n\n[Comet client protocols](./docs/en/proto.md)\n\n##More\nTODO\n"
  },
  {
    "path": "api/comet/comet.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// source: comet/comet.proto\n\npackage comet\n\nimport (\n\tcontext \"context\"\n\tfmt \"fmt\"\n\tprotocol \"github.com/Terry-Mao/goim/api/protocol\"\n\tproto \"github.com/golang/protobuf/proto\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\tmath \"math\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package\n\ntype PushMsgReq struct {\n\tKeys                 []string        `protobuf:\"bytes,1,rep,name=keys,proto3\" json:\"keys,omitempty\"`\n\tProtoOp              int32           `protobuf:\"varint,3,opt,name=protoOp,proto3\" json:\"protoOp,omitempty\"`\n\tProto                *protocol.Proto `protobuf:\"bytes,2,opt,name=proto,proto3\" json:\"proto,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}        `json:\"-\"`\n\tXXX_unrecognized     []byte          `json:\"-\"`\n\tXXX_sizecache        int32           `json:\"-\"`\n}\n\nfunc (m *PushMsgReq) Reset()         { *m = PushMsgReq{} }\nfunc (m *PushMsgReq) String() string { return proto.CompactTextString(m) }\nfunc (*PushMsgReq) ProtoMessage()    {}\nfunc (*PushMsgReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_327b4a7d084564be, []int{0}\n}\n\nfunc (m *PushMsgReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_PushMsgReq.Unmarshal(m, b)\n}\nfunc (m *PushMsgReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_PushMsgReq.Marshal(b, m, deterministic)\n}\nfunc (m *PushMsgReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_PushMsgReq.Merge(m, src)\n}\nfunc (m *PushMsgReq) XXX_Size() int {\n\treturn xxx_messageInfo_PushMsgReq.Size(m)\n}\nfunc (m *PushMsgReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_PushMsgReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_PushMsgReq proto.InternalMessageInfo\n\nfunc (m *PushMsgReq) GetKeys() []string {\n\tif m != nil {\n\t\treturn m.Keys\n\t}\n\treturn nil\n}\n\nfunc (m *PushMsgReq) GetProtoOp() int32 {\n\tif m != nil {\n\t\treturn m.ProtoOp\n\t}\n\treturn 0\n}\n\nfunc (m *PushMsgReq) GetProto() *protocol.Proto {\n\tif m != nil {\n\t\treturn m.Proto\n\t}\n\treturn nil\n}\n\ntype PushMsgReply struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *PushMsgReply) Reset()         { *m = PushMsgReply{} }\nfunc (m *PushMsgReply) String() string { return proto.CompactTextString(m) }\nfunc (*PushMsgReply) ProtoMessage()    {}\nfunc (*PushMsgReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_327b4a7d084564be, []int{1}\n}\n\nfunc (m *PushMsgReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_PushMsgReply.Unmarshal(m, b)\n}\nfunc (m *PushMsgReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_PushMsgReply.Marshal(b, m, deterministic)\n}\nfunc (m *PushMsgReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_PushMsgReply.Merge(m, src)\n}\nfunc (m *PushMsgReply) XXX_Size() int {\n\treturn xxx_messageInfo_PushMsgReply.Size(m)\n}\nfunc (m *PushMsgReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_PushMsgReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_PushMsgReply proto.InternalMessageInfo\n\ntype BroadcastReq struct {\n\tProtoOp              int32           `protobuf:\"varint,1,opt,name=protoOp,proto3\" json:\"protoOp,omitempty\"`\n\tProto                *protocol.Proto `protobuf:\"bytes,2,opt,name=proto,proto3\" json:\"proto,omitempty\"`\n\tSpeed                int32           `protobuf:\"varint,3,opt,name=speed,proto3\" json:\"speed,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}        `json:\"-\"`\n\tXXX_unrecognized     []byte          `json:\"-\"`\n\tXXX_sizecache        int32           `json:\"-\"`\n}\n\nfunc (m *BroadcastReq) Reset()         { *m = BroadcastReq{} }\nfunc (m *BroadcastReq) String() string { return proto.CompactTextString(m) }\nfunc (*BroadcastReq) ProtoMessage()    {}\nfunc (*BroadcastReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_327b4a7d084564be, []int{2}\n}\n\nfunc (m *BroadcastReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_BroadcastReq.Unmarshal(m, b)\n}\nfunc (m *BroadcastReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_BroadcastReq.Marshal(b, m, deterministic)\n}\nfunc (m *BroadcastReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_BroadcastReq.Merge(m, src)\n}\nfunc (m *BroadcastReq) XXX_Size() int {\n\treturn xxx_messageInfo_BroadcastReq.Size(m)\n}\nfunc (m *BroadcastReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_BroadcastReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_BroadcastReq proto.InternalMessageInfo\n\nfunc (m *BroadcastReq) GetProtoOp() int32 {\n\tif m != nil {\n\t\treturn m.ProtoOp\n\t}\n\treturn 0\n}\n\nfunc (m *BroadcastReq) GetProto() *protocol.Proto {\n\tif m != nil {\n\t\treturn m.Proto\n\t}\n\treturn nil\n}\n\nfunc (m *BroadcastReq) GetSpeed() int32 {\n\tif m != nil {\n\t\treturn m.Speed\n\t}\n\treturn 0\n}\n\ntype BroadcastReply struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *BroadcastReply) Reset()         { *m = BroadcastReply{} }\nfunc (m *BroadcastReply) String() string { return proto.CompactTextString(m) }\nfunc (*BroadcastReply) ProtoMessage()    {}\nfunc (*BroadcastReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_327b4a7d084564be, []int{3}\n}\n\nfunc (m *BroadcastReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_BroadcastReply.Unmarshal(m, b)\n}\nfunc (m *BroadcastReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_BroadcastReply.Marshal(b, m, deterministic)\n}\nfunc (m *BroadcastReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_BroadcastReply.Merge(m, src)\n}\nfunc (m *BroadcastReply) XXX_Size() int {\n\treturn xxx_messageInfo_BroadcastReply.Size(m)\n}\nfunc (m *BroadcastReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_BroadcastReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_BroadcastReply proto.InternalMessageInfo\n\ntype BroadcastRoomReq struct {\n\tRoomID               string          `protobuf:\"bytes,1,opt,name=roomID,proto3\" json:\"roomID,omitempty\"`\n\tProto                *protocol.Proto `protobuf:\"bytes,2,opt,name=proto,proto3\" json:\"proto,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}        `json:\"-\"`\n\tXXX_unrecognized     []byte          `json:\"-\"`\n\tXXX_sizecache        int32           `json:\"-\"`\n}\n\nfunc (m *BroadcastRoomReq) Reset()         { *m = BroadcastRoomReq{} }\nfunc (m *BroadcastRoomReq) String() string { return proto.CompactTextString(m) }\nfunc (*BroadcastRoomReq) ProtoMessage()    {}\nfunc (*BroadcastRoomReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_327b4a7d084564be, []int{4}\n}\n\nfunc (m *BroadcastRoomReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_BroadcastRoomReq.Unmarshal(m, b)\n}\nfunc (m *BroadcastRoomReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_BroadcastRoomReq.Marshal(b, m, deterministic)\n}\nfunc (m *BroadcastRoomReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_BroadcastRoomReq.Merge(m, src)\n}\nfunc (m *BroadcastRoomReq) XXX_Size() int {\n\treturn xxx_messageInfo_BroadcastRoomReq.Size(m)\n}\nfunc (m *BroadcastRoomReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_BroadcastRoomReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_BroadcastRoomReq proto.InternalMessageInfo\n\nfunc (m *BroadcastRoomReq) GetRoomID() string {\n\tif m != nil {\n\t\treturn m.RoomID\n\t}\n\treturn \"\"\n}\n\nfunc (m *BroadcastRoomReq) GetProto() *protocol.Proto {\n\tif m != nil {\n\t\treturn m.Proto\n\t}\n\treturn nil\n}\n\ntype BroadcastRoomReply struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *BroadcastRoomReply) Reset()         { *m = BroadcastRoomReply{} }\nfunc (m *BroadcastRoomReply) String() string { return proto.CompactTextString(m) }\nfunc (*BroadcastRoomReply) ProtoMessage()    {}\nfunc (*BroadcastRoomReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_327b4a7d084564be, []int{5}\n}\n\nfunc (m *BroadcastRoomReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_BroadcastRoomReply.Unmarshal(m, b)\n}\nfunc (m *BroadcastRoomReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_BroadcastRoomReply.Marshal(b, m, deterministic)\n}\nfunc (m *BroadcastRoomReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_BroadcastRoomReply.Merge(m, src)\n}\nfunc (m *BroadcastRoomReply) XXX_Size() int {\n\treturn xxx_messageInfo_BroadcastRoomReply.Size(m)\n}\nfunc (m *BroadcastRoomReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_BroadcastRoomReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_BroadcastRoomReply proto.InternalMessageInfo\n\ntype RoomsReq struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *RoomsReq) Reset()         { *m = RoomsReq{} }\nfunc (m *RoomsReq) String() string { return proto.CompactTextString(m) }\nfunc (*RoomsReq) ProtoMessage()    {}\nfunc (*RoomsReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_327b4a7d084564be, []int{6}\n}\n\nfunc (m *RoomsReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_RoomsReq.Unmarshal(m, b)\n}\nfunc (m *RoomsReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_RoomsReq.Marshal(b, m, deterministic)\n}\nfunc (m *RoomsReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_RoomsReq.Merge(m, src)\n}\nfunc (m *RoomsReq) XXX_Size() int {\n\treturn xxx_messageInfo_RoomsReq.Size(m)\n}\nfunc (m *RoomsReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_RoomsReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_RoomsReq proto.InternalMessageInfo\n\ntype RoomsReply struct {\n\tRooms                map[string]bool `protobuf:\"bytes,1,rep,name=rooms,proto3\" json:\"rooms,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"varint,2,opt,name=value,proto3\"`\n\tXXX_NoUnkeyedLiteral struct{}        `json:\"-\"`\n\tXXX_unrecognized     []byte          `json:\"-\"`\n\tXXX_sizecache        int32           `json:\"-\"`\n}\n\nfunc (m *RoomsReply) Reset()         { *m = RoomsReply{} }\nfunc (m *RoomsReply) String() string { return proto.CompactTextString(m) }\nfunc (*RoomsReply) ProtoMessage()    {}\nfunc (*RoomsReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_327b4a7d084564be, []int{7}\n}\n\nfunc (m *RoomsReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_RoomsReply.Unmarshal(m, b)\n}\nfunc (m *RoomsReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_RoomsReply.Marshal(b, m, deterministic)\n}\nfunc (m *RoomsReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_RoomsReply.Merge(m, src)\n}\nfunc (m *RoomsReply) XXX_Size() int {\n\treturn xxx_messageInfo_RoomsReply.Size(m)\n}\nfunc (m *RoomsReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_RoomsReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_RoomsReply proto.InternalMessageInfo\n\nfunc (m *RoomsReply) GetRooms() map[string]bool {\n\tif m != nil {\n\t\treturn m.Rooms\n\t}\n\treturn nil\n}\n\nfunc init() {\n\tproto.RegisterType((*PushMsgReq)(nil), \"goim.comet.PushMsgReq\")\n\tproto.RegisterType((*PushMsgReply)(nil), \"goim.comet.PushMsgReply\")\n\tproto.RegisterType((*BroadcastReq)(nil), \"goim.comet.BroadcastReq\")\n\tproto.RegisterType((*BroadcastReply)(nil), \"goim.comet.BroadcastReply\")\n\tproto.RegisterType((*BroadcastRoomReq)(nil), \"goim.comet.BroadcastRoomReq\")\n\tproto.RegisterType((*BroadcastRoomReply)(nil), \"goim.comet.BroadcastRoomReply\")\n\tproto.RegisterType((*RoomsReq)(nil), \"goim.comet.RoomsReq\")\n\tproto.RegisterType((*RoomsReply)(nil), \"goim.comet.RoomsReply\")\n\tproto.RegisterMapType((map[string]bool)(nil), \"goim.comet.RoomsReply.RoomsEntry\")\n}\n\nfunc init() { proto.RegisterFile(\"comet/comet.proto\", fileDescriptor_327b4a7d084564be) }\n\nvar fileDescriptor_327b4a7d084564be = []byte{\n\t// 404 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x53, 0xcf, 0x4f, 0xe2, 0x40,\n\t0x14, 0x4e, 0x61, 0xcb, 0x8f, 0x07, 0x4b, 0xd8, 0x49, 0x43, 0x9a, 0x66, 0xb3, 0x61, 0x7b, 0x62,\n\t0xd7, 0xd8, 0x26, 0x18, 0x22, 0x91, 0x93, 0xa8, 0x07, 0x0f, 0x44, 0x32, 0x31, 0x1e, 0xbc, 0x15,\n\t0x18, 0x01, 0x69, 0x9d, 0xd2, 0x16, 0x93, 0x9e, 0xfc, 0x0b, 0xfc, 0x9f, 0xcd, 0x9b, 0xa9, 0x6d,\n\t0x31, 0x95, 0x44, 0x2f, 0xcd, 0xf7, 0xde, 0x7c, 0xf3, 0xbe, 0x6f, 0xbe, 0x07, 0xf0, 0x6b, 0xce,\n\t0x3d, 0x16, 0xd9, 0xe2, 0x6b, 0xf9, 0x01, 0x8f, 0x38, 0x81, 0x25, 0x5f, 0x7b, 0x96, 0xe8, 0x18,\n\t0x83, 0xe5, 0x3a, 0x5a, 0xed, 0x66, 0x58, 0xd9, 0xb7, 0x2c, 0x08, 0xe2, 0xe3, 0x89, 0xc3, 0x6d,\n\t0x24, 0xd8, 0x8e, 0xbf, 0xb6, 0xc5, 0x85, 0x39, 0x77, 0x53, 0x20, 0x47, 0x98, 0x0f, 0x00, 0xd3,\n\t0x5d, 0xb8, 0x9a, 0x84, 0x4b, 0xca, 0xb6, 0x84, 0xc0, 0x8f, 0x0d, 0x8b, 0x43, 0x5d, 0xe9, 0x96,\n\t0x7b, 0x75, 0x2a, 0x30, 0xd1, 0xa1, 0x2a, 0xa8, 0x37, 0xbe, 0x5e, 0xee, 0x2a, 0x3d, 0x95, 0xbe,\n\t0x97, 0xe4, 0x3f, 0xa8, 0x02, 0xea, 0xa5, 0xae, 0xd2, 0x6b, 0xf4, 0x35, 0x4b, 0xd8, 0x49, 0x05,\n\t0xa6, 0x08, 0xa8, 0xa4, 0x98, 0x2d, 0x68, 0xa6, 0x3a, 0xbe, 0x1b, 0x9b, 0x8f, 0xd0, 0x1c, 0x07,\n\t0xdc, 0x59, 0xcc, 0x9d, 0x30, 0x42, 0xe5, 0x9c, 0x8a, 0xf2, 0x6d, 0x15, 0xa2, 0x81, 0x1a, 0xfa,\n\t0x8c, 0x2d, 0x12, 0xa7, 0xb2, 0x30, 0xdb, 0xd0, 0xca, 0x69, 0xa1, 0xfa, 0x1d, 0xb4, 0xb3, 0x0e,\n\t0xe7, 0x1e, 0x3a, 0xe8, 0x40, 0x25, 0xe0, 0xdc, 0xbb, 0xbe, 0x14, 0x06, 0xea, 0x34, 0xa9, 0xbe,\n\t0xf4, 0x4a, 0x0d, 0xc8, 0x87, 0xb9, 0xa8, 0x06, 0x50, 0xc3, 0x22, 0xa4, 0x6c, 0x6b, 0xbe, 0x00,\n\t0x24, 0xd8, 0x77, 0x63, 0x72, 0x0a, 0x2a, 0xaa, 0xc8, 0xc0, 0x1b, 0xfd, 0xbf, 0x56, 0xb6, 0x50,\n\t0x2b, 0xa3, 0x49, 0x78, 0xf5, 0x14, 0x05, 0x31, 0x95, 0x7c, 0x63, 0x98, 0x8c, 0x11, 0x4d, 0xd2,\n\t0x86, 0xf2, 0x86, 0xc5, 0x89, 0x6f, 0x84, 0x18, 0xc4, 0xb3, 0xe3, 0xee, 0x98, 0x30, 0x5d, 0xa3,\n\t0xb2, 0x38, 0x2b, 0x0d, 0x95, 0xfe, 0x6b, 0x09, 0xd4, 0x0b, 0x14, 0x20, 0x23, 0xa8, 0x26, 0x2b,\n\t0x21, 0x9d, 0xbc, 0x70, 0xf6, 0x7b, 0x30, 0xf4, 0xc2, 0x3e, 0x3a, 0x3f, 0x87, 0x7a, 0xfa, 0x52,\n\t0xb2, 0x47, 0xcb, 0xaf, 0xd5, 0x30, 0x3e, 0x39, 0xc1, 0x11, 0x13, 0xf8, 0xb9, 0x17, 0x16, 0xf9,\n\t0x5d, 0x4c, 0x96, 0xfb, 0x31, 0xfe, 0x1c, 0x38, 0xc5, 0x71, 0x03, 0x50, 0x45, 0x24, 0x44, 0x2b,\n\t0x48, 0x71, 0x6b, 0x74, 0x8a, 0xb3, 0x1d, 0x1f, 0xdd, 0xff, 0x3b, 0xfc, 0xcf, 0x11, 0x37, 0x46,\n\t0xe2, 0x3b, 0xab, 0x88, 0x35, 0x9f, 0xbc, 0x05, 0x00, 0x00, 0xff, 0xff, 0xb3, 0xcb, 0xf3, 0x2a,\n\t0x8c, 0x03, 0x00, 0x00,\n}\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ context.Context\nvar _ grpc.ClientConn\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\nconst _ = grpc.SupportPackageIsVersion4\n\n// CometClient is the client API for Comet service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype CometClient interface {\n\t// PushMsg push by key or mid\n\tPushMsg(ctx context.Context, in *PushMsgReq, opts ...grpc.CallOption) (*PushMsgReply, error)\n\t// Broadcast send to every enrity\n\tBroadcast(ctx context.Context, in *BroadcastReq, opts ...grpc.CallOption) (*BroadcastReply, error)\n\t// BroadcastRoom broadcast to one room\n\tBroadcastRoom(ctx context.Context, in *BroadcastRoomReq, opts ...grpc.CallOption) (*BroadcastRoomReply, error)\n\t// Rooms get all rooms\n\tRooms(ctx context.Context, in *RoomsReq, opts ...grpc.CallOption) (*RoomsReply, error)\n}\n\ntype cometClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewCometClient(cc *grpc.ClientConn) CometClient {\n\treturn &cometClient{cc}\n}\n\nfunc (c *cometClient) PushMsg(ctx context.Context, in *PushMsgReq, opts ...grpc.CallOption) (*PushMsgReply, error) {\n\tout := new(PushMsgReply)\n\terr := c.cc.Invoke(ctx, \"/goim.comet.Comet/PushMsg\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cometClient) Broadcast(ctx context.Context, in *BroadcastReq, opts ...grpc.CallOption) (*BroadcastReply, error) {\n\tout := new(BroadcastReply)\n\terr := c.cc.Invoke(ctx, \"/goim.comet.Comet/Broadcast\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cometClient) BroadcastRoom(ctx context.Context, in *BroadcastRoomReq, opts ...grpc.CallOption) (*BroadcastRoomReply, error) {\n\tout := new(BroadcastRoomReply)\n\terr := c.cc.Invoke(ctx, \"/goim.comet.Comet/BroadcastRoom\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *cometClient) Rooms(ctx context.Context, in *RoomsReq, opts ...grpc.CallOption) (*RoomsReply, error) {\n\tout := new(RoomsReply)\n\terr := c.cc.Invoke(ctx, \"/goim.comet.Comet/Rooms\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// CometServer is the server API for Comet service.\ntype CometServer interface {\n\t// PushMsg push by key or mid\n\tPushMsg(context.Context, *PushMsgReq) (*PushMsgReply, error)\n\t// Broadcast send to every enrity\n\tBroadcast(context.Context, *BroadcastReq) (*BroadcastReply, error)\n\t// BroadcastRoom broadcast to one room\n\tBroadcastRoom(context.Context, *BroadcastRoomReq) (*BroadcastRoomReply, error)\n\t// Rooms get all rooms\n\tRooms(context.Context, *RoomsReq) (*RoomsReply, error)\n}\n\n// UnimplementedCometServer can be embedded to have forward compatible implementations.\ntype UnimplementedCometServer struct {\n}\n\nfunc (*UnimplementedCometServer) PushMsg(ctx context.Context, req *PushMsgReq) (*PushMsgReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method PushMsg not implemented\")\n}\nfunc (*UnimplementedCometServer) Broadcast(ctx context.Context, req *BroadcastReq) (*BroadcastReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Broadcast not implemented\")\n}\nfunc (*UnimplementedCometServer) BroadcastRoom(ctx context.Context, req *BroadcastRoomReq) (*BroadcastRoomReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method BroadcastRoom not implemented\")\n}\nfunc (*UnimplementedCometServer) Rooms(ctx context.Context, req *RoomsReq) (*RoomsReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Rooms not implemented\")\n}\n\nfunc RegisterCometServer(s *grpc.Server, srv CometServer) {\n\ts.RegisterService(&_Comet_serviceDesc, srv)\n}\n\nfunc _Comet_PushMsg_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(PushMsgReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CometServer).PushMsg(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.comet.Comet/PushMsg\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CometServer).PushMsg(ctx, req.(*PushMsgReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Comet_Broadcast_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(BroadcastReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CometServer).Broadcast(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.comet.Comet/Broadcast\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CometServer).Broadcast(ctx, req.(*BroadcastReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Comet_BroadcastRoom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(BroadcastRoomReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CometServer).BroadcastRoom(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.comet.Comet/BroadcastRoom\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CometServer).BroadcastRoom(ctx, req.(*BroadcastRoomReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Comet_Rooms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RoomsReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(CometServer).Rooms(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.comet.Comet/Rooms\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(CometServer).Rooms(ctx, req.(*RoomsReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _Comet_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"goim.comet.Comet\",\n\tHandlerType: (*CometServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"PushMsg\",\n\t\t\tHandler:    _Comet_PushMsg_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Broadcast\",\n\t\t\tHandler:    _Comet_Broadcast_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"BroadcastRoom\",\n\t\t\tHandler:    _Comet_BroadcastRoom_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Rooms\",\n\t\t\tHandler:    _Comet_Rooms_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"comet/comet.proto\",\n}\n"
  },
  {
    "path": "api/comet/comet.proto",
    "content": "syntax = \"proto3\";\n\npackage goim.comet;\n\noption go_package = \"github.com/Terry-Mao/goim/api/comet;comet\";\n\nimport \"github.com/Terry-Mao/goim/api/protocol/protocol.proto\";\n\nmessage PushMsgReq {\n    repeated string keys = 1;\n    int32 protoOp = 3;\n    goim.protocol.Proto proto = 2;\n}\n\nmessage PushMsgReply {}\n\nmessage BroadcastReq{\n    int32 protoOp = 1;\n    goim.protocol.Proto proto = 2;\n    int32 speed = 3;\n}\n\nmessage BroadcastReply{}\n\nmessage BroadcastRoomReq {\n    string roomID = 1;\n    goim.protocol.Proto proto = 2;\n}\n\nmessage BroadcastRoomReply{}\n\nmessage RoomsReq{}\n\nmessage RoomsReply {\n    map<string,bool> rooms = 1;\n}\n\nservice Comet { \n    // PushMsg push by key or mid\n    rpc PushMsg(PushMsgReq) returns (PushMsgReply);\n    // Broadcast send to every enrity\n    rpc Broadcast(BroadcastReq) returns (BroadcastReply);\n    // BroadcastRoom broadcast to one room\n    rpc BroadcastRoom(BroadcastRoomReq) returns (BroadcastRoomReply);\n    // Rooms get all rooms\n    rpc Rooms(RoomsReq) returns (RoomsReply);\n}\n"
  },
  {
    "path": "api/generate.go",
    "content": "package api\n\n//go:generate protoc -I. -I$GOPATH/src --go_out=plugins=grpc:. --go_opt=paths=source_relative protocol/protocol.proto\n//go:generate protoc -I. -I$GOPATH/src --go_out=plugins=grpc:. --go_opt=paths=source_relative comet/comet.proto\n//go:generate protoc -I. -I$GOPATH/src --go_out=plugins=grpc:. --go_opt=paths=source_relative logic/logic.proto\n"
  },
  {
    "path": "api/logic/logic.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// source: logic/logic.proto\n\npackage logic\n\nimport (\n\tcontext \"context\"\n\tfmt \"fmt\"\n\tprotocol \"github.com/Terry-Mao/goim/api/protocol\"\n\tproto \"github.com/golang/protobuf/proto\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\tmath \"math\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package\n\ntype PushMsg_Type int32\n\nconst (\n\tPushMsg_PUSH      PushMsg_Type = 0\n\tPushMsg_ROOM      PushMsg_Type = 1\n\tPushMsg_BROADCAST PushMsg_Type = 2\n)\n\nvar PushMsg_Type_name = map[int32]string{\n\t0: \"PUSH\",\n\t1: \"ROOM\",\n\t2: \"BROADCAST\",\n}\n\nvar PushMsg_Type_value = map[string]int32{\n\t\"PUSH\":      0,\n\t\"ROOM\":      1,\n\t\"BROADCAST\": 2,\n}\n\nfunc (x PushMsg_Type) String() string {\n\treturn proto.EnumName(PushMsg_Type_name, int32(x))\n}\n\nfunc (PushMsg_Type) EnumDescriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{0, 0}\n}\n\ntype PushMsg struct {\n\tType                 PushMsg_Type `protobuf:\"varint,1,opt,name=type,proto3,enum=goim.logic.PushMsg_Type\" json:\"type,omitempty\"`\n\tOperation            int32        `protobuf:\"varint,2,opt,name=operation,proto3\" json:\"operation,omitempty\"`\n\tSpeed                int32        `protobuf:\"varint,3,opt,name=speed,proto3\" json:\"speed,omitempty\"`\n\tServer               string       `protobuf:\"bytes,4,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tRoom                 string       `protobuf:\"bytes,5,opt,name=room,proto3\" json:\"room,omitempty\"`\n\tKeys                 []string     `protobuf:\"bytes,6,rep,name=keys,proto3\" json:\"keys,omitempty\"`\n\tMsg                  []byte       `protobuf:\"bytes,7,opt,name=msg,proto3\" json:\"msg,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}     `json:\"-\"`\n\tXXX_unrecognized     []byte       `json:\"-\"`\n\tXXX_sizecache        int32        `json:\"-\"`\n}\n\nfunc (m *PushMsg) Reset()         { *m = PushMsg{} }\nfunc (m *PushMsg) String() string { return proto.CompactTextString(m) }\nfunc (*PushMsg) ProtoMessage()    {}\nfunc (*PushMsg) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{0}\n}\n\nfunc (m *PushMsg) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_PushMsg.Unmarshal(m, b)\n}\nfunc (m *PushMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_PushMsg.Marshal(b, m, deterministic)\n}\nfunc (m *PushMsg) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_PushMsg.Merge(m, src)\n}\nfunc (m *PushMsg) XXX_Size() int {\n\treturn xxx_messageInfo_PushMsg.Size(m)\n}\nfunc (m *PushMsg) XXX_DiscardUnknown() {\n\txxx_messageInfo_PushMsg.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_PushMsg proto.InternalMessageInfo\n\nfunc (m *PushMsg) GetType() PushMsg_Type {\n\tif m != nil {\n\t\treturn m.Type\n\t}\n\treturn PushMsg_PUSH\n}\n\nfunc (m *PushMsg) GetOperation() int32 {\n\tif m != nil {\n\t\treturn m.Operation\n\t}\n\treturn 0\n}\n\nfunc (m *PushMsg) GetSpeed() int32 {\n\tif m != nil {\n\t\treturn m.Speed\n\t}\n\treturn 0\n}\n\nfunc (m *PushMsg) GetServer() string {\n\tif m != nil {\n\t\treturn m.Server\n\t}\n\treturn \"\"\n}\n\nfunc (m *PushMsg) GetRoom() string {\n\tif m != nil {\n\t\treturn m.Room\n\t}\n\treturn \"\"\n}\n\nfunc (m *PushMsg) GetKeys() []string {\n\tif m != nil {\n\t\treturn m.Keys\n\t}\n\treturn nil\n}\n\nfunc (m *PushMsg) GetMsg() []byte {\n\tif m != nil {\n\t\treturn m.Msg\n\t}\n\treturn nil\n}\n\ntype ConnectReq struct {\n\tServer               string   `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tCookie               string   `protobuf:\"bytes,2,opt,name=cookie,proto3\" json:\"cookie,omitempty\"`\n\tToken                []byte   `protobuf:\"bytes,3,opt,name=token,proto3\" json:\"token,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *ConnectReq) Reset()         { *m = ConnectReq{} }\nfunc (m *ConnectReq) String() string { return proto.CompactTextString(m) }\nfunc (*ConnectReq) ProtoMessage()    {}\nfunc (*ConnectReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{1}\n}\n\nfunc (m *ConnectReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_ConnectReq.Unmarshal(m, b)\n}\nfunc (m *ConnectReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_ConnectReq.Marshal(b, m, deterministic)\n}\nfunc (m *ConnectReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_ConnectReq.Merge(m, src)\n}\nfunc (m *ConnectReq) XXX_Size() int {\n\treturn xxx_messageInfo_ConnectReq.Size(m)\n}\nfunc (m *ConnectReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_ConnectReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_ConnectReq proto.InternalMessageInfo\n\nfunc (m *ConnectReq) GetServer() string {\n\tif m != nil {\n\t\treturn m.Server\n\t}\n\treturn \"\"\n}\n\nfunc (m *ConnectReq) GetCookie() string {\n\tif m != nil {\n\t\treturn m.Cookie\n\t}\n\treturn \"\"\n}\n\nfunc (m *ConnectReq) GetToken() []byte {\n\tif m != nil {\n\t\treturn m.Token\n\t}\n\treturn nil\n}\n\ntype ConnectReply struct {\n\tMid                  int64    `protobuf:\"varint,1,opt,name=mid,proto3\" json:\"mid,omitempty\"`\n\tKey                  string   `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tRoomID               string   `protobuf:\"bytes,3,opt,name=roomID,proto3\" json:\"roomID,omitempty\"`\n\tAccepts              []int32  `protobuf:\"varint,4,rep,packed,name=accepts,proto3\" json:\"accepts,omitempty\"`\n\tHeartbeat            int64    `protobuf:\"varint,5,opt,name=heartbeat,proto3\" json:\"heartbeat,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *ConnectReply) Reset()         { *m = ConnectReply{} }\nfunc (m *ConnectReply) String() string { return proto.CompactTextString(m) }\nfunc (*ConnectReply) ProtoMessage()    {}\nfunc (*ConnectReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{2}\n}\n\nfunc (m *ConnectReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_ConnectReply.Unmarshal(m, b)\n}\nfunc (m *ConnectReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_ConnectReply.Marshal(b, m, deterministic)\n}\nfunc (m *ConnectReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_ConnectReply.Merge(m, src)\n}\nfunc (m *ConnectReply) XXX_Size() int {\n\treturn xxx_messageInfo_ConnectReply.Size(m)\n}\nfunc (m *ConnectReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_ConnectReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_ConnectReply proto.InternalMessageInfo\n\nfunc (m *ConnectReply) GetMid() int64 {\n\tif m != nil {\n\t\treturn m.Mid\n\t}\n\treturn 0\n}\n\nfunc (m *ConnectReply) GetKey() string {\n\tif m != nil {\n\t\treturn m.Key\n\t}\n\treturn \"\"\n}\n\nfunc (m *ConnectReply) GetRoomID() string {\n\tif m != nil {\n\t\treturn m.RoomID\n\t}\n\treturn \"\"\n}\n\nfunc (m *ConnectReply) GetAccepts() []int32 {\n\tif m != nil {\n\t\treturn m.Accepts\n\t}\n\treturn nil\n}\n\nfunc (m *ConnectReply) GetHeartbeat() int64 {\n\tif m != nil {\n\t\treturn m.Heartbeat\n\t}\n\treturn 0\n}\n\ntype DisconnectReq struct {\n\tMid                  int64    `protobuf:\"varint,1,opt,name=mid,proto3\" json:\"mid,omitempty\"`\n\tKey                  string   `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tServer               string   `protobuf:\"bytes,3,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *DisconnectReq) Reset()         { *m = DisconnectReq{} }\nfunc (m *DisconnectReq) String() string { return proto.CompactTextString(m) }\nfunc (*DisconnectReq) ProtoMessage()    {}\nfunc (*DisconnectReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{3}\n}\n\nfunc (m *DisconnectReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_DisconnectReq.Unmarshal(m, b)\n}\nfunc (m *DisconnectReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_DisconnectReq.Marshal(b, m, deterministic)\n}\nfunc (m *DisconnectReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_DisconnectReq.Merge(m, src)\n}\nfunc (m *DisconnectReq) XXX_Size() int {\n\treturn xxx_messageInfo_DisconnectReq.Size(m)\n}\nfunc (m *DisconnectReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_DisconnectReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_DisconnectReq proto.InternalMessageInfo\n\nfunc (m *DisconnectReq) GetMid() int64 {\n\tif m != nil {\n\t\treturn m.Mid\n\t}\n\treturn 0\n}\n\nfunc (m *DisconnectReq) GetKey() string {\n\tif m != nil {\n\t\treturn m.Key\n\t}\n\treturn \"\"\n}\n\nfunc (m *DisconnectReq) GetServer() string {\n\tif m != nil {\n\t\treturn m.Server\n\t}\n\treturn \"\"\n}\n\ntype DisconnectReply struct {\n\tHas                  bool     `protobuf:\"varint,1,opt,name=has,proto3\" json:\"has,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *DisconnectReply) Reset()         { *m = DisconnectReply{} }\nfunc (m *DisconnectReply) String() string { return proto.CompactTextString(m) }\nfunc (*DisconnectReply) ProtoMessage()    {}\nfunc (*DisconnectReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{4}\n}\n\nfunc (m *DisconnectReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_DisconnectReply.Unmarshal(m, b)\n}\nfunc (m *DisconnectReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_DisconnectReply.Marshal(b, m, deterministic)\n}\nfunc (m *DisconnectReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_DisconnectReply.Merge(m, src)\n}\nfunc (m *DisconnectReply) XXX_Size() int {\n\treturn xxx_messageInfo_DisconnectReply.Size(m)\n}\nfunc (m *DisconnectReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_DisconnectReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_DisconnectReply proto.InternalMessageInfo\n\nfunc (m *DisconnectReply) GetHas() bool {\n\tif m != nil {\n\t\treturn m.Has\n\t}\n\treturn false\n}\n\ntype HeartbeatReq struct {\n\tMid                  int64    `protobuf:\"varint,1,opt,name=mid,proto3\" json:\"mid,omitempty\"`\n\tKey                  string   `protobuf:\"bytes,2,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tServer               string   `protobuf:\"bytes,3,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *HeartbeatReq) Reset()         { *m = HeartbeatReq{} }\nfunc (m *HeartbeatReq) String() string { return proto.CompactTextString(m) }\nfunc (*HeartbeatReq) ProtoMessage()    {}\nfunc (*HeartbeatReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{5}\n}\n\nfunc (m *HeartbeatReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_HeartbeatReq.Unmarshal(m, b)\n}\nfunc (m *HeartbeatReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_HeartbeatReq.Marshal(b, m, deterministic)\n}\nfunc (m *HeartbeatReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_HeartbeatReq.Merge(m, src)\n}\nfunc (m *HeartbeatReq) XXX_Size() int {\n\treturn xxx_messageInfo_HeartbeatReq.Size(m)\n}\nfunc (m *HeartbeatReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_HeartbeatReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_HeartbeatReq proto.InternalMessageInfo\n\nfunc (m *HeartbeatReq) GetMid() int64 {\n\tif m != nil {\n\t\treturn m.Mid\n\t}\n\treturn 0\n}\n\nfunc (m *HeartbeatReq) GetKey() string {\n\tif m != nil {\n\t\treturn m.Key\n\t}\n\treturn \"\"\n}\n\nfunc (m *HeartbeatReq) GetServer() string {\n\tif m != nil {\n\t\treturn m.Server\n\t}\n\treturn \"\"\n}\n\ntype HeartbeatReply struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *HeartbeatReply) Reset()         { *m = HeartbeatReply{} }\nfunc (m *HeartbeatReply) String() string { return proto.CompactTextString(m) }\nfunc (*HeartbeatReply) ProtoMessage()    {}\nfunc (*HeartbeatReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{6}\n}\n\nfunc (m *HeartbeatReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_HeartbeatReply.Unmarshal(m, b)\n}\nfunc (m *HeartbeatReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_HeartbeatReply.Marshal(b, m, deterministic)\n}\nfunc (m *HeartbeatReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_HeartbeatReply.Merge(m, src)\n}\nfunc (m *HeartbeatReply) XXX_Size() int {\n\treturn xxx_messageInfo_HeartbeatReply.Size(m)\n}\nfunc (m *HeartbeatReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_HeartbeatReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_HeartbeatReply proto.InternalMessageInfo\n\ntype OnlineReq struct {\n\tServer               string           `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\tRoomCount            map[string]int32 `protobuf:\"bytes,2,rep,name=roomCount,proto3\" json:\"roomCount,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"varint,2,opt,name=value,proto3\"`\n\tXXX_NoUnkeyedLiteral struct{}         `json:\"-\"`\n\tXXX_unrecognized     []byte           `json:\"-\"`\n\tXXX_sizecache        int32            `json:\"-\"`\n}\n\nfunc (m *OnlineReq) Reset()         { *m = OnlineReq{} }\nfunc (m *OnlineReq) String() string { return proto.CompactTextString(m) }\nfunc (*OnlineReq) ProtoMessage()    {}\nfunc (*OnlineReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{7}\n}\n\nfunc (m *OnlineReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_OnlineReq.Unmarshal(m, b)\n}\nfunc (m *OnlineReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_OnlineReq.Marshal(b, m, deterministic)\n}\nfunc (m *OnlineReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_OnlineReq.Merge(m, src)\n}\nfunc (m *OnlineReq) XXX_Size() int {\n\treturn xxx_messageInfo_OnlineReq.Size(m)\n}\nfunc (m *OnlineReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_OnlineReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_OnlineReq proto.InternalMessageInfo\n\nfunc (m *OnlineReq) GetServer() string {\n\tif m != nil {\n\t\treturn m.Server\n\t}\n\treturn \"\"\n}\n\nfunc (m *OnlineReq) GetRoomCount() map[string]int32 {\n\tif m != nil {\n\t\treturn m.RoomCount\n\t}\n\treturn nil\n}\n\ntype OnlineReply struct {\n\tAllRoomCount         map[string]int32 `protobuf:\"bytes,1,rep,name=allRoomCount,proto3\" json:\"allRoomCount,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"varint,2,opt,name=value,proto3\"`\n\tXXX_NoUnkeyedLiteral struct{}         `json:\"-\"`\n\tXXX_unrecognized     []byte           `json:\"-\"`\n\tXXX_sizecache        int32            `json:\"-\"`\n}\n\nfunc (m *OnlineReply) Reset()         { *m = OnlineReply{} }\nfunc (m *OnlineReply) String() string { return proto.CompactTextString(m) }\nfunc (*OnlineReply) ProtoMessage()    {}\nfunc (*OnlineReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{8}\n}\n\nfunc (m *OnlineReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_OnlineReply.Unmarshal(m, b)\n}\nfunc (m *OnlineReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_OnlineReply.Marshal(b, m, deterministic)\n}\nfunc (m *OnlineReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_OnlineReply.Merge(m, src)\n}\nfunc (m *OnlineReply) XXX_Size() int {\n\treturn xxx_messageInfo_OnlineReply.Size(m)\n}\nfunc (m *OnlineReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_OnlineReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_OnlineReply proto.InternalMessageInfo\n\nfunc (m *OnlineReply) GetAllRoomCount() map[string]int32 {\n\tif m != nil {\n\t\treturn m.AllRoomCount\n\t}\n\treturn nil\n}\n\ntype ReceiveReq struct {\n\tMid                  int64           `protobuf:\"varint,1,opt,name=mid,proto3\" json:\"mid,omitempty\"`\n\tProto                *protocol.Proto `protobuf:\"bytes,2,opt,name=proto,proto3\" json:\"proto,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}        `json:\"-\"`\n\tXXX_unrecognized     []byte          `json:\"-\"`\n\tXXX_sizecache        int32           `json:\"-\"`\n}\n\nfunc (m *ReceiveReq) Reset()         { *m = ReceiveReq{} }\nfunc (m *ReceiveReq) String() string { return proto.CompactTextString(m) }\nfunc (*ReceiveReq) ProtoMessage()    {}\nfunc (*ReceiveReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{9}\n}\n\nfunc (m *ReceiveReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_ReceiveReq.Unmarshal(m, b)\n}\nfunc (m *ReceiveReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_ReceiveReq.Marshal(b, m, deterministic)\n}\nfunc (m *ReceiveReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_ReceiveReq.Merge(m, src)\n}\nfunc (m *ReceiveReq) XXX_Size() int {\n\treturn xxx_messageInfo_ReceiveReq.Size(m)\n}\nfunc (m *ReceiveReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_ReceiveReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_ReceiveReq proto.InternalMessageInfo\n\nfunc (m *ReceiveReq) GetMid() int64 {\n\tif m != nil {\n\t\treturn m.Mid\n\t}\n\treturn 0\n}\n\nfunc (m *ReceiveReq) GetProto() *protocol.Proto {\n\tif m != nil {\n\t\treturn m.Proto\n\t}\n\treturn nil\n}\n\ntype ReceiveReply struct {\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *ReceiveReply) Reset()         { *m = ReceiveReply{} }\nfunc (m *ReceiveReply) String() string { return proto.CompactTextString(m) }\nfunc (*ReceiveReply) ProtoMessage()    {}\nfunc (*ReceiveReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{10}\n}\n\nfunc (m *ReceiveReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_ReceiveReply.Unmarshal(m, b)\n}\nfunc (m *ReceiveReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_ReceiveReply.Marshal(b, m, deterministic)\n}\nfunc (m *ReceiveReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_ReceiveReply.Merge(m, src)\n}\nfunc (m *ReceiveReply) XXX_Size() int {\n\treturn xxx_messageInfo_ReceiveReply.Size(m)\n}\nfunc (m *ReceiveReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_ReceiveReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_ReceiveReply proto.InternalMessageInfo\n\ntype NodesReq struct {\n\tPlatform             string   `protobuf:\"bytes,1,opt,name=platform,proto3\" json:\"platform,omitempty\"`\n\tClientIP             string   `protobuf:\"bytes,2,opt,name=clientIP,proto3\" json:\"clientIP,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *NodesReq) Reset()         { *m = NodesReq{} }\nfunc (m *NodesReq) String() string { return proto.CompactTextString(m) }\nfunc (*NodesReq) ProtoMessage()    {}\nfunc (*NodesReq) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{11}\n}\n\nfunc (m *NodesReq) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_NodesReq.Unmarshal(m, b)\n}\nfunc (m *NodesReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_NodesReq.Marshal(b, m, deterministic)\n}\nfunc (m *NodesReq) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_NodesReq.Merge(m, src)\n}\nfunc (m *NodesReq) XXX_Size() int {\n\treturn xxx_messageInfo_NodesReq.Size(m)\n}\nfunc (m *NodesReq) XXX_DiscardUnknown() {\n\txxx_messageInfo_NodesReq.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_NodesReq proto.InternalMessageInfo\n\nfunc (m *NodesReq) GetPlatform() string {\n\tif m != nil {\n\t\treturn m.Platform\n\t}\n\treturn \"\"\n}\n\nfunc (m *NodesReq) GetClientIP() string {\n\tif m != nil {\n\t\treturn m.ClientIP\n\t}\n\treturn \"\"\n}\n\ntype NodesReply struct {\n\tDomain               string   `protobuf:\"bytes,1,opt,name=domain,proto3\" json:\"domain,omitempty\"`\n\tTcpPort              int32    `protobuf:\"varint,2,opt,name=tcp_port,json=tcpPort,proto3\" json:\"tcp_port,omitempty\"`\n\tWsPort               int32    `protobuf:\"varint,3,opt,name=ws_port,json=wsPort,proto3\" json:\"ws_port,omitempty\"`\n\tWssPort              int32    `protobuf:\"varint,4,opt,name=wss_port,json=wssPort,proto3\" json:\"wss_port,omitempty\"`\n\tHeartbeat            int32    `protobuf:\"varint,5,opt,name=heartbeat,proto3\" json:\"heartbeat,omitempty\"`\n\tNodes                []string `protobuf:\"bytes,6,rep,name=nodes,proto3\" json:\"nodes,omitempty\"`\n\tBackoff              *Backoff `protobuf:\"bytes,7,opt,name=backoff,proto3\" json:\"backoff,omitempty\"`\n\tHeartbeatMax         int32    `protobuf:\"varint,8,opt,name=heartbeat_max,json=heartbeatMax,proto3\" json:\"heartbeat_max,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *NodesReply) Reset()         { *m = NodesReply{} }\nfunc (m *NodesReply) String() string { return proto.CompactTextString(m) }\nfunc (*NodesReply) ProtoMessage()    {}\nfunc (*NodesReply) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{12}\n}\n\nfunc (m *NodesReply) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_NodesReply.Unmarshal(m, b)\n}\nfunc (m *NodesReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_NodesReply.Marshal(b, m, deterministic)\n}\nfunc (m *NodesReply) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_NodesReply.Merge(m, src)\n}\nfunc (m *NodesReply) XXX_Size() int {\n\treturn xxx_messageInfo_NodesReply.Size(m)\n}\nfunc (m *NodesReply) XXX_DiscardUnknown() {\n\txxx_messageInfo_NodesReply.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_NodesReply proto.InternalMessageInfo\n\nfunc (m *NodesReply) GetDomain() string {\n\tif m != nil {\n\t\treturn m.Domain\n\t}\n\treturn \"\"\n}\n\nfunc (m *NodesReply) GetTcpPort() int32 {\n\tif m != nil {\n\t\treturn m.TcpPort\n\t}\n\treturn 0\n}\n\nfunc (m *NodesReply) GetWsPort() int32 {\n\tif m != nil {\n\t\treturn m.WsPort\n\t}\n\treturn 0\n}\n\nfunc (m *NodesReply) GetWssPort() int32 {\n\tif m != nil {\n\t\treturn m.WssPort\n\t}\n\treturn 0\n}\n\nfunc (m *NodesReply) GetHeartbeat() int32 {\n\tif m != nil {\n\t\treturn m.Heartbeat\n\t}\n\treturn 0\n}\n\nfunc (m *NodesReply) GetNodes() []string {\n\tif m != nil {\n\t\treturn m.Nodes\n\t}\n\treturn nil\n}\n\nfunc (m *NodesReply) GetBackoff() *Backoff {\n\tif m != nil {\n\t\treturn m.Backoff\n\t}\n\treturn nil\n}\n\nfunc (m *NodesReply) GetHeartbeatMax() int32 {\n\tif m != nil {\n\t\treturn m.HeartbeatMax\n\t}\n\treturn 0\n}\n\ntype Backoff struct {\n\tMaxDelay             int32    `protobuf:\"varint,1,opt,name=max_delay,json=maxDelay,proto3\" json:\"max_delay,omitempty\"`\n\tBaseDelay            int32    `protobuf:\"varint,2,opt,name=base_delay,json=baseDelay,proto3\" json:\"base_delay,omitempty\"`\n\tFactor               float32  `protobuf:\"fixed32,3,opt,name=factor,proto3\" json:\"factor,omitempty\"`\n\tJitter               float32  `protobuf:\"fixed32,4,opt,name=jitter,proto3\" json:\"jitter,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Backoff) Reset()         { *m = Backoff{} }\nfunc (m *Backoff) String() string { return proto.CompactTextString(m) }\nfunc (*Backoff) ProtoMessage()    {}\nfunc (*Backoff) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_2dfb3aef05fe3328, []int{13}\n}\n\nfunc (m *Backoff) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_Backoff.Unmarshal(m, b)\n}\nfunc (m *Backoff) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_Backoff.Marshal(b, m, deterministic)\n}\nfunc (m *Backoff) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Backoff.Merge(m, src)\n}\nfunc (m *Backoff) XXX_Size() int {\n\treturn xxx_messageInfo_Backoff.Size(m)\n}\nfunc (m *Backoff) XXX_DiscardUnknown() {\n\txxx_messageInfo_Backoff.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Backoff proto.InternalMessageInfo\n\nfunc (m *Backoff) GetMaxDelay() int32 {\n\tif m != nil {\n\t\treturn m.MaxDelay\n\t}\n\treturn 0\n}\n\nfunc (m *Backoff) GetBaseDelay() int32 {\n\tif m != nil {\n\t\treturn m.BaseDelay\n\t}\n\treturn 0\n}\n\nfunc (m *Backoff) GetFactor() float32 {\n\tif m != nil {\n\t\treturn m.Factor\n\t}\n\treturn 0\n}\n\nfunc (m *Backoff) GetJitter() float32 {\n\tif m != nil {\n\t\treturn m.Jitter\n\t}\n\treturn 0\n}\n\nfunc init() {\n\tproto.RegisterEnum(\"goim.logic.PushMsg_Type\", PushMsg_Type_name, PushMsg_Type_value)\n\tproto.RegisterType((*PushMsg)(nil), \"goim.logic.PushMsg\")\n\tproto.RegisterType((*ConnectReq)(nil), \"goim.logic.ConnectReq\")\n\tproto.RegisterType((*ConnectReply)(nil), \"goim.logic.ConnectReply\")\n\tproto.RegisterType((*DisconnectReq)(nil), \"goim.logic.DisconnectReq\")\n\tproto.RegisterType((*DisconnectReply)(nil), \"goim.logic.DisconnectReply\")\n\tproto.RegisterType((*HeartbeatReq)(nil), \"goim.logic.HeartbeatReq\")\n\tproto.RegisterType((*HeartbeatReply)(nil), \"goim.logic.HeartbeatReply\")\n\tproto.RegisterType((*OnlineReq)(nil), \"goim.logic.OnlineReq\")\n\tproto.RegisterMapType((map[string]int32)(nil), \"goim.logic.OnlineReq.RoomCountEntry\")\n\tproto.RegisterType((*OnlineReply)(nil), \"goim.logic.OnlineReply\")\n\tproto.RegisterMapType((map[string]int32)(nil), \"goim.logic.OnlineReply.AllRoomCountEntry\")\n\tproto.RegisterType((*ReceiveReq)(nil), \"goim.logic.ReceiveReq\")\n\tproto.RegisterType((*ReceiveReply)(nil), \"goim.logic.ReceiveReply\")\n\tproto.RegisterType((*NodesReq)(nil), \"goim.logic.NodesReq\")\n\tproto.RegisterType((*NodesReply)(nil), \"goim.logic.NodesReply\")\n\tproto.RegisterType((*Backoff)(nil), \"goim.logic.Backoff\")\n}\n\nfunc init() { proto.RegisterFile(\"logic/logic.proto\", fileDescriptor_2dfb3aef05fe3328) }\n\nvar fileDescriptor_2dfb3aef05fe3328 = []byte{\n\t// 893 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x4f, 0x6f, 0xe3, 0x44,\n\t0x14, 0xc7, 0x71, 0x9c, 0xc4, 0x2f, 0x69, 0xc9, 0x0e, 0x25, 0xeb, 0x7a, 0x41, 0x8a, 0xbc, 0x1c,\n\t0x52, 0x60, 0x13, 0x29, 0x68, 0x25, 0xc4, 0x82, 0x50, 0xd3, 0x20, 0xed, 0x2e, 0x84, 0x46, 0xb3,\n\t0xe5, 0xc2, 0xa5, 0x9a, 0x38, 0xd3, 0xd4, 0xc4, 0xf6, 0x18, 0x7b, 0xd2, 0xd4, 0x37, 0xc4, 0xf7,\n\t0xe0, 0xc8, 0x77, 0xe3, 0x3b, 0x70, 0x41, 0xf3, 0x27, 0xb6, 0xa3, 0x4d, 0x57, 0x20, 0x2e, 0xd6,\n\t0xfb, 0x37, 0xbf, 0xf9, 0xbd, 0xf7, 0xe6, 0x3d, 0xc3, 0xa3, 0x90, 0xad, 0x02, 0x7f, 0x24, 0xbf,\n\t0xc3, 0x24, 0x65, 0x9c, 0x21, 0x58, 0xb1, 0x20, 0x1a, 0x4a, 0x8b, 0xfb, 0x7c, 0x15, 0xf0, 0xdb,\n\t0xcd, 0x62, 0xe8, 0xb3, 0x68, 0x74, 0x45, 0xd3, 0x34, 0x7f, 0x36, 0x23, 0x6c, 0x24, 0x02, 0x46,\n\t0x24, 0x09, 0x46, 0xf2, 0x80, 0xcf, 0xc2, 0x42, 0x50, 0x10, 0xde, 0x5f, 0x06, 0x34, 0xe7, 0x9b,\n\t0xec, 0x76, 0x96, 0xad, 0xd0, 0xe7, 0x50, 0xe7, 0x79, 0x42, 0x1d, 0xa3, 0x6f, 0x0c, 0x8e, 0xc7,\n\t0xce, 0xb0, 0x44, 0x1f, 0xea, 0x90, 0xe1, 0x55, 0x9e, 0x50, 0x2c, 0xa3, 0xd0, 0x47, 0x60, 0xb3,\n\t0x84, 0xa6, 0x84, 0x07, 0x2c, 0x76, 0x6a, 0x7d, 0x63, 0x60, 0xe1, 0xd2, 0x80, 0x4e, 0xc0, 0xca,\n\t0x12, 0x4a, 0x97, 0x8e, 0x29, 0x3d, 0x4a, 0x41, 0x3d, 0x68, 0x64, 0x34, 0xbd, 0xa3, 0xa9, 0x53,\n\t0xef, 0x1b, 0x03, 0x1b, 0x6b, 0x0d, 0x21, 0xa8, 0xa7, 0x8c, 0x45, 0x8e, 0x25, 0xad, 0x52, 0x16,\n\t0xb6, 0x35, 0xcd, 0x33, 0xa7, 0xd1, 0x37, 0x85, 0x4d, 0xc8, 0xa8, 0x0b, 0x66, 0x94, 0xad, 0x9c,\n\t0x66, 0xdf, 0x18, 0x74, 0xb0, 0x10, 0xbd, 0x33, 0xa8, 0x0b, 0x4e, 0xa8, 0x05, 0xf5, 0xf9, 0x4f,\n\t0x6f, 0x5e, 0x76, 0xdf, 0x13, 0x12, 0xbe, 0xbc, 0x9c, 0x75, 0x0d, 0x74, 0x04, 0xf6, 0x04, 0x5f,\n\t0x9e, 0x4f, 0x2f, 0xce, 0xdf, 0x5c, 0x75, 0x6b, 0x1e, 0x06, 0xb8, 0x60, 0x71, 0x4c, 0x7d, 0x8e,\n\t0xe9, 0xaf, 0x15, 0x2a, 0xc6, 0x1e, 0x95, 0x1e, 0x34, 0x7c, 0xc6, 0xd6, 0x01, 0x95, 0x39, 0xd9,\n\t0x58, 0x6b, 0x22, 0x21, 0xce, 0xd6, 0x34, 0x96, 0x09, 0x75, 0xb0, 0x52, 0xbc, 0xdf, 0x0d, 0xe8,\n\t0x14, 0xa0, 0x49, 0x98, 0x4b, 0x86, 0xc1, 0x52, 0x62, 0x9a, 0x58, 0x88, 0xc2, 0xb2, 0xa6, 0xb9,\n\t0x46, 0x13, 0xa2, 0xb8, 0x42, 0x64, 0xf8, 0x6a, 0x2a, 0xb1, 0x6c, 0xac, 0x35, 0xe4, 0x40, 0x93,\n\t0xf8, 0x3e, 0x4d, 0x78, 0xe6, 0xd4, 0xfb, 0xe6, 0xc0, 0xc2, 0x3b, 0x55, 0xd4, 0xfa, 0x96, 0x92,\n\t0x94, 0x2f, 0x28, 0xe1, 0xb2, 0x48, 0x26, 0x2e, 0x0d, 0xde, 0xf7, 0x70, 0x34, 0x0d, 0x32, 0xbf,\n\t0xcc, 0xed, 0x5f, 0x92, 0xd0, 0xf9, 0x9b, 0xd5, 0xfc, 0xbd, 0xa7, 0xf0, 0x7e, 0x15, 0x4c, 0xe7,\n\t0x74, 0x4b, 0x32, 0x09, 0xd7, 0xc2, 0x42, 0xf4, 0x5e, 0x43, 0xe7, 0xe5, 0xee, 0xfa, 0xff, 0x7b,\n\t0x61, 0x17, 0x8e, 0x2b, 0x58, 0x49, 0x98, 0x7b, 0x7f, 0x1a, 0x60, 0x5f, 0xc6, 0x61, 0x10, 0xd3,\n\t0x77, 0x35, 0x6a, 0x02, 0xb6, 0xa8, 0xdb, 0x05, 0xdb, 0xc4, 0xdc, 0xa9, 0xf5, 0xcd, 0x41, 0x7b,\n\t0xfc, 0x49, 0xf5, 0xc9, 0x16, 0x08, 0x43, 0xbc, 0x0b, 0xfb, 0x2e, 0xe6, 0x69, 0x8e, 0xcb, 0x63,\n\t0xee, 0xd7, 0x70, 0xbc, 0xef, 0xdc, 0xf1, 0x36, 0x4a, 0xde, 0x27, 0x60, 0xdd, 0x91, 0x70, 0x43,\n\t0xf5, 0x1b, 0x57, 0xca, 0x57, 0xb5, 0x2f, 0x0d, 0xef, 0x0f, 0x03, 0xda, 0xbb, 0x5b, 0x44, 0x9d,\n\t0x66, 0xd0, 0x21, 0x61, 0x58, 0x00, 0x3a, 0x86, 0x24, 0x75, 0x76, 0x88, 0x54, 0x12, 0xe6, 0xc3,\n\t0xf3, 0x4a, 0xac, 0x62, 0xb6, 0x77, 0xdc, 0xfd, 0x16, 0x1e, 0xbd, 0x15, 0xf2, 0x9f, 0xf8, 0xbd,\n\t0x06, 0xc0, 0xd4, 0xa7, 0xc1, 0x1d, 0x3d, 0xdc, 0xa3, 0x4f, 0xc1, 0x92, 0x4b, 0x40, 0x9e, 0x6c,\n\t0x8f, 0x4f, 0x14, 0xd1, 0x62, 0x41, 0xcc, 0x85, 0x80, 0x55, 0x88, 0x77, 0x0c, 0x9d, 0x02, 0x4b,\n\t0xf4, 0x68, 0x02, 0xad, 0x1f, 0xd9, 0x92, 0x66, 0x02, 0xd9, 0x85, 0x56, 0x12, 0x12, 0x7e, 0xc3,\n\t0xd2, 0x48, 0x13, 0x2b, 0x74, 0xe1, 0xf3, 0xc3, 0x80, 0xc6, 0xfc, 0xd5, 0x5c, 0x3f, 0x86, 0x42,\n\t0xf7, 0xfe, 0x36, 0x00, 0x34, 0x88, 0x28, 0x5f, 0x0f, 0x1a, 0x4b, 0x16, 0x91, 0x20, 0xde, 0x35,\n\t0x5a, 0x69, 0xe8, 0x14, 0x5a, 0xdc, 0x4f, 0xae, 0x13, 0x96, 0x72, 0x9d, 0x63, 0x93, 0xfb, 0xc9,\n\t0x9c, 0xa5, 0x1c, 0x3d, 0x86, 0xe6, 0x36, 0x53, 0x1e, 0xb5, 0x67, 0x1a, 0xdb, 0x4c, 0x3a, 0x4e,\n\t0xa1, 0xb5, 0xcd, 0xb4, 0xa7, 0xae, 0xce, 0x6c, 0x33, 0xe5, 0x7a, 0x6b, 0x96, 0xac, 0xca, 0x2c,\n\t0x89, 0x6a, 0xc6, 0x82, 0x92, 0x5e, 0x3b, 0x4a, 0x41, 0xcf, 0xa0, 0xb9, 0x20, 0xfe, 0x9a, 0xdd,\n\t0xdc, 0xc8, 0xdd, 0xd3, 0x1e, 0x7f, 0x50, 0x6d, 0xea, 0x44, 0xb9, 0xf0, 0x2e, 0x06, 0x3d, 0x85,\n\t0xa3, 0x02, 0xf1, 0x3a, 0x22, 0xf7, 0x4e, 0x4b, 0x5e, 0xd3, 0x29, 0x8c, 0x33, 0x72, 0xef, 0x6d,\n\t0xa0, 0xa9, 0x0f, 0xa2, 0x27, 0x60, 0x47, 0xe4, 0xfe, 0x7a, 0x49, 0x43, 0xa2, 0x5a, 0x6b, 0xe1,\n\t0x56, 0x44, 0xee, 0xa7, 0x42, 0x47, 0x1f, 0x03, 0x2c, 0x48, 0x46, 0xb5, 0x57, 0x2f, 0x5a, 0x61,\n\t0x51, 0xee, 0x1e, 0x34, 0x6e, 0x88, 0xcf, 0x99, 0x1a, 0xab, 0x1a, 0xd6, 0x9a, 0xb0, 0xff, 0x12,\n\t0x70, 0xae, 0x57, 0x6d, 0x0d, 0x6b, 0x6d, 0xfc, 0x9b, 0x09, 0xd6, 0x0f, 0x82, 0x36, 0x7a, 0x01,\n\t0x4d, 0xbd, 0xba, 0x50, 0xaf, 0x9a, 0x4e, 0xb9, 0x24, 0x5d, 0xe7, 0xa0, 0x5d, 0x34, 0x6b, 0x0a,\n\t0x50, 0xae, 0x09, 0x74, 0x5a, 0x8d, 0xdb, 0xdb, 0x45, 0xee, 0x93, 0x87, 0x5c, 0x02, 0xe5, 0x1c,\n\t0xec, 0x62, 0xf6, 0xd1, 0xde, 0x65, 0xd5, 0xf5, 0xe2, 0xba, 0x0f, 0x78, 0x04, 0xc4, 0x37, 0xd0,\n\t0xc6, 0x34, 0xa6, 0x5b, 0x35, 0x59, 0xe8, 0xc3, 0x83, 0x2b, 0xc0, 0x7d, 0xfc, 0xc0, 0x10, 0x8a,\n\t0x22, 0xe8, 0x77, 0xbd, 0x5f, 0x84, 0x72, 0x70, 0xf6, 0x8b, 0x50, 0x1d, 0x02, 0xf4, 0x1c, 0x2c,\n\t0xf9, 0x7e, 0xd1, 0x49, 0x35, 0x64, 0x37, 0x17, 0x6e, 0xef, 0x80, 0x35, 0x09, 0xf3, 0xc9, 0x67,\n\t0x3f, 0x9f, 0xbd, 0xfb, 0x67, 0x2d, 0x4f, 0xbc, 0x90, 0xdf, 0x45, 0x43, 0xce, 0xdf, 0x17, 0xff,\n\t0x04, 0x00, 0x00, 0xff, 0xff, 0x6f, 0x34, 0x54, 0x90, 0xff, 0x07, 0x00, 0x00,\n}\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ context.Context\nvar _ grpc.ClientConn\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\nconst _ = grpc.SupportPackageIsVersion4\n\n// LogicClient is the client API for Logic service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.\ntype LogicClient interface {\n\t// Connect\n\tConnect(ctx context.Context, in *ConnectReq, opts ...grpc.CallOption) (*ConnectReply, error)\n\t// Disconnect\n\tDisconnect(ctx context.Context, in *DisconnectReq, opts ...grpc.CallOption) (*DisconnectReply, error)\n\t// Heartbeat\n\tHeartbeat(ctx context.Context, in *HeartbeatReq, opts ...grpc.CallOption) (*HeartbeatReply, error)\n\t// RenewOnline\n\tRenewOnline(ctx context.Context, in *OnlineReq, opts ...grpc.CallOption) (*OnlineReply, error)\n\t// Receive\n\tReceive(ctx context.Context, in *ReceiveReq, opts ...grpc.CallOption) (*ReceiveReply, error)\n\t//ServerList\n\tNodes(ctx context.Context, in *NodesReq, opts ...grpc.CallOption) (*NodesReply, error)\n}\n\ntype logicClient struct {\n\tcc *grpc.ClientConn\n}\n\nfunc NewLogicClient(cc *grpc.ClientConn) LogicClient {\n\treturn &logicClient{cc}\n}\n\nfunc (c *logicClient) Connect(ctx context.Context, in *ConnectReq, opts ...grpc.CallOption) (*ConnectReply, error) {\n\tout := new(ConnectReply)\n\terr := c.cc.Invoke(ctx, \"/goim.logic.Logic/Connect\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *logicClient) Disconnect(ctx context.Context, in *DisconnectReq, opts ...grpc.CallOption) (*DisconnectReply, error) {\n\tout := new(DisconnectReply)\n\terr := c.cc.Invoke(ctx, \"/goim.logic.Logic/Disconnect\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *logicClient) Heartbeat(ctx context.Context, in *HeartbeatReq, opts ...grpc.CallOption) (*HeartbeatReply, error) {\n\tout := new(HeartbeatReply)\n\terr := c.cc.Invoke(ctx, \"/goim.logic.Logic/Heartbeat\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *logicClient) RenewOnline(ctx context.Context, in *OnlineReq, opts ...grpc.CallOption) (*OnlineReply, error) {\n\tout := new(OnlineReply)\n\terr := c.cc.Invoke(ctx, \"/goim.logic.Logic/RenewOnline\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *logicClient) Receive(ctx context.Context, in *ReceiveReq, opts ...grpc.CallOption) (*ReceiveReply, error) {\n\tout := new(ReceiveReply)\n\terr := c.cc.Invoke(ctx, \"/goim.logic.Logic/Receive\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *logicClient) Nodes(ctx context.Context, in *NodesReq, opts ...grpc.CallOption) (*NodesReply, error) {\n\tout := new(NodesReply)\n\terr := c.cc.Invoke(ctx, \"/goim.logic.Logic/Nodes\", in, out, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// LogicServer is the server API for Logic service.\ntype LogicServer interface {\n\t// Connect\n\tConnect(context.Context, *ConnectReq) (*ConnectReply, error)\n\t// Disconnect\n\tDisconnect(context.Context, *DisconnectReq) (*DisconnectReply, error)\n\t// Heartbeat\n\tHeartbeat(context.Context, *HeartbeatReq) (*HeartbeatReply, error)\n\t// RenewOnline\n\tRenewOnline(context.Context, *OnlineReq) (*OnlineReply, error)\n\t// Receive\n\tReceive(context.Context, *ReceiveReq) (*ReceiveReply, error)\n\t//ServerList\n\tNodes(context.Context, *NodesReq) (*NodesReply, error)\n}\n\n// UnimplementedLogicServer can be embedded to have forward compatible implementations.\ntype UnimplementedLogicServer struct {\n}\n\nfunc (*UnimplementedLogicServer) Connect(ctx context.Context, req *ConnectReq) (*ConnectReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Connect not implemented\")\n}\nfunc (*UnimplementedLogicServer) Disconnect(ctx context.Context, req *DisconnectReq) (*DisconnectReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Disconnect not implemented\")\n}\nfunc (*UnimplementedLogicServer) Heartbeat(ctx context.Context, req *HeartbeatReq) (*HeartbeatReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Heartbeat not implemented\")\n}\nfunc (*UnimplementedLogicServer) RenewOnline(ctx context.Context, req *OnlineReq) (*OnlineReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method RenewOnline not implemented\")\n}\nfunc (*UnimplementedLogicServer) Receive(ctx context.Context, req *ReceiveReq) (*ReceiveReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Receive not implemented\")\n}\nfunc (*UnimplementedLogicServer) Nodes(ctx context.Context, req *NodesReq) (*NodesReply, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Nodes not implemented\")\n}\n\nfunc RegisterLogicServer(s *grpc.Server, srv LogicServer) {\n\ts.RegisterService(&_Logic_serviceDesc, srv)\n}\n\nfunc _Logic_Connect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ConnectReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LogicServer).Connect(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.logic.Logic/Connect\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LogicServer).Connect(ctx, req.(*ConnectReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Logic_Disconnect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DisconnectReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LogicServer).Disconnect(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.logic.Logic/Disconnect\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LogicServer).Disconnect(ctx, req.(*DisconnectReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Logic_Heartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(HeartbeatReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LogicServer).Heartbeat(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.logic.Logic/Heartbeat\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LogicServer).Heartbeat(ctx, req.(*HeartbeatReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Logic_RenewOnline_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(OnlineReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LogicServer).RenewOnline(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.logic.Logic/RenewOnline\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LogicServer).RenewOnline(ctx, req.(*OnlineReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Logic_Receive_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ReceiveReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LogicServer).Receive(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.logic.Logic/Receive\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LogicServer).Receive(ctx, req.(*ReceiveReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Logic_Nodes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(NodesReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(LogicServer).Nodes(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: \"/goim.logic.Logic/Nodes\",\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(LogicServer).Nodes(ctx, req.(*NodesReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nvar _Logic_serviceDesc = grpc.ServiceDesc{\n\tServiceName: \"goim.logic.Logic\",\n\tHandlerType: (*LogicServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Connect\",\n\t\t\tHandler:    _Logic_Connect_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Disconnect\",\n\t\t\tHandler:    _Logic_Disconnect_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Heartbeat\",\n\t\t\tHandler:    _Logic_Heartbeat_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RenewOnline\",\n\t\t\tHandler:    _Logic_RenewOnline_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Receive\",\n\t\t\tHandler:    _Logic_Receive_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Nodes\",\n\t\t\tHandler:    _Logic_Nodes_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"logic/logic.proto\",\n}\n"
  },
  {
    "path": "api/logic/logic.proto",
    "content": "syntax = \"proto3\";\n\npackage goim.logic;\n\noption go_package = \"github.com/Terry-Mao/goim/api/logic;logic\";\n\nimport \"github.com/Terry-Mao/goim/api/protocol/protocol.proto\";\n\nmessage PushMsg {\n    enum Type {\n        PUSH = 0;\n        ROOM = 1;\n        BROADCAST = 2;\n    }\n    Type type = 1;\n    int32 operation = 2;\n    int32 speed = 3;\n    string server = 4;\n    string room = 5;\n    repeated string keys = 6;\n    bytes msg = 7;\n}\n\nmessage ConnectReq {\n    string server = 1;\n    string cookie = 2;\n    bytes token = 3;\n}\n\nmessage ConnectReply {\n    int64 mid = 1;\n    string key = 2;\n    string roomID = 3;\n    repeated int32 accepts = 4;\n    int64 heartbeat = 5;\n}\n\nmessage DisconnectReq {\n    int64 mid = 1;\n    string key = 2;\n    string server = 3;\n}\n\nmessage DisconnectReply {\n    bool has = 1;\n}\n\nmessage HeartbeatReq {\n    int64 mid = 1;\n    string key = 2;\n    string server = 3;\n}\n\nmessage HeartbeatReply {\n}\n\nmessage OnlineReq {\n    string server = 1;\n    map<string, int32> roomCount = 2;\n}\n\nmessage OnlineReply {\n    map<string, int32> allRoomCount = 1;\n}\n\nmessage ReceiveReq {\n    int64 mid = 1;\n    goim.protocol.Proto proto = 2;\n}\n\nmessage ReceiveReply {\n}\n\nmessage NodesReq {\n\tstring platform = 1;\n\tstring clientIP = 2;\n}\n\nmessage NodesReply {\n\tstring domain = 1;\n\tint32 tcp_port = 2;\n\tint32 ws_port = 3;\n\tint32 wss_port = 4;\n\tint32 heartbeat = 5;\n\trepeated string nodes = 6;\n\tBackoff backoff = 7;\n\tint32 heartbeat_max = 8;\n}\n\nmessage Backoff {\n\tint32\tmax_delay = 1;\n\tint32\tbase_delay = 2;\n\tfloat\tfactor = 3;\n\tfloat\tjitter = 4;\n}\n\nservice Logic {\n    // Connect\n    rpc Connect(ConnectReq) returns (ConnectReply);\n    // Disconnect\n    rpc Disconnect(DisconnectReq) returns (DisconnectReply);\n    // Heartbeat\n    rpc Heartbeat(HeartbeatReq) returns (HeartbeatReply);\n    // RenewOnline\n    rpc RenewOnline(OnlineReq) returns (OnlineReply);\n    // Receive\n    rpc Receive(ReceiveReq) returns (ReceiveReply);\n\t//ServerList\n\trpc Nodes(NodesReq) returns (NodesReply);\n}\n"
  },
  {
    "path": "api/protocol/operation.go",
    "content": "package protocol\n\nconst (\n\t// OpHandshake handshake\n\tOpHandshake = int32(0)\n\t// OpHandshakeReply handshake reply\n\tOpHandshakeReply = int32(1)\n\n\t// OpHeartbeat heartbeat\n\tOpHeartbeat = int32(2)\n\t// OpHeartbeatReply heartbeat reply\n\tOpHeartbeatReply = int32(3)\n\n\t// OpSendMsg send message.\n\tOpSendMsg = int32(4)\n\t// OpSendMsgReply  send message reply\n\tOpSendMsgReply = int32(5)\n\n\t// OpDisconnectReply disconnect reply\n\tOpDisconnectReply = int32(6)\n\n\t// OpAuth auth connnect\n\tOpAuth = int32(7)\n\t// OpAuthReply auth connect reply\n\tOpAuthReply = int32(8)\n\n\t// OpRaw raw message\n\tOpRaw = int32(9)\n\n\t// OpProtoReady proto ready\n\tOpProtoReady = int32(10)\n\t// OpProtoFinish proto finish\n\tOpProtoFinish = int32(11)\n\n\t// OpChangeRoom change room\n\tOpChangeRoom = int32(12)\n\t// OpChangeRoomReply change room reply\n\tOpChangeRoomReply = int32(13)\n\n\t// OpSub subscribe operation\n\tOpSub = int32(14)\n\t// OpSubReply subscribe operation\n\tOpSubReply = int32(15)\n\n\t// OpUnsub unsubscribe operation\n\tOpUnsub = int32(16)\n\t// OpUnsubReply unsubscribe operation reply\n\tOpUnsubReply = int32(17)\n)\n"
  },
  {
    "path": "api/protocol/protocol.go",
    "content": "package protocol\n\nimport (\n\t\"errors\"\n\n\t\"github.com/Terry-Mao/goim/pkg/bufio\"\n\t\"github.com/Terry-Mao/goim/pkg/bytes\"\n\t\"github.com/Terry-Mao/goim/pkg/encoding/binary\"\n\t\"github.com/Terry-Mao/goim/pkg/websocket\"\n)\n\nconst (\n\t// MaxBodySize max proto body size\n\tMaxBodySize = int32(1 << 12)\n)\n\nconst (\n\t// size\n\t_packSize      = 4\n\t_headerSize    = 2\n\t_verSize       = 2\n\t_opSize        = 4\n\t_seqSize       = 4\n\t_heartSize     = 4\n\t_rawHeaderSize = _packSize + _headerSize + _verSize + _opSize + _seqSize\n\t_maxPackSize   = MaxBodySize + int32(_rawHeaderSize)\n\t// offset\n\t_packOffset   = 0\n\t_headerOffset = _packOffset + _packSize\n\t_verOffset    = _headerOffset + _headerSize\n\t_opOffset     = _verOffset + _verSize\n\t_seqOffset    = _opOffset + _opSize\n\t_heartOffset  = _seqOffset + _seqSize\n)\n\nvar (\n\t// ErrProtoPackLen proto packet len error\n\tErrProtoPackLen = errors.New(\"default server codec pack length error\")\n\t// ErrProtoHeaderLen proto header len error\n\tErrProtoHeaderLen = errors.New(\"default server codec header length error\")\n)\n\nvar (\n\t// ProtoReady proto ready\n\tProtoReady = &Proto{Op: OpProtoReady}\n\t// ProtoFinish proto finish\n\tProtoFinish = &Proto{Op: OpProtoFinish}\n)\n\n// WriteTo write a proto to bytes writer.\nfunc (p *Proto) WriteTo(b *bytes.Writer) {\n\tvar (\n\t\tpackLen = _rawHeaderSize + int32(len(p.Body))\n\t\tbuf     = b.Peek(_rawHeaderSize)\n\t)\n\tbinary.BigEndian.PutInt32(buf[_packOffset:], packLen)\n\tbinary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))\n\tbinary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))\n\tbinary.BigEndian.PutInt32(buf[_opOffset:], p.Op)\n\tbinary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)\n\tif p.Body != nil {\n\t\tb.Write(p.Body)\n\t}\n}\n\n// ReadTCP read a proto from TCP reader.\nfunc (p *Proto) ReadTCP(rr *bufio.Reader) (err error) {\n\tvar (\n\t\tbodyLen   int\n\t\theaderLen int16\n\t\tpackLen   int32\n\t\tbuf       []byte\n\t)\n\tif buf, err = rr.Pop(_rawHeaderSize); err != nil {\n\t\treturn\n\t}\n\tpackLen = binary.BigEndian.Int32(buf[_packOffset:_headerOffset])\n\theaderLen = binary.BigEndian.Int16(buf[_headerOffset:_verOffset])\n\tp.Ver = int32(binary.BigEndian.Int16(buf[_verOffset:_opOffset]))\n\tp.Op = binary.BigEndian.Int32(buf[_opOffset:_seqOffset])\n\tp.Seq = binary.BigEndian.Int32(buf[_seqOffset:])\n\tif packLen > _maxPackSize {\n\t\treturn ErrProtoPackLen\n\t}\n\tif headerLen != _rawHeaderSize {\n\t\treturn ErrProtoHeaderLen\n\t}\n\tif bodyLen = int(packLen - int32(headerLen)); bodyLen > 0 {\n\t\tp.Body, err = rr.Pop(bodyLen)\n\t} else {\n\t\tp.Body = nil\n\t}\n\treturn\n}\n\n// WriteTCP write a proto to TCP writer.\nfunc (p *Proto) WriteTCP(wr *bufio.Writer) (err error) {\n\tvar (\n\t\tbuf     []byte\n\t\tpackLen int32\n\t)\n\tif p.Op == OpRaw {\n\t\t// write without buffer, job concact proto into raw buffer\n\t\t_, err = wr.WriteRaw(p.Body)\n\t\treturn\n\t}\n\tpackLen = _rawHeaderSize + int32(len(p.Body))\n\tif buf, err = wr.Peek(_rawHeaderSize); err != nil {\n\t\treturn\n\t}\n\tbinary.BigEndian.PutInt32(buf[_packOffset:], packLen)\n\tbinary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))\n\tbinary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))\n\tbinary.BigEndian.PutInt32(buf[_opOffset:], p.Op)\n\tbinary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)\n\tif p.Body != nil {\n\t\t_, err = wr.Write(p.Body)\n\t}\n\treturn\n}\n\n// WriteTCPHeart write TCP heartbeat with room online.\nfunc (p *Proto) WriteTCPHeart(wr *bufio.Writer, online int32) (err error) {\n\tvar (\n\t\tbuf     []byte\n\t\tpackLen int\n\t)\n\tpackLen = _rawHeaderSize + _heartSize\n\tif buf, err = wr.Peek(packLen); err != nil {\n\t\treturn\n\t}\n\t// header\n\tbinary.BigEndian.PutInt32(buf[_packOffset:], int32(packLen))\n\tbinary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))\n\tbinary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))\n\tbinary.BigEndian.PutInt32(buf[_opOffset:], p.Op)\n\tbinary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)\n\t// body\n\tbinary.BigEndian.PutInt32(buf[_heartOffset:], online)\n\treturn\n}\n\n// ReadWebsocket read a proto from websocket connection.\nfunc (p *Proto) ReadWebsocket(ws *websocket.Conn) (err error) {\n\tvar (\n\t\tbodyLen   int\n\t\theaderLen int16\n\t\tpackLen   int32\n\t\tbuf       []byte\n\t)\n\tif _, buf, err = ws.ReadMessage(); err != nil {\n\t\treturn\n\t}\n\tif len(buf) < _rawHeaderSize {\n\t\treturn ErrProtoPackLen\n\t}\n\tpackLen = binary.BigEndian.Int32(buf[_packOffset:_headerOffset])\n\theaderLen = binary.BigEndian.Int16(buf[_headerOffset:_verOffset])\n\tp.Ver = int32(binary.BigEndian.Int16(buf[_verOffset:_opOffset]))\n\tp.Op = binary.BigEndian.Int32(buf[_opOffset:_seqOffset])\n\tp.Seq = binary.BigEndian.Int32(buf[_seqOffset:])\n\tif packLen < 0 || packLen > _maxPackSize {\n\t\treturn ErrProtoPackLen\n\t}\n\tif headerLen != _rawHeaderSize {\n\t\treturn ErrProtoHeaderLen\n\t}\n\tif bodyLen = int(packLen - int32(headerLen)); bodyLen > 0 {\n\t\tp.Body = buf[headerLen:packLen]\n\t} else {\n\t\tp.Body = nil\n\t}\n\treturn\n}\n\n// WriteWebsocket write a proto to websocket connection.\nfunc (p *Proto) WriteWebsocket(ws *websocket.Conn) (err error) {\n\tvar (\n\t\tbuf     []byte\n\t\tpackLen int\n\t)\n\tpackLen = _rawHeaderSize + len(p.Body)\n\tif err = ws.WriteHeader(websocket.BinaryMessage, packLen); err != nil {\n\t\treturn\n\t}\n\tif buf, err = ws.Peek(_rawHeaderSize); err != nil {\n\t\treturn\n\t}\n\tbinary.BigEndian.PutInt32(buf[_packOffset:], int32(packLen))\n\tbinary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))\n\tbinary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))\n\tbinary.BigEndian.PutInt32(buf[_opOffset:], p.Op)\n\tbinary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)\n\tif p.Body != nil {\n\t\terr = ws.WriteBody(p.Body)\n\t}\n\treturn\n}\n\n// WriteWebsocketHeart write websocket heartbeat with room online.\nfunc (p *Proto) WriteWebsocketHeart(wr *websocket.Conn, online int32) (err error) {\n\tvar (\n\t\tbuf     []byte\n\t\tpackLen int\n\t)\n\tpackLen = _rawHeaderSize + _heartSize\n\t// websocket header\n\tif err = wr.WriteHeader(websocket.BinaryMessage, packLen); err != nil {\n\t\treturn\n\t}\n\tif buf, err = wr.Peek(packLen); err != nil {\n\t\treturn\n\t}\n\t// proto header\n\tbinary.BigEndian.PutInt32(buf[_packOffset:], int32(packLen))\n\tbinary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))\n\tbinary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))\n\tbinary.BigEndian.PutInt32(buf[_opOffset:], p.Op)\n\tbinary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)\n\t// proto body\n\tbinary.BigEndian.PutInt32(buf[_heartOffset:], online)\n\treturn\n}\n"
  },
  {
    "path": "api/protocol/protocol.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// source: protocol/protocol.proto\n\npackage protocol\n\nimport (\n\tfmt \"fmt\"\n\tproto \"github.com/golang/protobuf/proto\"\n\tmath \"math\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package\n\n//\n// v1.0.0\n// protocol\ntype Proto struct {\n\tVer                  int32    `protobuf:\"varint,1,opt,name=ver,proto3\" json:\"ver,omitempty\"`\n\tOp                   int32    `protobuf:\"varint,2,opt,name=op,proto3\" json:\"op,omitempty\"`\n\tSeq                  int32    `protobuf:\"varint,3,opt,name=seq,proto3\" json:\"seq,omitempty\"`\n\tBody                 []byte   `protobuf:\"bytes,4,opt,name=body,proto3\" json:\"body,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Proto) Reset()         { *m = Proto{} }\nfunc (m *Proto) String() string { return proto.CompactTextString(m) }\nfunc (*Proto) ProtoMessage()    {}\nfunc (*Proto) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_87968d26f3046c60, []int{0}\n}\n\nfunc (m *Proto) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_Proto.Unmarshal(m, b)\n}\nfunc (m *Proto) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_Proto.Marshal(b, m, deterministic)\n}\nfunc (m *Proto) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Proto.Merge(m, src)\n}\nfunc (m *Proto) XXX_Size() int {\n\treturn xxx_messageInfo_Proto.Size(m)\n}\nfunc (m *Proto) XXX_DiscardUnknown() {\n\txxx_messageInfo_Proto.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Proto proto.InternalMessageInfo\n\nfunc (m *Proto) GetVer() int32 {\n\tif m != nil {\n\t\treturn m.Ver\n\t}\n\treturn 0\n}\n\nfunc (m *Proto) GetOp() int32 {\n\tif m != nil {\n\t\treturn m.Op\n\t}\n\treturn 0\n}\n\nfunc (m *Proto) GetSeq() int32 {\n\tif m != nil {\n\t\treturn m.Seq\n\t}\n\treturn 0\n}\n\nfunc (m *Proto) GetBody() []byte {\n\tif m != nil {\n\t\treturn m.Body\n\t}\n\treturn nil\n}\n\nfunc init() {\n\tproto.RegisterType((*Proto)(nil), \"goim.protocol.Proto\")\n}\n\nfunc init() { proto.RegisterFile(\"protocol/protocol.proto\", fileDescriptor_87968d26f3046c60) }\n\nvar fileDescriptor_87968d26f3046c60 = []byte{\n\t// 153 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2f, 0x28, 0xca, 0x2f,\n\t0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0x87, 0x31, 0xf4, 0xc0, 0x0c, 0x21, 0xde, 0xf4, 0xfc, 0xcc, 0x5c,\n\t0x3d, 0x98, 0xa0, 0x92, 0x3f, 0x17, 0x6b, 0x00, 0x58, 0x5c, 0x80, 0x8b, 0xb9, 0x2c, 0xb5, 0x48,\n\t0x82, 0x51, 0x81, 0x51, 0x83, 0x35, 0x08, 0xc4, 0x14, 0xe2, 0xe3, 0x62, 0xca, 0x2f, 0x90, 0x60,\n\t0x02, 0x0b, 0x30, 0xe5, 0x17, 0x80, 0x54, 0x14, 0xa7, 0x16, 0x4a, 0x30, 0x43, 0x54, 0x14, 0xa7,\n\t0x16, 0x0a, 0x09, 0x71, 0xb1, 0x24, 0xe5, 0xa7, 0x54, 0x4a, 0xb0, 0x28, 0x30, 0x6a, 0xf0, 0x04,\n\t0x81, 0xd9, 0x4e, 0x86, 0x51, 0xfa, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9,\n\t0xfa, 0x21, 0xa9, 0x45, 0x45, 0x95, 0xba, 0xbe, 0x89, 0xf9, 0xfa, 0x20, 0x6b, 0xf5, 0x13, 0x0b,\n\t0x32, 0xe1, 0xee, 0xb1, 0x86, 0x31, 0x92, 0xd8, 0xc0, 0x2c, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff,\n\t0xff, 0x8d, 0xc7, 0xbf, 0x64, 0xb4, 0x00, 0x00, 0x00,\n}\n"
  },
  {
    "path": "api/protocol/protocol.proto",
    "content": "syntax = \"proto3\";\n\npackage goim.protocol;\n\noption go_package = \"github.com/Terry-Mao/goim/api/protocol;protocol\";\n\n/*\n * v1.0.0\n * protocol\n */\nmessage Proto {\n    int32 ver = 1;\n    int32 op = 2;\n    int32 seq = 3;\n    bytes body = 4;\n}\n"
  },
  {
    "path": "benchmarks/client/main.go",
    "content": "package main\n\n// Start Commond eg: ./client 1 1000 localhost:3101\n// first parameter：beginning userId\n// second parameter: amount of clients\n// third parameter: comet server ip\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\tlog \"github.com/golang/glog\"\n)\n\nconst (\n\topHeartbeat      = int32(2)\n\topHeartbeatReply = int32(3)\n\topAuth           = int32(7)\n\topAuthReply      = int32(8)\n)\n\nconst (\n\trawHeaderLen = uint16(16)\n\theart        = 240 * time.Second\n)\n\n// Proto proto.\ntype Proto struct {\n\tPackLen   int32  // package length\n\tHeaderLen int16  // header length\n\tVer       int16  // protocol version\n\tOperation int32  // operation for request\n\tSeq       int32  // sequence number chosen by client\n\tBody      []byte // body\n}\n\n// AuthToken auth token.\ntype AuthToken struct {\n\tMid      int64   `json:\"mid\"`\n\tKey      string  `json:\"key\"`\n\tRoomID   string  `json:\"room_id\"`\n\tPlatform string  `json:\"platform\"`\n\tAccepts  []int32 `json:\"accepts\"`\n}\n\nvar (\n\tcountDown  int64\n\taliveCount int64\n)\n\nfunc main() {\n\truntime.GOMAXPROCS(runtime.NumCPU())\n\tflag.Parse()\n\tbegin, err := strconv.Atoi(os.Args[1])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tnum, err := strconv.Atoi(os.Args[2])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tgo result()\n\tfor i := begin; i < begin+num; i++ {\n\t\tgo client(int64(i))\n\t}\n\t// signal\n\tvar exit chan bool\n\t<-exit\n}\n\nfunc result() {\n\tvar (\n\t\tlastTimes int64\n\t\tinterval  = int64(5)\n\t)\n\tfor {\n\t\tnowCount := atomic.LoadInt64(&countDown)\n\t\tnowAlive := atomic.LoadInt64(&aliveCount)\n\t\tdiff := nowCount - lastTimes\n\t\tlastTimes = nowCount\n\t\tfmt.Println(fmt.Sprintf(\"%s alive:%d down:%d down/s:%d\", time.Now().Format(\"2006-01-02 15:04:05\"), nowAlive, nowCount, diff/interval))\n\t\ttime.Sleep(time.Second * time.Duration(interval))\n\t}\n}\n\nfunc client(mid int64) {\n\tfor {\n\t\tstartClient(mid)\n\t\ttime.Sleep(time.Duration(rand.Intn(10)) * time.Second)\n\t}\n}\n\nfunc startClient(key int64) {\n\ttime.Sleep(time.Duration(rand.Intn(120)) * time.Second)\n\tatomic.AddInt64(&aliveCount, 1)\n\tquit := make(chan bool, 1)\n\tdefer func() {\n\t\tclose(quit)\n\t\tatomic.AddInt64(&aliveCount, -1)\n\t}()\n\t// connnect to server\n\tconn, err := net.Dial(\"tcp\", os.Args[3])\n\tif err != nil {\n\t\tlog.Errorf(\"net.Dial(%s) error(%v)\", os.Args[3], err)\n\t\treturn\n\t}\n\tseq := int32(0)\n\twr := bufio.NewWriter(conn)\n\trd := bufio.NewReader(conn)\n\tauthToken := &AuthToken{\n\t\tkey,\n\t\t\"\",\n\t\t\"test://1\",\n\t\t\"ios\",\n\t\t[]int32{1000, 1001, 1002},\n\t}\n\tproto := new(Proto)\n\tproto.Ver = 1\n\tproto.Operation = opAuth\n\tproto.Seq = seq\n\tproto.Body, _ = json.Marshal(authToken)\n\tif err = tcpWriteProto(wr, proto); err != nil {\n\t\tlog.Errorf(\"tcpWriteProto() error(%v)\", err)\n\t\treturn\n\t}\n\tif err = tcpReadProto(rd, proto); err != nil {\n\t\tlog.Errorf(\"tcpReadProto() error(%v)\", err)\n\t\treturn\n\t}\n\tlog.Infof(\"key:%d auth ok, proto: %v\", key, proto)\n\tseq++\n\t// writer\n\tgo func() {\n\t\thbProto := new(Proto)\n\t\tfor {\n\t\t\t// heartbeat\n\t\t\thbProto.Operation = opHeartbeat\n\t\t\thbProto.Seq = seq\n\t\t\thbProto.Body = nil\n\t\t\tif err = tcpWriteProto(wr, hbProto); err != nil {\n\t\t\t\tlog.Errorf(\"key:%d tcpWriteProto() error(%v)\", key, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlog.Infof(\"key:%d Write heartbeat\", key)\n\t\t\ttime.Sleep(heart)\n\t\t\tseq++\n\t\t\tselect {\n\t\t\tcase <-quit:\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t}()\n\t// reader\n\tfor {\n\t\tif err = tcpReadProto(rd, proto); err != nil {\n\t\t\tlog.Errorf(\"key:%d tcpReadProto() error(%v)\", key, err)\n\t\t\tquit <- true\n\t\t\treturn\n\t\t}\n\t\tif proto.Operation == opAuthReply {\n\t\t\tlog.Infof(\"key:%d auth success\", key)\n\t\t} else if proto.Operation == opHeartbeatReply {\n\t\t\tlog.Infof(\"key:%d receive heartbeat\", key)\n\t\t\tif err = conn.SetReadDeadline(time.Now().Add(heart + 60*time.Second)); err != nil {\n\t\t\t\tlog.Errorf(\"conn.SetReadDeadline() error(%v)\", err)\n\t\t\t\tquit <- true\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Infof(\"key:%d op:%d msg: %s\", key, proto.Operation, string(proto.Body))\n\t\t\tatomic.AddInt64(&countDown, 1)\n\t\t}\n\t}\n}\n\nfunc tcpWriteProto(wr *bufio.Writer, proto *Proto) (err error) {\n\t// write\n\tif err = binary.Write(wr, binary.BigEndian, uint32(rawHeaderLen)+uint32(len(proto.Body))); err != nil {\n\t\treturn\n\t}\n\tif err = binary.Write(wr, binary.BigEndian, rawHeaderLen); err != nil {\n\t\treturn\n\t}\n\tif err = binary.Write(wr, binary.BigEndian, proto.Ver); err != nil {\n\t\treturn\n\t}\n\tif err = binary.Write(wr, binary.BigEndian, proto.Operation); err != nil {\n\t\treturn\n\t}\n\tif err = binary.Write(wr, binary.BigEndian, proto.Seq); err != nil {\n\t\treturn\n\t}\n\tif proto.Body != nil {\n\t\tif err = binary.Write(wr, binary.BigEndian, proto.Body); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\terr = wr.Flush()\n\treturn\n}\n\nfunc tcpReadProto(rd *bufio.Reader, proto *Proto) (err error) {\n\tvar (\n\t\tpackLen   int32\n\t\theaderLen int16\n\t)\n\t// read\n\tif err = binary.Read(rd, binary.BigEndian, &packLen); err != nil {\n\t\treturn\n\t}\n\tif err = binary.Read(rd, binary.BigEndian, &headerLen); err != nil {\n\t\treturn\n\t}\n\tif err = binary.Read(rd, binary.BigEndian, &proto.Ver); err != nil {\n\t\treturn\n\t}\n\tif err = binary.Read(rd, binary.BigEndian, &proto.Operation); err != nil {\n\t\treturn\n\t}\n\tif err = binary.Read(rd, binary.BigEndian, &proto.Seq); err != nil {\n\t\treturn\n\t}\n\tvar (\n\t\tn, t    int\n\t\tbodyLen = int(packLen - int32(headerLen))\n\t)\n\tif bodyLen > 0 {\n\t\tproto.Body = make([]byte, bodyLen)\n\t\tfor {\n\t\t\tif t, err = rd.Read(proto.Body[n:]); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif n += t; n == bodyLen {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\tproto.Body = nil\n\t}\n\treturn\n}\n"
  },
  {
    "path": "benchmarks/multi_push/main.go",
    "content": "package main\n\n// Start Command eg : ./multi_push 0 20000 localhost:7172 60\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"time\"\n)\n\nvar (\n\tlg         *log.Logger\n\thttpClient *http.Client\n\tt          int\n)\n\nconst testContent = \"{\\\"test\\\":1}\"\n\ntype pushsBodyMsg struct {\n\tMsg     json.RawMessage `json:\"m\"`\n\tUserIds []int64         `json:\"u\"`\n}\n\nfunc init() {\n\thttpTransport := &http.Transport{\n\t\tDial: func(netw, addr string) (net.Conn, error) {\n\t\t\tdeadline := time.Now().Add(30 * time.Second)\n\t\t\tc, err := net.DialTimeout(netw, addr, 20*time.Second)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t_ = c.SetDeadline(deadline)\n\t\t\treturn c, nil\n\t\t},\n\t\tDisableKeepAlives: false,\n\t}\n\thttpClient = &http.Client{\n\t\tTransport: httpTransport,\n\t}\n}\n\nfunc main() {\n\truntime.GOMAXPROCS(runtime.NumCPU())\n\tinfoLogfi, err := os.OpenFile(\"./multi_push.log\", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlg = log.New(infoLogfi, \"\", log.LstdFlags|log.Lshortfile)\n\n\tbegin, err := strconv.Atoi(os.Args[1])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlength, err := strconv.Atoi(os.Args[2])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tt, err = strconv.Atoi(os.Args[4])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tnum := runtime.NumCPU() * 8\n\n\tl := length / num\n\tb, e := begin, begin+l\n\ttime.AfterFunc(time.Duration(t)*time.Second, stop)\n\tfor i := 0; i < num; i++ {\n\t\tgo startPush(b, e)\n\t\tb += l\n\t\te += l\n\t}\n\tif b < begin+length {\n\t\tgo startPush(b, begin+length)\n\t}\n\n\ttime.Sleep(9999 * time.Hour)\n}\n\nfunc stop() {\n\tos.Exit(-1)\n}\n\nfunc startPush(b, e int) {\n\tl := make([]int64, 0, e-b)\n\tfor i := b; i < e; i++ {\n\t\tl = append(l, int64(i))\n\t}\n\tmsg := &pushsBodyMsg{Msg: json.RawMessage(testContent), UserIds: l}\n\tbody, err := json.Marshal(msg)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfor {\n\t\tresp, err := httpPost(fmt.Sprintf(\"http://%s/goim/push/mids=%d\", os.Args[3], b), \"application/x-www-form-urlencoded\", bytes.NewBuffer(body))\n\t\tif err != nil {\n\t\t\tlg.Printf(\"post error (%v)\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tbody, err := ioutil.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tlg.Printf(\"post error (%v)\", err)\n\t\t\treturn\n\t\t}\n\t\tresp.Body.Close()\n\n\t\tlg.Printf(\"response %s\", string(body))\n\t}\n}\n\nfunc httpPost(url string, contentType string, body io.Reader) (*http.Response, error) {\n\treq, err := http.NewRequest(\"POST\", url, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"Content-Type\", contentType)\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn resp, nil\n}\n"
  },
  {
    "path": "benchmarks/push/main.go",
    "content": "package main\n\n// Start Command eg : ./push 0 20000 localhost:7172 60\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"time\"\n)\n\nvar (\n\thttpClient *http.Client\n\tt          int\n)\n\nconst testContent = \"{\\\"test\\\":1}\"\n\ntype pushBodyMsg struct {\n\tMsg    json.RawMessage `json:\"m\"`\n\tUserID int64           `json:\"u\"`\n}\n\nfunc init() {\n\thttpTransport := &http.Transport{\n\t\tDial: func(netw, addr string) (net.Conn, error) {\n\t\t\tdeadline := time.Now().Add(30 * time.Second)\n\t\t\tc, err := net.DialTimeout(netw, addr, 20*time.Second)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t_ = c.SetDeadline(deadline)\n\t\t\treturn c, nil\n\t\t},\n\t\tDisableKeepAlives: false,\n\t}\n\thttpClient = &http.Client{\n\t\tTransport: httpTransport,\n\t}\n}\n\nfunc main() {\n\truntime.GOMAXPROCS(runtime.NumCPU())\n\tbegin, err := strconv.Atoi(os.Args[1])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlength, err := strconv.Atoi(os.Args[2])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tt, err = strconv.Atoi(os.Args[4])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tnum := runtime.NumCPU() * 2\n\tlog.Printf(\"start routine num:%d\", num)\n\n\tl := length / num\n\tb, e := begin, begin+l\n\ttime.AfterFunc(time.Duration(t)*time.Second, stop)\n\tfor i := 0; i < num; i++ {\n\t\tgo startPush(b, e)\n\t\tb += l\n\t\te += l\n\t}\n\tif b < begin+length {\n\t\tgo startPush(b, begin+length)\n\t}\n\n\ttime.Sleep(9999 * time.Hour)\n}\n\nfunc stop() {\n\tos.Exit(-1)\n}\n\nfunc startPush(b, e int) {\n\tlog.Printf(\"start Push from %d to %d\", b, e)\n\tbodys := make([][]byte, e-b)\n\tfor i := 0; i < e-b; i++ {\n\t\tmsg := &pushBodyMsg{Msg: json.RawMessage(testContent), UserID: int64(b)}\n\t\tbody, err := json.Marshal(msg)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tbodys[i] = body\n\t}\n\n\tfor {\n\t\tfor i := 0; i < len(bodys); i++ {\n\t\t\tresp, err := httpPost(fmt.Sprintf(\"http://%s/goim/push/mids?operation=1000&mids=%d\", os.Args[3], b), \"application/x-www-form-urlencoded\", bytes.NewBuffer(bodys[i]))\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"post error (%v)\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbody, err := ioutil.ReadAll(resp.Body)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"post error (%v)\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresp.Body.Close()\n\n\t\t\tlog.Printf(\"response %s\", string(body))\n\t\t\t//time.Sleep(50 * time.Millisecond)\n\t\t}\n\t}\n}\n\nfunc httpPost(url string, contentType string, body io.Reader) (*http.Response, error) {\n\treq, err := http.NewRequest(\"POST\", url, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"Content-Type\", contentType)\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn resp, nil\n}\n"
  },
  {
    "path": "benchmarks/push_room/main.go",
    "content": "package main\n\n// Start Commond eg: ./push_room 1 20 localhost:3111\n// first parameter: room id\n// second parameter: num per seconds\n// third parameter: logic server ip\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n)\n\nfunc main() {\n\trountineNum, err := strconv.Atoi(os.Args[2])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\taddr := os.Args[3]\n\n\tgap := time.Second / time.Duration(rountineNum)\n\tdelay := time.Duration(0)\n\n\tgo run(addr, time.Duration(0)*time.Second)\n\tfor i := 0; i < rountineNum-1; i++ {\n\t\tgo run(addr, delay)\n\t\tdelay += gap\n\t\tfmt.Println(\"delay:\", delay)\n\t}\n\ttime.Sleep(9999 * time.Hour)\n}\n\nfunc run(addr string, delay time.Duration) {\n\ttime.Sleep(delay)\n\ti := int64(0)\n\tfor {\n\t\tgo post(addr, i)\n\t\ttime.Sleep(time.Second)\n\t\ti++\n\t}\n}\n\nfunc post(addr string, i int64) {\n\tresp, err := http.Post(\"http://\"+addr+\"/goim/push/room?operation=1000&type=test&room=\"+os.Args[1], \"application/json\", bytes.NewBufferString(fmt.Sprintf(\"{\\\"test\\\":%d}\", i)))\n\tif err != nil {\n\t\tfmt.Printf(\"Error: http.post() error(%v)\\n\", err)\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\tbody, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\tfmt.Printf(\"Error: http.post() error(%v)\\n\", err)\n\t\treturn\n\t}\n\n\tfmt.Printf(\"%s postId:%d, response:%s\\n\", time.Now().Format(\"2006-01-02 15:04:05\"), i, string(body))\n}\n"
  },
  {
    "path": "benchmarks/push_rooms/main.go",
    "content": "package main\n\n// Start Command eg : ./push_rooms 0 20000 localhost:7172 40\n// param 1 : the start of room number\n// param 2 : the end of room number\n// param 3 : comet server tcp address\n// param 4 : push amount each goroutines per second\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"time\"\n)\n\nvar (\n\thttpClient *http.Client\n)\n\nconst testContent = \"{\\\"test\\\":1}\"\n\nfunc init() {\n\thttpTransport := &http.Transport{\n\t\tDial: func(netw, addr string) (net.Conn, error) {\n\t\t\tdeadline := time.Now().Add(30 * time.Second)\n\t\t\tc, err := net.DialTimeout(netw, addr, 20*time.Second)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t_ = c.SetDeadline(deadline)\n\t\t\treturn c, nil\n\t\t},\n\t\tDisableKeepAlives: false,\n\t}\n\thttpClient = &http.Client{\n\t\tTransport: httpTransport,\n\t}\n}\n\nfunc main() {\n\truntime.GOMAXPROCS(runtime.NumCPU())\n\tbegin, err := strconv.Atoi(os.Args[1])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlength, err := strconv.Atoi(os.Args[2])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tnum, err := strconv.Atoi(os.Args[4])\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdelay := (1000 * time.Millisecond) / time.Duration(num)\n\n\troutines := runtime.NumCPU() * 2\n\tlog.Printf(\"start routine num:%d\", routines)\n\n\tl := length / routines\n\tb, e := begin, begin+l\n\tfor i := 0; i < routines; i++ {\n\t\tgo startPush(b, e, delay)\n\t\tb += l\n\t\te += l\n\t}\n\tif b < begin+length {\n\t\tgo startPush(b, begin+length, delay)\n\t}\n\n\ttime.Sleep(9999 * time.Hour)\n}\n\nfunc startPush(b, e int, delay time.Duration) {\n\tlog.Printf(\"start Push from %d to %d\", b, e)\n\n\tfor {\n\t\tfor i := b; i < e; i++ {\n\t\t\tresp, err := http.Post(fmt.Sprintf(\"http://%s/goim/push/room?operation=1000&type=test&room=%d\", os.Args[3], i), \"application/json\", bytes.NewBufferString(testContent))\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"post error (%v)\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbody, err := ioutil.ReadAll(resp.Body)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"post error (%v)\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresp.Body.Close()\n\n\t\t\tlog.Printf(\"push room:%d response %s\", i, string(body))\n\t\t\ttime.Sleep(delay)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/comet/comet-example.toml",
    "content": "# This is a TOML document. Boom\n[discovery]\n    nodes = [\"127.0.0.1:7171\"]\n\n[rpcServer]\n    addr = \":3109\"\n    timeout = \"1s\"\n\n[rpcClient]\n    dial = \"1s\"\n    timeout = \"1s\"\n\n[tcp]\n    bind = [\":3101\"]\n    sndbuf = 4096\n    rcvbuf = 4096\n    keepalive = false\n    reader = 32\n    readBuf = 1024\n    readBufSize = 8192\n    writer = 32\n    writeBuf = 1024\n    writeBufSize = 8192\n\n[websocket]\n    bind = [\":3102\"]\n    tlsOpen = false\n    tlsBind = [\":3103\"]\n    certFile = \"../../cert.pem\"\n    privateFile = \"../../private.pem\"\n\n[protocol]\n    timer = 32\n    timerSize = 2048\n    svrProto = 10\n    cliProto = 5\n    handshakeTimeout = \"8s\"\n\n[whitelist]\n    Whitelist = [123]\n    WhiteLog  = \"/tmp/white_list.log\"\n\n[bucket]\n    size = 32\n    channel = 1024\n    room = 1024\n    routineAmount = 32\n    routineSize = 1024\n"
  },
  {
    "path": "cmd/comet/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/bilibili/discovery/naming\"\n\tresolver \"github.com/bilibili/discovery/naming/grpc\"\n\t\"github.com/Terry-Mao/goim/internal/comet\"\n\t\"github.com/Terry-Mao/goim/internal/comet/conf\"\n\t\"github.com/Terry-Mao/goim/internal/comet/grpc\"\n\tmd \"github.com/Terry-Mao/goim/internal/logic/model\"\n\t\"github.com/Terry-Mao/goim/pkg/ip\"\n\tlog \"github.com/golang/glog\"\n)\n\nconst (\n\tver   = \"2.0.0\"\n\tappid = \"goim.comet\"\n)\n\nfunc main() {\n\tflag.Parse()\n\tif err := conf.Init(); err != nil {\n\t\tpanic(err)\n\t}\n\trand.Seed(time.Now().UTC().UnixNano())\n\truntime.GOMAXPROCS(runtime.NumCPU())\n\tprintln(conf.Conf.Debug)\n\tlog.Infof(\"goim-comet [version: %s env: %+v] start\", ver, conf.Conf.Env)\n\t// register discovery\n\tdis := naming.New(conf.Conf.Discovery)\n\tresolver.Register(dis)\n\t// new comet server\n\tsrv := comet.NewServer(conf.Conf)\n\tif err := comet.InitWhitelist(conf.Conf.Whitelist); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := comet.InitTCP(srv, conf.Conf.TCP.Bind, runtime.NumCPU()); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := comet.InitWebsocket(srv, conf.Conf.Websocket.Bind, runtime.NumCPU()); err != nil {\n\t\tpanic(err)\n\t}\n\tif conf.Conf.Websocket.TLSOpen {\n\t\tif err := comet.InitWebsocketWithTLS(srv, conf.Conf.Websocket.TLSBind, conf.Conf.Websocket.CertFile, conf.Conf.Websocket.PrivateFile, runtime.NumCPU()); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\t// new grpc server\n\trpcSrv := grpc.New(conf.Conf.RPCServer, srv)\n\tcancel := register(dis, srv)\n\t// signal\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)\n\tfor {\n\t\ts := <-c\n\t\tlog.Infof(\"goim-comet get a signal %s\", s.String())\n\t\tswitch s {\n\t\tcase syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:\n\t\t\tif cancel != nil {\n\t\t\t\tcancel()\n\t\t\t}\n\t\t\trpcSrv.GracefulStop()\n\t\t\tsrv.Close()\n\t\t\tlog.Infof(\"goim-comet [version: %s] exit\", ver)\n\t\t\tlog.Flush()\n\t\t\treturn\n\t\tcase syscall.SIGHUP:\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc register(dis *naming.Discovery, srv *comet.Server) context.CancelFunc {\n\tenv := conf.Conf.Env\n\taddr := ip.InternalIP()\n\t_, port, _ := net.SplitHostPort(conf.Conf.RPCServer.Addr)\n\tins := &naming.Instance{\n\t\tRegion:   env.Region,\n\t\tZone:     env.Zone,\n\t\tEnv:      env.DeployEnv,\n\t\tHostname: env.Host,\n\t\tAppID:    appid,\n\t\tAddrs: []string{\n\t\t\t\"grpc://\" + addr + \":\" + port,\n\t\t},\n\t\tMetadata: map[string]string{\n\t\t\tmd.MetaWeight:  strconv.FormatInt(env.Weight, 10),\n\t\t\tmd.MetaOffline: strconv.FormatBool(env.Offline),\n\t\t\tmd.MetaAddrs:   strings.Join(env.Addrs, \",\"),\n\t\t},\n\t}\n\tcancel, err := dis.Register(ins)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// renew discovery metadata\n\tgo func() {\n\t\tfor {\n\t\t\tvar (\n\t\t\t\terr   error\n\t\t\t\tconns int\n\t\t\t\tips   = make(map[string]struct{})\n\t\t\t)\n\t\t\tfor _, bucket := range srv.Buckets() {\n\t\t\t\tfor ip := range bucket.IPCount() {\n\t\t\t\t\tips[ip] = struct{}{}\n\t\t\t\t}\n\t\t\t\tconns += bucket.ChannelCount()\n\t\t\t}\n\t\t\tins.Metadata[md.MetaConnCount] = fmt.Sprint(conns)\n\t\t\tins.Metadata[md.MetaIPCount] = fmt.Sprint(len(ips))\n\t\t\tif err = dis.Set(ins); err != nil {\n\t\t\t\tlog.Errorf(\"dis.Set(%+v) error(%v)\", ins, err)\n\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttime.Sleep(time.Second * 10)\n\t\t}\n\t}()\n\treturn cancel\n}\n"
  },
  {
    "path": "cmd/job/job-example.toml",
    "content": "# This is a TOML document. Boom\n[discovery]\n    nodes = [\"127.0.0.1:7171\"]\n\n[kafka]\n    topic = \"goim-push-topic\"\n    group = \"goim-push-group-job\"\n    brokers = [\"127.0.0.1:9092\"]\n"
  },
  {
    "path": "cmd/job/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/bilibili/discovery/naming\"\n\t\"github.com/Terry-Mao/goim/internal/job\"\n\t\"github.com/Terry-Mao/goim/internal/job/conf\"\n\n\tresolver \"github.com/bilibili/discovery/naming/grpc\"\n\tlog \"github.com/golang/glog\"\n)\n\nvar (\n\tver = \"2.0.0\"\n)\n\nfunc main() {\n\tflag.Parse()\n\tif err := conf.Init(); err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Infof(\"goim-job [version: %s env: %+v] start\", ver, conf.Conf.Env)\n\t// grpc register naming\n\tdis := naming.New(conf.Conf.Discovery)\n\tresolver.Register(dis)\n\t// job\n\tj := job.New(conf.Conf)\n\tgo j.Consume()\n\t// signal\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)\n\tfor {\n\t\ts := <-c\n\t\tlog.Infof(\"goim-job get a signal %s\", s.String())\n\t\tswitch s {\n\t\tcase syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:\n\t\t\tj.Close()\n\t\t\tlog.Infof(\"goim-job [version: %s] exit\", ver)\n\t\t\tlog.Flush()\n\t\t\treturn\n\t\tcase syscall.SIGHUP:\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/logic/logic-example.toml",
    "content": "# This is a TOML document. Boom\n[discovery]\n    nodes = [\"127.0.0.1:7171\"]\n\n[regions]\n    \"bj\" = [\"北京\",\"天津\",\"河北\",\"山东\",\"山西\",\"内蒙古\",\"辽宁\",\"吉林\",\"黑龙江\",\"甘肃\",\"宁夏\",\"新疆\"]\n    \"sh\" = [\"上海\",\"江苏\",\"浙江\",\"安徽\",\"江西\",\"湖北\",\"重庆\",\"陕西\",\"青海\",\"河南\",\"台湾\"]\n    \"gz\" = [\"广东\",\"福建\",\"广西\",\"海南\",\"湖南\",\"四川\",\"贵州\",\"云南\",\"西藏\",\"香港\",\"澳门\"]\n    \n[node]\n    defaultDomain = \"conn.goim.io\"\n    hostDomain = \".goim.io\"\n    heartbeat = \"4m\"\n    heartbeatMax = 2\n    tcpPort = 3101\n    wsPort = 3102\n    wssPort = 3103\n    regionWeight = 1.6\n\n[backoff]\n    maxDelay = 300\n    baseDelay = 3\n    factor = 1.8\n    jitter = 0.3\n\n[rpcServer]\n    network = \"tcp\"\n    addr = \":3119\"\n    timeout = \"1s\"\n\n[rpcClient]\n    dial = \"1s\"\n    timeout = \"1s\"\n\n[httpServer]\n    network = \"tcp\"\n    addr = \":3111\"\n\treadTimeout = \"1s\"\n\twriteTimeout = \"1s\"\n\n[kafka]\n    topic = \"goim-push-topic\"\n    brokers = [\"127.0.0.1:9092\"]\n\n[redis]\n    network = \"tcp\"\n    addr = \"127.0.0.1:6379\"\n    active = 60000\n    idle = 1024\n    dialTimeout = \"200ms\"\n    readTimeout = \"500ms\"\n    writeTimeout = \"500ms\"\n    idleTimeout = \"120s\"\n    expire = \"30m\"\n"
  },
  {
    "path": "cmd/logic/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"syscall\"\n\n\t\"github.com/bilibili/discovery/naming\"\n\tresolver \"github.com/bilibili/discovery/naming/grpc\"\n\t\"github.com/Terry-Mao/goim/internal/logic\"\n\t\"github.com/Terry-Mao/goim/internal/logic/conf\"\n\t\"github.com/Terry-Mao/goim/internal/logic/grpc\"\n\t\"github.com/Terry-Mao/goim/internal/logic/http\"\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n\t\"github.com/Terry-Mao/goim/pkg/ip\"\n\tlog \"github.com/golang/glog\"\n)\n\nconst (\n\tver   = \"2.0.0\"\n\tappid = \"goim.logic\"\n)\n\nfunc main() {\n\tflag.Parse()\n\tif err := conf.Init(); err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Infof(\"goim-logic [version: %s env: %+v] start\", ver, conf.Conf.Env)\n\t// grpc register naming\n\tdis := naming.New(conf.Conf.Discovery)\n\tresolver.Register(dis)\n\t// logic\n\tsrv := logic.New(conf.Conf)\n\thttpSrv := http.New(conf.Conf.HTTPServer, srv)\n\trpcSrv := grpc.New(conf.Conf.RPCServer, srv)\n\tcancel := register(dis, srv)\n\t// signal\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)\n\tfor {\n\t\ts := <-c\n\t\tlog.Infof(\"goim-logic get a signal %s\", s.String())\n\t\tswitch s {\n\t\tcase syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:\n\t\t\tif cancel != nil {\n\t\t\t\tcancel()\n\t\t\t}\n\t\t\tsrv.Close()\n\t\t\thttpSrv.Close()\n\t\t\trpcSrv.GracefulStop()\n\t\t\tlog.Infof(\"goim-logic [version: %s] exit\", ver)\n\t\t\tlog.Flush()\n\t\t\treturn\n\t\tcase syscall.SIGHUP:\n\t\tdefault:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc register(dis *naming.Discovery, srv *logic.Logic) context.CancelFunc {\n\tenv := conf.Conf.Env\n\taddr := ip.InternalIP()\n\t_, port, _ := net.SplitHostPort(conf.Conf.RPCServer.Addr)\n\tins := &naming.Instance{\n\t\tRegion:   env.Region,\n\t\tZone:     env.Zone,\n\t\tEnv:      env.DeployEnv,\n\t\tHostname: env.Host,\n\t\tAppID:    appid,\n\t\tAddrs: []string{\n\t\t\t\"grpc://\" + addr + \":\" + port,\n\t\t},\n\t\tMetadata: map[string]string{\n\t\t\tmodel.MetaWeight: strconv.FormatInt(env.Weight, 10),\n\t\t},\n\t}\n\tcancel, err := dis.Register(ins)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn cancel\n}\n"
  },
  {
    "path": "codecov.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\necho \"\" > coverage.txt\n\nfor d in $(go list ./... | grep -v cmd | grep -v docs | grep -v srcipts | grep -v benchmarks | grep -v examples); do\n    echo \"testing for $d ...\"\n    go test -coverprofile=profile.out -covermode=atomic $d\n    if [ -f profile.out ]; then\n        cat profile.out >> coverage.txt\n        rm profile.out\n    fi\ndone\n"
  },
  {
    "path": "docs/benchmark_cn.md",
    "content": "## 压测图表\n![benchmark](benchmark.jpg)\n\n### 服务端配置\n| CPU | 内存 | 操作系统 | 数量 |\n| :---- | :---- | :---- | :---- |\n| Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz  | DDR3 32GB | Debian GNU/Linux 8 | 1 |\n\n### 压测参数\n* 不同UID同房间在线人数: 1,000,000\n* 持续推送时长: 15分钟\n* 持续推送数量: 40条/秒\n* 推送内容: {\"test\":1}\n* 推送类型: 单房间推送\n* 到达计算方式: 1秒统计一次,共30次\n\n### 资源使用\n* 每台服务端CPU使用: 2000%~2300%(刚好满负载)\n* 每台服务端内存使用: 14GB左右\n* GC耗时: 504毫秒左右\n* 流量使用: Incoming(450MBit/s), Outgoing(4.39GBit/s)\n\n### 压测结果\n* 推送到达: 3590万/秒左右;\n\n## comet模块\n![benchmark-comet](benchmark-comet.jpg)\n\n## 流量\n![benchmark-flow](benchmark-flow.jpg)\n\n## heap信息(包含GC)\n![benchmark-flow](benchmark-heap.jpg)\n"
  },
  {
    "path": "docs/benchmark_en.md",
    "content": "## Benchmark Chart\n![benchmark](benchmark.jpg)\n\n### Benchmark Server\n| CPU | Memory | OS | Instance |\n| :---- | :---- | :---- | :---- |\n| Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz  | DDR3 32GB | Debian GNU/Linux 8 | 1 |\n\n### Benchmark Case\n* Online: 1,000,000\n* Duration: 15min\n* Push Speed: 40/s (broadcast room)\n* Push Message: {\"test\":1}\n* Received calc mode: 1s per times, total 30 times\n\n### Benchmark Resource\n\n* CPU: 2000%~2300%\n* Memory: 14GB\n* GC Pause: 504ms\n* Network: Incoming(450MBit/s), Outgoing(4.39GBit/s)\n\n### Benchmark Result\n* Received: 35,900,000/s\n\n## Comet\n![benchmark-comet](benchmark-comet.jpg)\n\n## Network traffic\n![benchmark-flow](benchmark-flow.jpg)\n\n## Heap (include GC)\n![benchmark-flow](benchmark-heap.jpg)\n"
  },
  {
    "path": "docs/en/proto.md",
    "content": "# comet and clients protocols\ncomet supports two protocols to communicate with client: WebSocket, TCP\n\n## websocket                                                                   \n**Request URL**\n\nws://DOMAIN/sub\n\n**HTTP Request Method**\n\nWebSocket (JSON Frame). Response is same as the request.\n\n**Response Result**\n\n```json\n{\n    \"ver\": 102,\n    \"op\": 10,\n    \"seq\": 10,\n    \"body\": {\"data\": \"xxx\"}\n}\n```\n\n**Request and Response Parameters**\n\n| parameter     | is required  | type | comment|\n| :-----     | :---  | :--- | :---       |\n| ver        | true  | int | Protocol version |\n| op         | true  | int    | Operation |\n| seq        | true  | int    | Sequence number (Server returned number maps to client sent) |\n| body        | json          | The JSON message pushed |\n\n## tcp                                                                         \n**Request URL**\n\ntcp://DOMAIN\n\n**Protocol**\n\nBinary. Response is same as the request.\n\n**Request and Response Parameters**\n\n| parameter     | is required  | type | comment|\n| :-----     | :---  | :--- | :---       |\n| package length        | true  | int32 bigendian | package length |\n| header Length         | true  | int16 bigendian    | header length |\n| ver        | true  | int16 bigendian    | Protocol version |\n| operation          | true | int32 bigendian | Operation |\n| seq         | true | int32 bigendian | jsonp callback |\n| body         | false | binary | $(package lenth) - $(header length) |\n\n## Operations\n| operation     | comment | \n| :-----     | :---  |\n| 2 | Client send heartbeat|\n| 3 | Server reply heartbeat|\n| 7 | authentication request |\n| 8 | authentication response |\n\n"
  },
  {
    "path": "docs/en/push.md",
    "content": "<h3>Terry-Mao/goim push HTTP protocols</h3>\npush HTTP interface protocols for pusher\n\n<h3>Interfaces</h3>\n| Name | URL | HTTP method |\n| :---- | :---- | :---- |\n| [single push](#single push)  | /1/push       | POST |\n| [multiple push](#multiple push) | /1/pushs      | POST |\n| [room push](#room push) | /1/push/room   | POST |\n| [broadcasting](#broadcasting) | /1/push/all   | POST |\n\n<h3>Public response body</h3>\n\n| response code | description |\n| :---- | :---- |\n| 1 | success |\n| 65535 | internal error |\n\n<h3>Response structure</h3>\n<pre>\n{\n    \"ret\": 1  //response code\n}\n</pre>\n\n\n##### single push\n * Example request\n\n```sh\n# uid is the user id pushing to?uid=0\ncurl -d \"{\\\"test\\\":1}\" http://127.0.0.1:7172/1/push?uid=0\n```\n\n * Response\n\n<pre>\n{\n    \"ret\": 1\n}\n</pre>\n\n##### Multiple push\n * Example request\n\n```sh\ncurl -d \"{\\\"u\\\":[1,2,3,4,5],\\\"m\\\":{\\\"test\\\":1}}\" http://127.0.0.1:7172/1/pushs\n```\n\n * Response\n\n<pre>\n{\n    \"ret\": 1\n}\n</pre>\n\n##### room push\n * Example request\n\n```sh\ncurl -d \"{\\\"test\\\": 1}\" http://127.0.0.1:7172/1/push/room?rid=1\n```\n\n * Response\n\n<pre>\n{\n    \"ret\": 1\n}\n</pre>\n\n##### Broadcasting\n * Example request\n\n```sh\ncurl -d \"{\\\"test\\\": 1}\" http://127.0.0.1:7172/1/push/all\n```\n\n * Response\n\n<pre>\n{\n    \"ret\": 1\n}\n</pre>\n"
  },
  {
    "path": "docs/proto.md",
    "content": "# comet 客户端通讯协议文档                                                     \ncomet支持两种协议和客户端通讯 websocket， tcp。\n\n## websocket                                                                   \n**请求URL**\n\nws://DOMAIN/sub\n\n**HTTP请求方式**\n\nWebsocket（JSON Frame），请求和返回协议一致\n\n**请求和返回json**\n\n```json\n{\n    \"ver\": 102,\n    \"op\": 10,\n    \"seq\": 10,\n    \"body\": {\"data\": \"xxx\"}\n}\n```\n\n**请求和返回参数说明**\n\n| 参数名     | 必选  | 类型 | 说明       |\n| :-----     | :---  | :--- | :---       |\n| ver        | true  | int | 协议版本号 |\n| op         | true  | int    | 指令 |\n| seq        | true  | int    | 序列号（服务端返回和客户端发送一一对应） |\n| body          | true | string | 授权令牌，用于检验获取用户真实用户Id |\n\n## tcp                                                                         \n**请求URL**\n\ntcp://DOMAIN\n\n**协议格式**\n\n二进制，请求和返回协议一致\n\n**请求&返回参数**\n\n| 参数名     | 必选  | 类型 | 说明       |\n| :-----     | :---  | :--- | :---       |\n| package length        | true  | int32 bigendian | 包长度 |\n| header Length         | true  | int16 bigendian    | 包头长度 |\n| ver        | true  | int16 bigendian    | 协议版本 |\n| operation          | true | int32 bigendian | 协议指令 |\n| seq         | true | int32 bigendian | 序列号 |\n| body         | false | binary | $(package lenth) - $(header length) |\n\n## 指令\n| 指令     | 说明  | \n| :-----     | :---  |\n| 2 | 客户端请求心跳 |\n| 3 | 服务端心跳答复 |\n| 5 | 下行消息 |\n| 7 | auth认证 |\n| 8 | auth认证返回 |\n\n"
  },
  {
    "path": "docs/push.md",
    "content": "## goim push API\n\n### error codes\n```\n// ok\nOK = 0\n\n// request error\nRequestErr = -400\n\n// server error\nServerErr = -500\n```\n\n### push keys\n[POST] /goim/push/keys\n\n| Name            | Type     | Remork                 |\n|:----------------|:--------:|:-----------------------|\n| [url]:operation | int32    | operation for response |\n| [url]:keys      | []string | multiple client keys   |\n| [Body]          | []byte   | http request body      |\n\nresponse:\n```\n{\n    \"code\": 0\n}\n```\n\n### push mids\n[POST] /goim/push/mids\n\n| Name            | Type     | Remork                 |\n|:----------------|:--------:|:-----------------------|\n| [url]:operation | int32    | operation for response |\n| [url]:mids      | []int64  | multiple user mids     |\n| [Body]          | []byte   | http request body      |\n\nresponse:\n```\n{\n    \"code\": 0\n}\n```\n\n### push room\n[POST] /goim/push/room\n\n| Name            | Type     | Remork                 |\n|:----------------|:--------:|:-----------------------|\n| [url]:operation | int32    | operation for response |\n| [url]:type      | string   | room type              |\n| [url]:room      | string   | room id                |\n| [Body]          | []byte   | http request body      |\n\nresponse:\n```\n{\n    \"code\": 0\n}\n```\n\n### push all\n[POST] /goim/push/all\n\n| Name            | Type     | Remork                 |\n|:----------------|:--------:|:-----------------------|\n| [url]:operation | int32    | operation for response |\n| [url]:speed     | int32    | push speed             |\n| [Body]          | []byte   | http request body      |\n\nresponse:\n```\n{\n    \"code\": 0\n}\n```\n\n### online top\n[GET] /goim/online/top\n\n| Name    | Type     | Remork                 |\n|:--------|:--------:|:-----------------------|\n| type    | string   | room type              |\n| limit   | string   | online limit           |\n\nresponse:\n```\n{\n    \"code\": 0,\n    \"message\": \"\",\n    \"data\": [\n        {\n            \"room_id\": \"1000\",\n            \"count\": 100\n        },\n        {\n            \"room_id\": \"2000\",\n            \"count\": 200\n        },\n        {\n            \"room_id\": \"3000\",\n            \"count\": 300\n        }\n    ]\n}\n```\n\n### online room\n[GET] /goim/online/room\n\n| Name    | Type     | Remork                 |\n|:--------|:--------:|:-----------------------|\n| type    | string   | room type              |\n| rooms   | []string | room ids               |\n\nresponse:\n```\n{\n    \"code\": 0,\n    \"message\": \"\",\n    \"data\": {\n        \"1000\": 100,\n        \"2000\": 200,\n        \"3000\": 300\n    }\n}\n```\n### online total\n[GET] /goim/online/total\n\nresponse:\n```\n{\n    \"code\": 0,\n    \"message\": \"\",\n    \"data\": {\n        \"conn_count\": 1,\n        \"ip_count\": 1\n    }\n}\n```\n\n### nodes weighted\n[GET] /goim/nodes/weighted\n\n| Name     | Type     | Remork                 |\n|:---------|:--------:|:-----------------------|\n| platform | string   | web/android/ios        |\n\nresponse:\n```\n{\n    \"code\": 0,\n    \"message\": \"\",\n    \"data\": {\n        \"domain\": \"conn.goim.io\",\n        \"tcp_port\": 3101,\n        \"ws_port\": 3102,\n        \"wss_port\": 3103,\n        \"heartbeat\": 30,    // heartbeat seconds\n        \"heartbeat_max\": 3  // heartbeat tries\n        \"nodes\": [\n            \"47.89.10.97\"\n        ],\n        \"backoff\": {\n            \"max_delay\": 300,\n            \"base_delay\": 3,\n            \"factor\": 1.8,\n            \"jitter\": 0.3\n        },\n        \n    }\n}\n```\n\n### nodes instances\n[GET] /nodes/instances\n\nresponse:\n```\n{\n    \"code\": 0,\n    \"message\": \"\",\n    \"data\": [\n        {\n            \"region\": \"sh\",\n            \"zone\": \"sh001\",\n            \"env\": \"dev\",\n            \"appid\": \"goim.comet\",\n            \"hostname\": \"test\",\n            \"addrs\": [\n                \"grpc://192.168.1.30:3109\"\n            ],\n            \"version\": \"\",\n            \"latest_timestamp\": 1545750122311688676,\n            \"metadata\": {\n                \"addrs\": \"47.89.10.97\",\n                \"conn_count\": \"1\",\n                \"ip_count\": \"1\",\n                \"offline\": \"false\",\n                \"weight\": \"10\"\n            }\n        }\n    ]\n}\n`\n"
  },
  {
    "path": "examples/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIID3TCCAsWgAwIBAgIJAKWU8wETRh4fMA0GCSqGSIb3DQEBBQUAMIGEMQswCQYD\nVQQGEwJjbjERMA8GA1UECAwIc2hhbmdoYWkxETAPBgNVBAcMCHNoYW5naGFpMQ0w\nCwYDVQQKDARiaWxpMQ0wCwYDVQQLDARiaWxpMREwDwYDVQQDDAhiaWxpLmNvbTEe\nMBwGCSqGSIb3DQEJARYPMjI0MzAzNTRAcXEuY29tMB4XDTE1MDkwMTEyMDQxMloX\nDTI1MDgyOTEyMDQxMlowgYQxCzAJBgNVBAYTAmNuMREwDwYDVQQIDAhzaGFuZ2hh\naTERMA8GA1UEBwwIc2hhbmdoYWkxDTALBgNVBAoMBGJpbGkxDTALBgNVBAsMBGJp\nbGkxETAPBgNVBAMMCGJpbGkuY29tMR4wHAYJKoZIhvcNAQkBFg8yMjQzMDM1NEBx\ncS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKFwSxJqYPxzMe\nm5PeYA4YmcUsDCqS9Z7PsszOMZ1YsWZIHMB74D49ad2R+9PoqlfNH1L9C4NFSBrF\nrhSkaLmFYxw9yeJ2EAPijASBgfxMFVrEJhu7SW86OPTVnHblU8UqQdnMFOqF49C9\nmCdbiGu/99BZVCL1WmlSQCWVEIzOgX+goxqHuwXUF58YUwr6WLtF0DuBcLUai1vB\nPg+PJ2fLjSR2o0KJkPOd6+y90cgoxfyJFUHuUKyV8EU4VwHEIA9rVizprziqPx6c\n9A9Ng0FpA2leSPLGYCjnDtKIOvbSOS8DMkRT55ujqoVrj0yiNWsuJlc/NbD6bS16\nfJjuLOtJAgMBAAGjUDBOMB0GA1UdDgQWBBQzxdSIYIkDABh98Cj6VeYasC7/STAf\nBgNVHSMEGDAWgBQzxdSIYIkDABh98Cj6VeYasC7/STAMBgNVHRMEBTADAQH/MA0G\nCSqGSIb3DQEBBQUAA4IBAQA1Fhr+SU62xHWlPOBhTbjod49+mNfXn2TZz/vBp/Jl\npHZgDLAEcrhXHmi2A0G9K9+qOIEn4BvTd70jSYvYlaeUSzZ/nEpeM0oE0f2Qaxov\nPhxDpsqPsSQm6pE64/los1doaiElfMVFaP56UGV01kFdI013wxwd2WCuj51Hmvi9\nthsS027aqxjHMJnKXPvBm2E6EDkPfc/e+AEmwBzry+aamRizaMrk/SfSGTy9/rvd\n+VbBfHiJ50kMld51SLIc6qkVaTXess7mIfcsk7kyjP4eFA0y+3wmXfRZeWadND3I\nO9XNNwsDVFXlhW40GUVriy95qa1Sq3sLUfUQcCH4VFFK\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "examples/javascript/client.js",
    "content": "(function(win) {\n    const rawHeaderLen = 16;\n    const packetOffset = 0;\n    const headerOffset = 4;\n    const verOffset = 6;\n    const opOffset = 8;\n    const seqOffset = 12;\n\n    var Client = function(options) {\n        var MAX_CONNECT_TIMES = 10;\n        var DELAY = 15000;\n        this.options = options || {};\n        this.createConnect(MAX_CONNECT_TIMES, DELAY);\n    }\n\n    var appendMsg = function(text) {\n        var span = document.createElement(\"SPAN\");\n        var text = document.createTextNode(text);\n        span.appendChild(text);\n        document.getElementById(\"box\").appendChild(span);\n    }\n\n    Client.prototype.createConnect = function(max, delay) {\n        var self = this;\n        if (max === 0) {\n            return;\n        }\n        connect();\n\n        var textDecoder = new TextDecoder();\n        var textEncoder = new TextEncoder();\n        var heartbeatInterval;\n        function connect() {\n            var ws = new WebSocket('ws://sh.tony.wiki:3102/sub');\n            //var ws = new WebSocket('ws://127.0.0.1:3102/sub');\n            ws.binaryType = 'arraybuffer';\n            ws.onopen = function() {\n                auth();\n            }\n\n            ws.onmessage = function(evt) {\n                var data = evt.data;\n                var dataView = new DataView(data, 0);\n                var packetLen = dataView.getInt32(packetOffset);\n                var headerLen = dataView.getInt16(headerOffset);\n                var ver = dataView.getInt16(verOffset);\n                var op = dataView.getInt32(opOffset);\n                var seq = dataView.getInt32(seqOffset);\n\n                console.log(\"receiveHeader: packetLen=\" + packetLen, \"headerLen=\" + headerLen, \"ver=\" + ver, \"op=\" + op, \"seq=\" + seq);\n\n                switch(op) {\n                    case 8:\n                        // auth reply ok\n                        document.getElementById(\"status\").innerHTML = \"<color style='color:green'>ok<color>\";\n                        appendMsg(\"receive: auth reply\");\n                        // send a heartbeat to server\n                        heartbeat();\n                        heartbeatInterval = setInterval(heartbeat, 30 * 1000);\n                        break;\n                    case 3:\n                        // receive a heartbeat from server\n                        console.log(\"receive: heartbeat\");\n                        appendMsg(\"receive: heartbeat reply\");\n                        break;\n                    case 9:\n                        // batch message\n                        for (var offset=rawHeaderLen; offset<data.byteLength; offset+=packetLen) {\n                            // parse\n                            var packetLen = dataView.getInt32(offset);\n                            var headerLen = dataView.getInt16(offset+headerOffset);\n                            var ver = dataView.getInt16(offset+verOffset);\n                            var op = dataView.getInt32(offset+opOffset);\n                            var seq = dataView.getInt32(offset+seqOffset);\n                            var msgBody = textDecoder.decode(data.slice(offset+headerLen, offset+packetLen));\n                            // callback\n                            messageReceived(ver, msgBody);\n                            appendMsg(\"receive: ver=\" + ver + \" op=\" + op + \" seq=\" + seq + \" message=\" + msgBody);\n                        }\n                        break;\n                    default:\n                        var msgBody = textDecoder.decode(data.slice(headerLen, packetLen));\n                        messageReceived(ver, msgBody);\n                        appendMsg(\"receive: ver=\" + ver + \" op=\" + op + \" seq=\" + seq + \" message=\" + msgBody);\n                        break\n                }\n            }\n\n            ws.onclose = function() {\n                if (heartbeatInterval) clearInterval(heartbeatInterval);\n                setTimeout(reConnect, delay);\n\n                document.getElementById(\"status\").innerHTML =  \"<color style='color:red'>failed<color>\";\n            }\n\n            function heartbeat() {\n                var headerBuf = new ArrayBuffer(rawHeaderLen);\n                var headerView = new DataView(headerBuf, 0);\n                headerView.setInt32(packetOffset, rawHeaderLen);\n                headerView.setInt16(headerOffset, rawHeaderLen);\n                headerView.setInt16(verOffset, 1);\n                headerView.setInt32(opOffset, 2);\n                headerView.setInt32(seqOffset, 1);\n                ws.send(headerBuf);\n                console.log(\"send: heartbeat\");\n                appendMsg(\"send: heartbeat\");\n            }\n\n            function auth() {\n                var token = '{\"mid\":123, \"room_id\":\"live://1000\", \"platform\":\"web\", \"accepts\":[1000,1001,1002]}'\n                var headerBuf = new ArrayBuffer(rawHeaderLen);\n                var headerView = new DataView(headerBuf, 0);\n                var bodyBuf = textEncoder.encode(token);\n                headerView.setInt32(packetOffset, rawHeaderLen + bodyBuf.byteLength);\n                headerView.setInt16(headerOffset, rawHeaderLen);\n                headerView.setInt16(verOffset, 1);\n                headerView.setInt32(opOffset, 7);\n                headerView.setInt32(seqOffset, 1);\n                ws.send(mergeArrayBuffer(headerBuf, bodyBuf));\n\n                appendMsg(\"send: auth token: \" + token);\n            }\n\n            function messageReceived(ver, body) {\n                var notify = self.options.notify;\n                if(notify) notify(body);\n                console.log(\"messageReceived:\", \"ver=\" + ver, \"body=\" + body);\n            }\n\n            function mergeArrayBuffer(ab1, ab2) {\n                var u81 = new Uint8Array(ab1),\n                    u82 = new Uint8Array(ab2),\n                    res = new Uint8Array(ab1.byteLength + ab2.byteLength);\n                res.set(u81, 0);\n                res.set(u82, ab1.byteLength);\n                return res.buffer;\n            }\n\n            function char2ab(str) {\n                var buf = new ArrayBuffer(str.length);\n                var bufView = new Uint8Array(buf);\n                for (var i=0; i<str.length; i++) {\n                    bufView[i] = str[i];\n                }\n                return buf;\n            }\n\n        }\n\n        function reConnect() {\n            self.createConnect(--max, delay * 2);\n        }\n    }\n\n    win['MyClient'] = Client;\n})(window);\n"
  },
  {
    "path": "examples/javascript/index.html",
    "content": "<html lang=\"zh-CN\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>client demo</title>\n    <script src=\"client.js\"></script>\n    <script>\n      var client = new MyClient({\n        notify: function(data) {\n\t\t  console.log(data);\n          // alert(JSON.stringify(data));\n        }\n      });\n    </script>\n    <style type=\"text/css\">\n        span{ display:block;}\n    </style>\n  </head>\n  <body>\n    <h1>websocket</h1>\n    <h2>status:</h2>\n    <span id=\"status\"></span>\n    <h2>push</h2>\n    <div>\n        <p>curl -d 'mid message' 'http://api.goim.io:3111/goim/push/mids?operation=1000&mids=123'</p>\n        <p>curl -d 'room message' 'http://api.goim.io:3111/goim/push/room?operation=1000&type=live&room=1000'</p>\n        <p>curl -d 'broadcast message' 'http://api.goim.io:3111/goim/push/all?operation=1000'</p>\n    </div>\n    <h2>message:</h2>\n    <div id=\"box\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/javascript/main.go",
    "content": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\t// Simple static webserver:\n\tlog.Fatal(http.ListenAndServe(\":1999\", http.FileServer(http.Dir(\"./\"))))\n}\n"
  },
  {
    "path": "examples/private.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAyhcEsSamD8czHpuT3mAOGJnFLAwqkvWez7LMzjGdWLFmSBzA\ne+A+PWndkfvT6KpXzR9S/QuDRUgaxa4UpGi5hWMcPcnidhAD4owEgYH8TBVaxCYb\nu0lvOjj01Zx25VPFKkHZzBTqhePQvZgnW4hrv/fQWVQi9VppUkAllRCMzoF/oKMa\nh7sF1BefGFMK+li7RdA7gXC1GotbwT4Pjydny40kdqNCiZDznevsvdHIKMX8iRVB\n7lCslfBFOFcBxCAPa1Ys6a84qj8enPQPTYNBaQNpXkjyxmAo5w7SiDr20jkvAzJE\nU+ebo6qFa49MojVrLiZXPzWw+m0tenyY7izrSQIDAQABAoIBAQCNasAwy2/nmKjw\nIUS/l44lru1oXncocdMZWvCw1c1a9IEzs1MLHKfRSBTyBDyNEy7v7pyfUQAiaku5\ny5DMYDB65BkuL+lWXuypCvxYOEL6ZvMmUdiUHdZE8vh5xsz4u788S+qCQpy+5uX6\n1s+r4PIt2tekuxjfgs4y7YqfHn66PmxAWXTV+jnWpUWYjpRTgDWeU6ikbk2fHjvG\nytF4AV4lEKNnGBC5F+5lk3QJKjb1IW/o5q42TCeERk1k5Ruc5kfzq72hdFYrQAF1\nPmstEXMExMS/K6AerYtPfiWWFWH2gCTZMf/C8Jwr0zPVpHM1PF+Ien3IkgiOTfZE\n1+/wrx4BAoGBAOnpnxFnnqhZdDRi+DALfY3mXPwOizFYnAhuuuzO87zoZsps529E\nj5pEZQoYPTiww5rptjFhNjoV0gsh2GO5QHCiMxM8A76aWVc4YmK0GiHVudpAu2t+\naK8+0Xr03SA27cKJjp12NWijzEdTjSQYaFwD/8/dMrU3MCu30kdEJ6iJAoGBAN0s\nJXLv4CtB/DLTvifTZ/OAGSc1/Z+X0w2EqIugw7IV9YuAm2721IFhsFFXeANRRHY6\nzonLUEgsuQUc30NCz+susChs2GMEFypLem8D1c5ZZY+9sIsR1WFB97o7bTMvp5lH\n2REpEDZKXVOB7UmrclLgTKRQgG3O73rMP6QXYPzBAoGBAJm8Gvi0crlQuagol9fz\n5Vwa2GgtItyW0U5VgHNdfSJeWBiYxO8DT6Jja0jcL3iP7K9nBYCk1KAOcVMxtmes\nfKbKY+kzW36tMSS7ASbAGiC8uH6yZru6hBERp1o5jw+6Kj/eaqYg5+9TIFKMnknn\n5Mb9NecnCUnC8Nz63rBKIgqJAoGBAJHUpeyfFaPwIiYxT1RbJFN9xxf/lXdBWDu1\nmJxYKDCoIfsVlWcZAQ0+KE+56LvnPcjnBX/9urWcJ3KjkuJ6jzV211gQTK0c6VlN\n4zCHytYAQ+L/JATOgW9bW8hDnsD9TvjWUt3pwXLKnbaOGLNWhE747g/5tHSy2VyS\nh/PeJmkBAoGABMfEaiLHXXQhUK0BPYxT3T8i9IjAYwXlrgSLlnvOGslZef/45kP5\nCM1UbMSwAn+HvAziOFt2WmynFCysy/lCyTud+Fd/IZFcMThp7wvi7fSZo0NDM7ES\n9JfgTmCY4Kwv6kT85poIka9bp4Nh47EVB9kDoqm/lSMkfYqcWH66DJA=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/Terry-Mao/goim\n\ngo 1.13\n\nrequire (\n\tgithub.com/BurntSushi/toml v0.3.1\n\tgithub.com/Shopify/sarama v1.19.0 // indirect\n\tgithub.com/Shopify/toxiproxy v2.1.4+incompatible // indirect\n\tgithub.com/bilibili/discovery v1.0.1\n\tgithub.com/bsm/sarama-cluster v2.1.15+incompatible\n\tgithub.com/eapache/go-resiliency v1.1.0 // indirect\n\tgithub.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect\n\tgithub.com/eapache/queue v1.1.0 // indirect\n\tgithub.com/gin-gonic/gin v1.7.0\n\tgithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b\n\tgithub.com/golang/protobuf v1.4.3\n\tgithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect\n\tgithub.com/gomodule/redigo v2.0.0+incompatible\n\tgithub.com/google/uuid v1.0.0\n\tgithub.com/onsi/ginkgo v1.16.0 // indirect\n\tgithub.com/onsi/gomega v1.11.0 // indirect\n\tgithub.com/pierrec/lz4 v2.0.5+incompatible // indirect\n\tgithub.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect\n\tgithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect\n\tgithub.com/stretchr/testify v1.5.1\n\tgithub.com/zhenjl/cityhash v0.0.0-20131128155616-cdd6a94144ab\n\tgolang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb\n\tgoogle.golang.org/grpc v1.22.3\n\tgopkg.in/Shopify/sarama.v1 v1.19.0\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.0.0-20170626110600-a368813c5e64/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s=\ngithub.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=\ngithub.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=\ngithub.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=\ngithub.com/bilibili/discovery v1.0.1 h1:6W9B2caxOdfBEKCMawwXU3dJ0W1TCONuprbXbkGe+s4=\ngithub.com/bilibili/discovery v1.0.1/go.mod h1:daS5nEYEBt0scrrmuoNCxWXDHFK6gtEpjhVKG6MUxUg=\ngithub.com/bsm/sarama-cluster v2.1.15+incompatible h1:RkV6WiNRnqEEbp81druK8zYhmnIgdOjqSVi0+9Cnl2A=\ngithub.com/bsm/sarama-cluster v2.1.15+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=\ngithub.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=\ngithub.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=\ngithub.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=\ngithub.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=\ngithub.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=\ngithub.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=\ngithub.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=\ngithub.com/gin-gonic/gin v0.0.0-20180512030042-bf7803815b0b/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=\ngithub.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU=\ngithub.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=\ngithub.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=\ngithub.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=\ngithub.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=\ngithub.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=\ngithub.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=\ngithub.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=\ngithub.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=\ngithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=\ngithub.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=\ngithub.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gopherjs/gopherjs v0.0.0-20180424202546-8dffc02ea1cb h1:g0omhilXoAZ+6sFcF6puAzT+/MoKK3ZBQ9e7nVIRjrc=\ngithub.com/gopherjs/gopherjs v0.0.0-20180424202546-8dffc02ea1cb/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/json-iterator/go v0.0.0-20180526014329-8744d7c5c7b4/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=\ngithub.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=\ngithub.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=\ngithub.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=\ngithub.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v0.0.0-20180511053014-58118c1ea916/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=\ngithub.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=\ngithub.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.16.0 h1:NBrNLB37exjJLxXtFOktx6CISBdS1aF8+7MwKlTV8U4=\ngithub.com/onsi/ginkgo v1.16.0/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug=\ngithub.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=\ngithub.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=\ngithub.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=\ngithub.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\ngithub.com/smartystreets/assertions v0.0.0-20180301161246-7678a5452ebe/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=\ngithub.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=\ngithub.com/smartystreets/gunit v0.0.0-20180314194857-6f0d6275bdcd/go.mod h1:XUKj4gbqj2QvJk/OdLWzyZ3FYli0f+MdpngyryX0gcw=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/ugorji/go v0.0.0-20180407103000-f3cacc17c85e/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=\ngithub.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=\ngithub.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=\ngithub.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=\ngithub.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/zhenjl/cityhash v0.0.0-20131128155616-cdd6a94144ab h1:BWHvAOZz0pBILkGl/ebPQKZDrqbaWj/iN9RE8AvaTvg=\ngithub.com/zhenjl/cityhash v0.0.0-20131128155616-cdd6a94144ab/go.mod h1:P6L88wrqK99Njntah9SB7AyzFpUXsXYq06LkjixxQmY=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=\ngolang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/grpc v0.0.0-20181030232906-a88340f3c899/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=\ngoogle.golang.org/grpc v1.22.3 h1:U4Oh9xJixJwAFqa1c5uLhNAh8ERM/lc3hNhPEJiAEhs=\ngoogle.golang.org/grpc v1.22.3/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngopkg.in/Shopify/sarama.v1 v1.19.0 h1:yvI/R1jfMpKvvwmX4r/AQjaI5oszWEOlvKxUdaj53OM=\ngopkg.in/Shopify/sarama.v1 v1.19.0/go.mod h1:AxnvoaevB2nBjNK17cG61A3LleFcWFwVBHBt+cot4Oc=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=\ngopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=\ngopkg.in/h2non/gock.v1 v1.0.8 h1:P8Ul3tXxL84suEhp+a7Uu6f9rBszP+gLkae2D6U1gS0=\ngopkg.in/h2non/gock.v1 v1.0.8/go.mod h1:KHI4Z1sxDW6P4N3DfTWSEza07YpkQP7KJBfglRMEjKY=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\nhonnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "internal/comet/bucket.go",
    "content": "package comet\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\n\tpb \"github.com/Terry-Mao/goim/api/comet\"\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/internal/comet/conf\"\n)\n\n// Bucket is a channel holder.\ntype Bucket struct {\n\tc     *conf.Bucket\n\tcLock sync.RWMutex        // protect the channels for chs\n\tchs   map[string]*Channel // map sub key to a channel\n\t// room\n\trooms       map[string]*Room // bucket room channels\n\troutines    []chan *pb.BroadcastRoomReq\n\troutinesNum uint64\n\n\tipCnts map[string]int32\n}\n\n// NewBucket new a bucket struct. store the key with im channel.\nfunc NewBucket(c *conf.Bucket) (b *Bucket) {\n\tb = new(Bucket)\n\tb.chs = make(map[string]*Channel, c.Channel)\n\tb.ipCnts = make(map[string]int32)\n\tb.c = c\n\tb.rooms = make(map[string]*Room, c.Room)\n\tb.routines = make([]chan *pb.BroadcastRoomReq, c.RoutineAmount)\n\tfor i := uint64(0); i < c.RoutineAmount; i++ {\n\t\tc := make(chan *pb.BroadcastRoomReq, c.RoutineSize)\n\t\tb.routines[i] = c\n\t\tgo b.roomproc(c)\n\t}\n\treturn\n}\n\n// ChannelCount channel count in the bucket\nfunc (b *Bucket) ChannelCount() int {\n\treturn len(b.chs)\n}\n\n// RoomCount room count in the bucket\nfunc (b *Bucket) RoomCount() int {\n\treturn len(b.rooms)\n}\n\n// RoomsCount get all room id where online number > 0.\nfunc (b *Bucket) RoomsCount() (res map[string]int32) {\n\tvar (\n\t\troomID string\n\t\troom   *Room\n\t)\n\tb.cLock.RLock()\n\tres = make(map[string]int32)\n\tfor roomID, room = range b.rooms {\n\t\tif room.Online > 0 {\n\t\t\tres[roomID] = room.Online\n\t\t}\n\t}\n\tb.cLock.RUnlock()\n\treturn\n}\n\n// ChangeRoom change ro room\nfunc (b *Bucket) ChangeRoom(nrid string, ch *Channel) (err error) {\n\tvar (\n\t\tnroom *Room\n\t\tok    bool\n\t\toroom = ch.Room\n\t)\n\t// change to no room\n\tif nrid == \"\" {\n\t\tif oroom != nil && oroom.Del(ch) {\n\t\t\tb.DelRoom(oroom)\n\t\t}\n\t\tch.Room = nil\n\t\treturn\n\t}\n\tb.cLock.Lock()\n\tif nroom, ok = b.rooms[nrid]; !ok {\n\t\tnroom = NewRoom(nrid)\n\t\tb.rooms[nrid] = nroom\n\t}\n\tb.cLock.Unlock()\n\tif oroom != nil && oroom.Del(ch) {\n\t\tb.DelRoom(oroom)\n\t}\n\n\tif err = nroom.Put(ch); err != nil {\n\t\treturn\n\t}\n\tch.Room = nroom\n\treturn\n}\n\n// Put put a channel according with sub key.\nfunc (b *Bucket) Put(rid string, ch *Channel) (err error) {\n\tvar (\n\t\troom *Room\n\t\tok   bool\n\t)\n\tb.cLock.Lock()\n\t// close old channel\n\tif dch := b.chs[ch.Key]; dch != nil {\n\t\tdch.Close()\n\t}\n\tb.chs[ch.Key] = ch\n\tif rid != \"\" {\n\t\tif room, ok = b.rooms[rid]; !ok {\n\t\t\troom = NewRoom(rid)\n\t\t\tb.rooms[rid] = room\n\t\t}\n\t\tch.Room = room\n\t}\n\tb.ipCnts[ch.IP]++\n\tb.cLock.Unlock()\n\tif room != nil {\n\t\terr = room.Put(ch)\n\t}\n\treturn\n}\n\n// Del delete the channel by sub key.\nfunc (b *Bucket) Del(dch *Channel) {\n\troom := dch.Room\n\tb.cLock.Lock()\n\tif ch, ok := b.chs[dch.Key]; ok {\n\t\tif ch == dch {\n\t\t\tdelete(b.chs, ch.Key)\n\t\t}\n\t\t// ip counter\n\t\tif b.ipCnts[ch.IP] > 1 {\n\t\t\tb.ipCnts[ch.IP]--\n\t\t} else {\n\t\t\tdelete(b.ipCnts, ch.IP)\n\t\t}\n\t}\n\tb.cLock.Unlock()\n\tif room != nil && room.Del(dch) {\n\t\t// if empty room, must delete from bucket\n\t\tb.DelRoom(room)\n\t}\n}\n\n// Channel get a channel by sub key.\nfunc (b *Bucket) Channel(key string) (ch *Channel) {\n\tb.cLock.RLock()\n\tch = b.chs[key]\n\tb.cLock.RUnlock()\n\treturn\n}\n\n// Broadcast push msgs to all channels in the bucket.\nfunc (b *Bucket) Broadcast(p *protocol.Proto, op int32) {\n\tvar ch *Channel\n\tb.cLock.RLock()\n\tfor _, ch = range b.chs {\n\t\tif !ch.NeedPush(op) {\n\t\t\tcontinue\n\t\t}\n\t\t_ = ch.Push(p)\n\t}\n\tb.cLock.RUnlock()\n}\n\n// Room get a room by roomid.\nfunc (b *Bucket) Room(rid string) (room *Room) {\n\tb.cLock.RLock()\n\troom = b.rooms[rid]\n\tb.cLock.RUnlock()\n\treturn\n}\n\n// DelRoom delete a room by roomid.\nfunc (b *Bucket) DelRoom(room *Room) {\n\tb.cLock.Lock()\n\tdelete(b.rooms, room.ID)\n\tb.cLock.Unlock()\n\troom.Close()\n}\n\n// BroadcastRoom broadcast a message to specified room\nfunc (b *Bucket) BroadcastRoom(arg *pb.BroadcastRoomReq) {\n\tnum := atomic.AddUint64(&b.routinesNum, 1) % b.c.RoutineAmount\n\tb.routines[num] <- arg\n}\n\n// Rooms get all room id where online number > 0.\nfunc (b *Bucket) Rooms() (res map[string]struct{}) {\n\tvar (\n\t\troomID string\n\t\troom   *Room\n\t)\n\tres = make(map[string]struct{})\n\tb.cLock.RLock()\n\tfor roomID, room = range b.rooms {\n\t\tif room.Online > 0 {\n\t\t\tres[roomID] = struct{}{}\n\t\t}\n\t}\n\tb.cLock.RUnlock()\n\treturn\n}\n\n// IPCount get ip count.\nfunc (b *Bucket) IPCount() (res map[string]struct{}) {\n\tvar (\n\t\tip string\n\t)\n\tb.cLock.RLock()\n\tres = make(map[string]struct{}, len(b.ipCnts))\n\tfor ip = range b.ipCnts {\n\t\tres[ip] = struct{}{}\n\t}\n\tb.cLock.RUnlock()\n\treturn\n}\n\n// UpRoomsCount update all room count\nfunc (b *Bucket) UpRoomsCount(roomCountMap map[string]int32) {\n\tvar (\n\t\troomID string\n\t\troom   *Room\n\t)\n\tb.cLock.RLock()\n\tfor roomID, room = range b.rooms {\n\t\troom.AllOnline = roomCountMap[roomID]\n\t}\n\tb.cLock.RUnlock()\n}\n\n// roomproc\nfunc (b *Bucket) roomproc(c chan *pb.BroadcastRoomReq) {\n\tfor {\n\t\targ := <-c\n\t\tif room := b.Room(arg.RoomID); room != nil {\n\t\t\troom.Push(arg.Proto)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/comet/channel.go",
    "content": "package comet\n\nimport (\n\t\"sync\"\n\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/internal/comet/errors\"\n\t\"github.com/Terry-Mao/goim/pkg/bufio\"\n)\n\n// Channel used by message pusher send msg to write goroutine.\ntype Channel struct {\n\tRoom     *Room\n\tCliProto Ring\n\tsignal   chan *protocol.Proto\n\tWriter   bufio.Writer\n\tReader   bufio.Reader\n\tNext     *Channel\n\tPrev     *Channel\n\n\tMid      int64\n\tKey      string\n\tIP       string\n\twatchOps map[int32]struct{}\n\tmutex    sync.RWMutex\n}\n\n// NewChannel new a channel.\nfunc NewChannel(cli, svr int) *Channel {\n\tc := new(Channel)\n\tc.CliProto.Init(cli)\n\tc.signal = make(chan *protocol.Proto, svr)\n\tc.watchOps = make(map[int32]struct{})\n\treturn c\n}\n\n// Watch watch a operation.\nfunc (c *Channel) Watch(accepts ...int32) {\n\tc.mutex.Lock()\n\tfor _, op := range accepts {\n\t\tc.watchOps[op] = struct{}{}\n\t}\n\tc.mutex.Unlock()\n}\n\n// UnWatch unwatch an operation\nfunc (c *Channel) UnWatch(accepts ...int32) {\n\tc.mutex.Lock()\n\tfor _, op := range accepts {\n\t\tdelete(c.watchOps, op)\n\t}\n\tc.mutex.Unlock()\n}\n\n// NeedPush verify if in watch.\nfunc (c *Channel) NeedPush(op int32) bool {\n\tc.mutex.RLock()\n\tif _, ok := c.watchOps[op]; ok {\n\t\tc.mutex.RUnlock()\n\t\treturn true\n\t}\n\tc.mutex.RUnlock()\n\treturn false\n}\n\n// Push server push message.\nfunc (c *Channel) Push(p *protocol.Proto) (err error) {\n\tselect {\n\tcase c.signal <- p:\n\tdefault:\n\t\terr = errors.ErrSignalFullMsgDropped\n\t}\n\treturn\n}\n\n// Ready check the channel ready or close?\nfunc (c *Channel) Ready() *protocol.Proto {\n\treturn <-c.signal\n}\n\n// Signal send signal to the channel, protocol ready.\nfunc (c *Channel) Signal() {\n\tc.signal <- protocol.ProtoReady\n}\n\n// Close close the channel.\nfunc (c *Channel) Close() {\n\tc.signal <- protocol.ProtoFinish\n}\n"
  },
  {
    "path": "internal/comet/conf/conf.go",
    "content": "package conf\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/bilibili/discovery/naming\"\n\t\"github.com/BurntSushi/toml\"\n\txtime \"github.com/Terry-Mao/goim/pkg/time\"\n)\n\nvar (\n\tconfPath  string\n\tregion    string\n\tzone      string\n\tdeployEnv string\n\thost      string\n\taddrs     string\n\tweight    int64\n\toffline   bool\n\tdebug     bool\n\n\t// Conf config\n\tConf *Config\n)\n\nfunc init() {\n\tvar (\n\t\tdefHost, _    = os.Hostname()\n\t\tdefAddrs      = os.Getenv(\"ADDRS\")\n\t\tdefWeight, _  = strconv.ParseInt(os.Getenv(\"WEIGHT\"), 10, 32)\n\t\tdefOffline, _ = strconv.ParseBool(os.Getenv(\"OFFLINE\"))\n\t\tdefDebug, _   = strconv.ParseBool(os.Getenv(\"DEBUG\"))\n\t)\n\tflag.StringVar(&confPath, \"conf\", \"comet-example.toml\", \"default config path.\")\n\tflag.StringVar(&region, \"region\", os.Getenv(\"REGION\"), \"avaliable region. or use REGION env variable, value: sh etc.\")\n\tflag.StringVar(&zone, \"zone\", os.Getenv(\"ZONE\"), \"avaliable zone. or use ZONE env variable, value: sh001/sh002 etc.\")\n\tflag.StringVar(&deployEnv, \"deploy.env\", os.Getenv(\"DEPLOY_ENV\"), \"deploy env. or use DEPLOY_ENV env variable, value: dev/fat1/uat/pre/prod etc.\")\n\tflag.StringVar(&host, \"host\", defHost, \"machine hostname. or use default machine hostname.\")\n\tflag.StringVar(&addrs, \"addrs\", defAddrs, \"server public ip addrs. or use ADDRS env variable, value: 127.0.0.1 etc.\")\n\tflag.Int64Var(&weight, \"weight\", defWeight, \"load balancing weight, or use WEIGHT env variable, value: 10 etc.\")\n\tflag.BoolVar(&offline, \"offline\", defOffline, \"server offline. or use OFFLINE env variable, value: true/false etc.\")\n\tflag.BoolVar(&debug, \"debug\", defDebug, \"server debug. or use DEBUG env variable, value: true/false etc.\")\n}\n\n// Init init config.\nfunc Init() (err error) {\n\tConf = Default()\n\t_, err = toml.DecodeFile(confPath, &Conf)\n\treturn\n}\n\n// Default new a config with specified defualt value.\nfunc Default() *Config {\n\treturn &Config{\n\t\tDebug:     debug,\n\t\tEnv:       &Env{Region: region, Zone: zone, DeployEnv: deployEnv, Host: host, Weight: weight, Addrs: strings.Split(addrs, \",\"), Offline: offline},\n\t\tDiscovery: &naming.Config{Region: region, Zone: zone, Env: deployEnv, Host: host},\n\t\tRPCClient: &RPCClient{\n\t\t\tDial:    xtime.Duration(time.Second),\n\t\t\tTimeout: xtime.Duration(time.Second),\n\t\t},\n\t\tRPCServer: &RPCServer{\n\t\t\tNetwork:           \"tcp\",\n\t\t\tAddr:              \":3109\",\n\t\t\tTimeout:           xtime.Duration(time.Second),\n\t\t\tIdleTimeout:       xtime.Duration(time.Second * 60),\n\t\t\tMaxLifeTime:       xtime.Duration(time.Hour * 2),\n\t\t\tForceCloseWait:    xtime.Duration(time.Second * 20),\n\t\t\tKeepAliveInterval: xtime.Duration(time.Second * 60),\n\t\t\tKeepAliveTimeout:  xtime.Duration(time.Second * 20),\n\t\t},\n\t\tTCP: &TCP{\n\t\t\tBind:         []string{\":3101\"},\n\t\t\tSndbuf:       4096,\n\t\t\tRcvbuf:       4096,\n\t\t\tKeepAlive:    false,\n\t\t\tReader:       32,\n\t\t\tReadBuf:      1024,\n\t\t\tReadBufSize:  8192,\n\t\t\tWriter:       32,\n\t\t\tWriteBuf:     1024,\n\t\t\tWriteBufSize: 8192,\n\t\t},\n\t\tWebsocket: &Websocket{\n\t\t\tBind: []string{\":3102\"},\n\t\t},\n\t\tProtocol: &Protocol{\n\t\t\tTimer:            32,\n\t\t\tTimerSize:        2048,\n\t\t\tCliProto:         5,\n\t\t\tSvrProto:         10,\n\t\t\tHandshakeTimeout: xtime.Duration(time.Second * 5),\n\t\t},\n\t\tBucket: &Bucket{\n\t\t\tSize:          32,\n\t\t\tChannel:       1024,\n\t\t\tRoom:          1024,\n\t\t\tRoutineAmount: 32,\n\t\t\tRoutineSize:   1024,\n\t\t},\n\t}\n}\n\n// Config is comet config.\ntype Config struct {\n\tDebug     bool\n\tEnv       *Env\n\tDiscovery *naming.Config\n\tTCP       *TCP\n\tWebsocket *Websocket\n\tProtocol  *Protocol\n\tBucket    *Bucket\n\tRPCClient *RPCClient\n\tRPCServer *RPCServer\n\tWhitelist *Whitelist\n}\n\n// Env is env config.\ntype Env struct {\n\tRegion    string\n\tZone      string\n\tDeployEnv string\n\tHost      string\n\tWeight    int64\n\tOffline   bool\n\tAddrs     []string\n}\n\n// RPCClient is RPC client config.\ntype RPCClient struct {\n\tDial    xtime.Duration\n\tTimeout xtime.Duration\n}\n\n// RPCServer is RPC server config.\ntype RPCServer struct {\n\tNetwork           string\n\tAddr              string\n\tTimeout           xtime.Duration\n\tIdleTimeout       xtime.Duration\n\tMaxLifeTime       xtime.Duration\n\tForceCloseWait    xtime.Duration\n\tKeepAliveInterval xtime.Duration\n\tKeepAliveTimeout  xtime.Duration\n}\n\n// TCP is tcp config.\ntype TCP struct {\n\tBind         []string\n\tSndbuf       int\n\tRcvbuf       int\n\tKeepAlive    bool\n\tReader       int\n\tReadBuf      int\n\tReadBufSize  int\n\tWriter       int\n\tWriteBuf     int\n\tWriteBufSize int\n}\n\n// Websocket is websocket config.\ntype Websocket struct {\n\tBind        []string\n\tTLSOpen     bool\n\tTLSBind     []string\n\tCertFile    string\n\tPrivateFile string\n}\n\n// Protocol is protocol config.\ntype Protocol struct {\n\tTimer            int\n\tTimerSize        int\n\tSvrProto         int\n\tCliProto         int\n\tHandshakeTimeout xtime.Duration\n}\n\n// Bucket is bucket config.\ntype Bucket struct {\n\tSize          int\n\tChannel       int\n\tRoom          int\n\tRoutineAmount uint64\n\tRoutineSize   int\n}\n\n// Whitelist is white list config.\ntype Whitelist struct {\n\tWhitelist []int64\n\tWhiteLog  string\n}\n"
  },
  {
    "path": "internal/comet/errors/errors.go",
    "content": "package errors\n\nimport (\n\t\"errors\"\n)\n\n// .\nvar (\n\t// server\n\tErrHandshake = errors.New(\"handshake failed\")\n\tErrOperation = errors.New(\"request operation not valid\")\n\t// ring\n\tErrRingEmpty = errors.New(\"ring buffer empty\")\n\tErrRingFull  = errors.New(\"ring buffer full\")\n\t// timer\n\tErrTimerFull   = errors.New(\"timer full\")\n\tErrTimerEmpty  = errors.New(\"timer empty\")\n\tErrTimerNoItem = errors.New(\"timer item not exist\")\n\t// channel\n\tErrPushMsgArg           = errors.New(\"rpc pushmsg arg error\")\n\tErrPushMsgsArg          = errors.New(\"rpc pushmsgs arg error\")\n\tErrMPushMsgArg          = errors.New(\"rpc mpushmsg arg error\")\n\tErrMPushMsgsArg         = errors.New(\"rpc mpushmsgs arg error\")\n\tErrSignalFullMsgDropped = errors.New(\"signal channel full, msg dropped\")\n\t// bucket\n\tErrBroadCastArg     = errors.New(\"rpc broadcast arg error\")\n\tErrBroadCastRoomArg = errors.New(\"rpc broadcast  room arg error\")\n\n\t// room\n\tErrRoomDroped = errors.New(\"room droped\")\n\t// rpc\n\tErrLogic = errors.New(\"logic rpc is not available\")\n)\n"
  },
  {
    "path": "internal/comet/grpc/server.go",
    "content": "package grpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\tpb \"github.com/Terry-Mao/goim/api/comet\"\n\t\"github.com/Terry-Mao/goim/internal/comet\"\n\t\"github.com/Terry-Mao/goim/internal/comet/conf\"\n\t\"github.com/Terry-Mao/goim/internal/comet/errors\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/keepalive\"\n)\n\n// New comet grpc server.\nfunc New(c *conf.RPCServer, s *comet.Server) *grpc.Server {\n\tkeepParams := grpc.KeepaliveParams(keepalive.ServerParameters{\n\t\tMaxConnectionIdle:     time.Duration(c.IdleTimeout),\n\t\tMaxConnectionAgeGrace: time.Duration(c.ForceCloseWait),\n\t\tTime:                  time.Duration(c.KeepAliveInterval),\n\t\tTimeout:               time.Duration(c.KeepAliveTimeout),\n\t\tMaxConnectionAge:      time.Duration(c.MaxLifeTime),\n\t})\n\tsrv := grpc.NewServer(keepParams)\n\tpb.RegisterCometServer(srv, &server{s})\n\tlis, err := net.Listen(c.Network, c.Addr)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tgo func() {\n\t\tif err := srv.Serve(lis); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\treturn srv\n}\n\ntype server struct {\n\tsrv *comet.Server\n}\n\nvar _ pb.CometServer = &server{}\n\n// PushMsg push a message to specified sub keys.\nfunc (s *server) PushMsg(ctx context.Context, req *pb.PushMsgReq) (reply *pb.PushMsgReply, err error) {\n\tif len(req.Keys) == 0 || req.Proto == nil {\n\t\treturn nil, errors.ErrPushMsgArg\n\t}\n\tfor _, key := range req.Keys {\n\t\tbucket := s.srv.Bucket(key)\n\t\tif bucket == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif channel := bucket.Channel(key); channel != nil {\n\t\t\tif !channel.NeedPush(req.ProtoOp) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err = channel.Push(req.Proto); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\treturn &pb.PushMsgReply{}, nil\n}\n\n// Broadcast broadcast msg to all user.\nfunc (s *server) Broadcast(ctx context.Context, req *pb.BroadcastReq) (*pb.BroadcastReply, error) {\n\tif req.Proto == nil {\n\t\treturn nil, errors.ErrBroadCastArg\n\t}\n\t// TODO use broadcast queue\n\tgo func() {\n\t\tfor _, bucket := range s.srv.Buckets() {\n\t\t\tbucket.Broadcast(req.GetProto(), req.ProtoOp)\n\t\t\tif req.Speed > 0 {\n\t\t\t\tt := bucket.ChannelCount() / int(req.Speed)\n\t\t\t\ttime.Sleep(time.Duration(t) * time.Second)\n\t\t\t}\n\t\t}\n\t}()\n\treturn &pb.BroadcastReply{}, nil\n}\n\n// BroadcastRoom broadcast msg to specified room.\nfunc (s *server) BroadcastRoom(ctx context.Context, req *pb.BroadcastRoomReq) (*pb.BroadcastRoomReply, error) {\n\tif req.Proto == nil || req.RoomID == \"\" {\n\t\treturn nil, errors.ErrBroadCastRoomArg\n\t}\n\tfor _, bucket := range s.srv.Buckets() {\n\t\tbucket.BroadcastRoom(req)\n\t}\n\treturn &pb.BroadcastRoomReply{}, nil\n}\n\n// Rooms gets all the room ids for the server.\nfunc (s *server) Rooms(ctx context.Context, req *pb.RoomsReq) (*pb.RoomsReply, error) {\n\tvar (\n\t\troomIds = make(map[string]bool)\n\t)\n\tfor _, bucket := range s.srv.Buckets() {\n\t\tfor roomID := range bucket.Rooms() {\n\t\t\troomIds[roomID] = true\n\t\t}\n\t}\n\treturn &pb.RoomsReply{Rooms: roomIds}, nil\n}\n"
  },
  {
    "path": "internal/comet/operation.go",
    "content": "package comet\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/Terry-Mao/goim/api/logic\"\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/pkg/strings\"\n\tlog \"github.com/golang/glog\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/encoding/gzip\"\n)\n\n// Connect connected a connection.\nfunc (s *Server) Connect(c context.Context, p *protocol.Proto, cookie string) (mid int64, key, rid string, accepts []int32, heartbeat time.Duration, err error) {\n\treply, err := s.rpcClient.Connect(c, &logic.ConnectReq{\n\t\tServer: s.serverID,\n\t\tCookie: cookie,\n\t\tToken:  p.Body,\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\treturn reply.Mid, reply.Key, reply.RoomID, reply.Accepts, time.Duration(reply.Heartbeat), nil\n}\n\n// Disconnect disconnected a connection.\nfunc (s *Server) Disconnect(c context.Context, mid int64, key string) (err error) {\n\t_, err = s.rpcClient.Disconnect(context.Background(), &logic.DisconnectReq{\n\t\tServer: s.serverID,\n\t\tMid:    mid,\n\t\tKey:    key,\n\t})\n\treturn\n}\n\n// Heartbeat heartbeat a connection session.\nfunc (s *Server) Heartbeat(ctx context.Context, mid int64, key string) (err error) {\n\t_, err = s.rpcClient.Heartbeat(ctx, &logic.HeartbeatReq{\n\t\tServer: s.serverID,\n\t\tMid:    mid,\n\t\tKey:    key,\n\t})\n\treturn\n}\n\n// RenewOnline renew room online.\nfunc (s *Server) RenewOnline(ctx context.Context, serverID string, roomCount map[string]int32) (allRoom map[string]int32, err error) {\n\treply, err := s.rpcClient.RenewOnline(ctx, &logic.OnlineReq{\n\t\tServer:    s.serverID,\n\t\tRoomCount: roomCount,\n\t}, grpc.UseCompressor(gzip.Name))\n\tif err != nil {\n\t\treturn\n\t}\n\treturn reply.AllRoomCount, nil\n}\n\n// Receive receive a message.\nfunc (s *Server) Receive(ctx context.Context, mid int64, p *protocol.Proto) (err error) {\n\t_, err = s.rpcClient.Receive(ctx, &logic.ReceiveReq{Mid: mid, Proto: p})\n\treturn\n}\n\n// Operate operate.\nfunc (s *Server) Operate(ctx context.Context, p *protocol.Proto, ch *Channel, b *Bucket) error {\n\tswitch p.Op {\n\tcase protocol.OpChangeRoom:\n\t\tif err := b.ChangeRoom(string(p.Body), ch); err != nil {\n\t\t\tlog.Errorf(\"b.ChangeRoom(%s) error(%v)\", p.Body, err)\n\t\t}\n\t\tp.Op = protocol.OpChangeRoomReply\n\tcase protocol.OpSub:\n\t\tif ops, err := strings.SplitInt32s(string(p.Body), \",\"); err == nil {\n\t\t\tch.Watch(ops...)\n\t\t}\n\t\tp.Op = protocol.OpSubReply\n\tcase protocol.OpUnsub:\n\t\tif ops, err := strings.SplitInt32s(string(p.Body), \",\"); err == nil {\n\t\t\tch.UnWatch(ops...)\n\t\t}\n\t\tp.Op = protocol.OpUnsubReply\n\tdefault:\n\t\t// TODO ack ok&failed\n\t\tif err := s.Receive(ctx, ch.Mid, p); err != nil {\n\t\t\tlog.Errorf(\"s.Report(%d) op:%d error(%v)\", ch.Mid, p.Op, err)\n\t\t}\n\t\tp.Body = nil\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/comet/ring.go",
    "content": "package comet\n\nimport (\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/internal/comet/conf\"\n\t\"github.com/Terry-Mao/goim/internal/comet/errors\"\n\tlog \"github.com/golang/glog\"\n)\n\n// Ring ring proto buffer.\ntype Ring struct {\n\t// read\n\trp   uint64\n\tnum  uint64\n\tmask uint64\n\t// TODO split cacheline, many cpu cache line size is 64\n\t// pad [40]byte\n\t// write\n\twp   uint64\n\tdata []protocol.Proto\n}\n\n// NewRing new a ring buffer.\nfunc NewRing(num int) *Ring {\n\tr := new(Ring)\n\tr.init(uint64(num))\n\treturn r\n}\n\n// Init init ring.\nfunc (r *Ring) Init(num int) {\n\tr.init(uint64(num))\n}\n\nfunc (r *Ring) init(num uint64) {\n\t// 2^N\n\tif num&(num-1) != 0 {\n\t\tfor num&(num-1) != 0 {\n\t\t\tnum &= num - 1\n\t\t}\n\t\tnum <<= 1\n\t}\n\tr.data = make([]protocol.Proto, num)\n\tr.num = num\n\tr.mask = r.num - 1\n}\n\n// Get get a proto from ring.\nfunc (r *Ring) Get() (proto *protocol.Proto, err error) {\n\tif r.rp == r.wp {\n\t\treturn nil, errors.ErrRingEmpty\n\t}\n\tproto = &r.data[r.rp&r.mask]\n\treturn\n}\n\n// GetAdv incr read index.\nfunc (r *Ring) GetAdv() {\n\tr.rp++\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"ring rp: %d, idx: %d\", r.rp, r.rp&r.mask)\n\t}\n}\n\n// Set get a proto to write.\nfunc (r *Ring) Set() (proto *protocol.Proto, err error) {\n\tif r.wp-r.rp >= r.num {\n\t\treturn nil, errors.ErrRingFull\n\t}\n\tproto = &r.data[r.wp&r.mask]\n\treturn\n}\n\n// SetAdv incr write index.\nfunc (r *Ring) SetAdv() {\n\tr.wp++\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"ring wp: %d, idx: %d\", r.wp, r.wp&r.mask)\n\t}\n}\n\n// Reset reset ring.\nfunc (r *Ring) Reset() {\n\tr.rp = 0\n\tr.wp = 0\n\t// prevent pad compiler optimization\n\t// r.pad = [40]byte{}\n}\n"
  },
  {
    "path": "internal/comet/room.go",
    "content": "package comet\n\nimport (\n\t\"sync\"\n\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/internal/comet/errors\"\n)\n\n// Room is a room and store channel room info.\ntype Room struct {\n\tID        string\n\trLock     sync.RWMutex\n\tnext      *Channel\n\tdrop      bool\n\tOnline    int32 // dirty read is ok\n\tAllOnline int32\n}\n\n// NewRoom new a room struct, store channel room info.\nfunc NewRoom(id string) (r *Room) {\n\tr = new(Room)\n\tr.ID = id\n\tr.drop = false\n\tr.next = nil\n\tr.Online = 0\n\treturn\n}\n\n// Put put channel into the room.\nfunc (r *Room) Put(ch *Channel) (err error) {\n\tr.rLock.Lock()\n\tif !r.drop {\n\t\tif r.next != nil {\n\t\t\tr.next.Prev = ch\n\t\t}\n\t\tch.Next = r.next\n\t\tch.Prev = nil\n\t\tr.next = ch // insert to header\n\t\tr.Online++\n\t} else {\n\t\terr = errors.ErrRoomDroped\n\t}\n\tr.rLock.Unlock()\n\treturn\n}\n\n// Del delete channel from the room.\nfunc (r *Room) Del(ch *Channel) bool {\n\tr.rLock.Lock()\n\tif ch.Next != nil {\n\t\t// if not footer\n\t\tch.Next.Prev = ch.Prev\n\t}\n\tif ch.Prev != nil {\n\t\t// if not header\n\t\tch.Prev.Next = ch.Next\n\t} else {\n\t\tr.next = ch.Next\n\t}\n\tch.Next = nil\n\tch.Prev = nil\n\tr.Online--\n\tr.drop = r.Online == 0\n\tr.rLock.Unlock()\n\treturn r.drop\n}\n\n// Push push msg to the room, if chan full discard it.\nfunc (r *Room) Push(p *protocol.Proto) {\n\tr.rLock.RLock()\n\tfor ch := r.next; ch != nil; ch = ch.Next {\n\t\t_ = ch.Push(p)\n\t}\n\tr.rLock.RUnlock()\n}\n\n// Close close the room.\nfunc (r *Room) Close() {\n\tr.rLock.RLock()\n\tfor ch := r.next; ch != nil; ch = ch.Next {\n\t\tch.Close()\n\t}\n\tr.rLock.RUnlock()\n}\n\n// OnlineNum the room all online.\nfunc (r *Room) OnlineNum() int32 {\n\tif r.AllOnline > 0 {\n\t\treturn r.AllOnline\n\t}\n\treturn r.Online\n}\n"
  },
  {
    "path": "internal/comet/round.go",
    "content": "package comet\n\nimport (\n\t\"github.com/Terry-Mao/goim/internal/comet/conf\"\n\t\"github.com/Terry-Mao/goim/pkg/bytes\"\n\t\"github.com/Terry-Mao/goim/pkg/time\"\n)\n\n// RoundOptions round options.\ntype RoundOptions struct {\n\tTimer        int\n\tTimerSize    int\n\tReader       int\n\tReadBuf      int\n\tReadBufSize  int\n\tWriter       int\n\tWriteBuf     int\n\tWriteBufSize int\n}\n\n// Round used for connection round-robin get a reader/writer/timer for split big lock.\ntype Round struct {\n\treaders []bytes.Pool\n\twriters []bytes.Pool\n\ttimers  []time.Timer\n\toptions RoundOptions\n}\n\n// NewRound new a round struct.\nfunc NewRound(c *conf.Config) (r *Round) {\n\tvar i int\n\tr = &Round{\n\t\toptions: RoundOptions{\n\t\t\tReader:       c.TCP.Reader,\n\t\t\tReadBuf:      c.TCP.ReadBuf,\n\t\t\tReadBufSize:  c.TCP.ReadBufSize,\n\t\t\tWriter:       c.TCP.Writer,\n\t\t\tWriteBuf:     c.TCP.WriteBuf,\n\t\t\tWriteBufSize: c.TCP.WriteBufSize,\n\t\t\tTimer:        c.Protocol.Timer,\n\t\t\tTimerSize:    c.Protocol.TimerSize,\n\t\t}}\n\t// reader\n\tr.readers = make([]bytes.Pool, r.options.Reader)\n\tfor i = 0; i < r.options.Reader; i++ {\n\t\tr.readers[i].Init(r.options.ReadBuf, r.options.ReadBufSize)\n\t}\n\t// writer\n\tr.writers = make([]bytes.Pool, r.options.Writer)\n\tfor i = 0; i < r.options.Writer; i++ {\n\t\tr.writers[i].Init(r.options.WriteBuf, r.options.WriteBufSize)\n\t}\n\t// timer\n\tr.timers = make([]time.Timer, r.options.Timer)\n\tfor i = 0; i < r.options.Timer; i++ {\n\t\tr.timers[i].Init(r.options.TimerSize)\n\t}\n\treturn\n}\n\n// Timer get a timer.\nfunc (r *Round) Timer(rn int) *time.Timer {\n\treturn &(r.timers[rn%r.options.Timer])\n}\n\n// Reader get a reader memory buffer.\nfunc (r *Round) Reader(rn int) *bytes.Pool {\n\treturn &(r.readers[rn%r.options.Reader])\n}\n\n// Writer get a writer memory buffer pool.\nfunc (r *Round) Writer(rn int) *bytes.Pool {\n\treturn &(r.writers[rn%r.options.Writer])\n}\n"
  },
  {
    "path": "internal/comet/server.go",
    "content": "package comet\n\nimport (\n\t\"context\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/Terry-Mao/goim/api/logic\"\n\t\"github.com/Terry-Mao/goim/internal/comet/conf\"\n\tlog \"github.com/golang/glog\"\n\t\"github.com/zhenjl/cityhash\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/balancer/roundrobin\"\n\t\"google.golang.org/grpc/keepalive\"\n)\n\nconst (\n\tminServerHeartbeat = time.Minute * 10\n\tmaxServerHeartbeat = time.Minute * 30\n\t// grpc options\n\tgrpcInitialWindowSize     = 1 << 24\n\tgrpcInitialConnWindowSize = 1 << 24\n\tgrpcMaxSendMsgSize        = 1 << 24\n\tgrpcMaxCallMsgSize        = 1 << 24\n\tgrpcKeepAliveTime         = time.Second * 10\n\tgrpcKeepAliveTimeout      = time.Second * 3\n\tgrpcBackoffMaxDelay       = time.Second * 3\n)\n\nfunc newLogicClient(c *conf.RPCClient) logic.LogicClient {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.Dial))\n\tdefer cancel()\n\tconn, err := grpc.DialContext(ctx, \"discovery://default/goim.logic\",\n\t\t[]grpc.DialOption{\n\t\t\tgrpc.WithInsecure(),\n\t\t\tgrpc.WithInitialWindowSize(grpcInitialWindowSize),\n\t\t\tgrpc.WithInitialConnWindowSize(grpcInitialConnWindowSize),\n\t\t\tgrpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcMaxCallMsgSize)),\n\t\t\tgrpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcMaxSendMsgSize)),\n\t\t\tgrpc.WithBackoffMaxDelay(grpcBackoffMaxDelay),\n\t\t\tgrpc.WithKeepaliveParams(keepalive.ClientParameters{\n\t\t\t\tTime:                grpcKeepAliveTime,\n\t\t\t\tTimeout:             grpcKeepAliveTimeout,\n\t\t\t\tPermitWithoutStream: true,\n\t\t\t}),\n\t\t\tgrpc.WithBalancerName(roundrobin.Name),\n\t\t}...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn logic.NewLogicClient(conn)\n}\n\n// Server is comet server.\ntype Server struct {\n\tc         *conf.Config\n\tround     *Round    // accept round store\n\tbuckets   []*Bucket // subkey bucket\n\tbucketIdx uint32\n\n\tserverID  string\n\trpcClient logic.LogicClient\n}\n\n// NewServer returns a new Server.\nfunc NewServer(c *conf.Config) *Server {\n\ts := &Server{\n\t\tc:         c,\n\t\tround:     NewRound(c),\n\t\trpcClient: newLogicClient(c.RPCClient),\n\t}\n\t// init bucket\n\ts.buckets = make([]*Bucket, c.Bucket.Size)\n\ts.bucketIdx = uint32(c.Bucket.Size)\n\tfor i := 0; i < c.Bucket.Size; i++ {\n\t\ts.buckets[i] = NewBucket(c.Bucket)\n\t}\n\ts.serverID = c.Env.Host\n\tgo s.onlineproc()\n\treturn s\n}\n\n// Buckets return all buckets.\nfunc (s *Server) Buckets() []*Bucket {\n\treturn s.buckets\n}\n\n// Bucket get the bucket by subkey.\nfunc (s *Server) Bucket(subKey string) *Bucket {\n\tidx := cityhash.CityHash32([]byte(subKey), uint32(len(subKey))) % s.bucketIdx\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"%s hit channel bucket index: %d use cityhash\", subKey, idx)\n\t}\n\treturn s.buckets[idx]\n}\n\n// RandServerHearbeat rand server heartbeat.\nfunc (s *Server) RandServerHearbeat() time.Duration {\n\treturn (minServerHeartbeat + time.Duration(rand.Int63n(int64(maxServerHeartbeat-minServerHeartbeat))))\n}\n\n// Close close the server.\nfunc (s *Server) Close() (err error) {\n\treturn\n}\n\nfunc (s *Server) onlineproc() {\n\tfor {\n\t\tvar (\n\t\t\tallRoomsCount map[string]int32\n\t\t\terr           error\n\t\t)\n\t\troomCount := make(map[string]int32)\n\t\tfor _, bucket := range s.buckets {\n\t\t\tfor roomID, count := range bucket.RoomsCount() {\n\t\t\t\troomCount[roomID] += count\n\t\t\t}\n\t\t}\n\t\tif allRoomsCount, err = s.RenewOnline(context.Background(), s.serverID, roomCount); err != nil {\n\t\t\ttime.Sleep(time.Second)\n\t\t\tcontinue\n\t\t}\n\t\tfor _, bucket := range s.buckets {\n\t\t\tbucket.UpRoomsCount(allRoomsCount)\n\t\t}\n\t\ttime.Sleep(time.Second * 10)\n\t}\n}\n"
  },
  {
    "path": "internal/comet/server_tcp.go",
    "content": "package comet\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/internal/comet/conf\"\n\t\"github.com/Terry-Mao/goim/pkg/bufio\"\n\t\"github.com/Terry-Mao/goim/pkg/bytes\"\n\txtime \"github.com/Terry-Mao/goim/pkg/time\"\n\tlog \"github.com/golang/glog\"\n)\n\nconst (\n\tmaxInt = 1<<31 - 1\n)\n\n// InitTCP listen all tcp.bind and start accept connections.\nfunc InitTCP(server *Server, addrs []string, accept int) (err error) {\n\tvar (\n\t\tbind     string\n\t\tlistener *net.TCPListener\n\t\taddr     *net.TCPAddr\n\t)\n\tfor _, bind = range addrs {\n\t\tif addr, err = net.ResolveTCPAddr(\"tcp\", bind); err != nil {\n\t\t\tlog.Errorf(\"net.ResolveTCPAddr(tcp, %s) error(%v)\", bind, err)\n\t\t\treturn\n\t\t}\n\t\tif listener, err = net.ListenTCP(\"tcp\", addr); err != nil {\n\t\t\tlog.Errorf(\"net.ListenTCP(tcp, %s) error(%v)\", bind, err)\n\t\t\treturn\n\t\t}\n\t\tlog.Infof(\"start tcp listen: %s\", bind)\n\t\t// split N core accept\n\t\tfor i := 0; i < accept; i++ {\n\t\t\tgo acceptTCP(server, listener)\n\t\t}\n\t}\n\treturn\n}\n\n// Accept accepts connections on the listener and serves requests\n// for each incoming connection.  Accept blocks; the caller typically\n// invokes it in a go statement.\nfunc acceptTCP(server *Server, lis *net.TCPListener) {\n\tvar (\n\t\tconn *net.TCPConn\n\t\terr  error\n\t\tr    int\n\t)\n\tfor {\n\t\tif conn, err = lis.AcceptTCP(); err != nil {\n\t\t\t// if listener close then return\n\t\t\tlog.Errorf(\"listener.Accept(\\\"%s\\\") error(%v)\", lis.Addr().String(), err)\n\t\t\treturn\n\t\t}\n\t\tif err = conn.SetKeepAlive(server.c.TCP.KeepAlive); err != nil {\n\t\t\tlog.Errorf(\"conn.SetKeepAlive() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t\tif err = conn.SetReadBuffer(server.c.TCP.Rcvbuf); err != nil {\n\t\t\tlog.Errorf(\"conn.SetReadBuffer() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t\tif err = conn.SetWriteBuffer(server.c.TCP.Sndbuf); err != nil {\n\t\t\tlog.Errorf(\"conn.SetWriteBuffer() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t\tgo serveTCP(server, conn, r)\n\t\tif r++; r == maxInt {\n\t\t\tr = 0\n\t\t}\n\t}\n}\n\nfunc serveTCP(s *Server, conn *net.TCPConn, r int) {\n\tvar (\n\t\t// timer\n\t\ttr = s.round.Timer(r)\n\t\trp = s.round.Reader(r)\n\t\twp = s.round.Writer(r)\n\t\t// ip addr\n\t\tlAddr = conn.LocalAddr().String()\n\t\trAddr = conn.RemoteAddr().String()\n\t)\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"start tcp serve \\\"%s\\\" with \\\"%s\\\"\", lAddr, rAddr)\n\t}\n\ts.ServeTCP(conn, rp, wp, tr)\n}\n\n// ServeTCP serve a tcp connection.\nfunc (s *Server) ServeTCP(conn *net.TCPConn, rp, wp *bytes.Pool, tr *xtime.Timer) {\n\tvar (\n\t\terr     error\n\t\trid     string\n\t\taccepts []int32\n\t\thb      time.Duration\n\t\twhite   bool\n\t\tp       *protocol.Proto\n\t\tb       *Bucket\n\t\ttrd     *xtime.TimerData\n\t\tlastHb  = time.Now()\n\t\trb      = rp.Get()\n\t\twb      = wp.Get()\n\t\tch      = NewChannel(s.c.Protocol.CliProto, s.c.Protocol.SvrProto)\n\t\trr      = &ch.Reader\n\t\twr      = &ch.Writer\n\t)\n\tch.Reader.ResetBuffer(conn, rb.Bytes())\n\tch.Writer.ResetBuffer(conn, wb.Bytes())\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\t// handshake\n\tstep := 0\n\ttrd = tr.Add(time.Duration(s.c.Protocol.HandshakeTimeout), func() {\n\t\tconn.Close()\n\t\tlog.Errorf(\"key: %s remoteIP: %s step: %d tcp handshake timeout\", ch.Key, conn.RemoteAddr().String(), step)\n\t})\n\tch.IP, _, _ = net.SplitHostPort(conn.RemoteAddr().String())\n\t// must not setadv, only used in auth\n\tstep = 1\n\tif p, err = ch.CliProto.Set(); err == nil {\n\t\tif ch.Mid, ch.Key, rid, accepts, hb, err = s.authTCP(ctx, rr, wr, p); err == nil {\n\t\t\tch.Watch(accepts...)\n\t\t\tb = s.Bucket(ch.Key)\n\t\t\terr = b.Put(rid, ch)\n\t\t\tif conf.Conf.Debug {\n\t\t\t\tlog.Infof(\"tcp connnected key:%s mid:%d proto:%+v\", ch.Key, ch.Mid, p)\n\t\t\t}\n\t\t}\n\t}\n\tstep = 2\n\tif err != nil {\n\t\tconn.Close()\n\t\trp.Put(rb)\n\t\twp.Put(wb)\n\t\ttr.Del(trd)\n\t\tlog.Errorf(\"key: %s handshake failed error(%v)\", ch.Key, err)\n\t\treturn\n\t}\n\ttrd.Key = ch.Key\n\ttr.Set(trd, hb)\n\twhite = whitelist.Contains(ch.Mid)\n\tif white {\n\t\twhitelist.Printf(\"key: %s[%s] auth\\n\", ch.Key, rid)\n\t}\n\tstep = 3\n\t// hanshake ok start dispatch goroutine\n\tgo s.dispatchTCP(conn, wr, wp, wb, ch)\n\tserverHeartbeat := s.RandServerHearbeat()\n\tfor {\n\t\tif p, err = ch.CliProto.Set(); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s start read proto\\n\", ch.Key)\n\t\t}\n\t\tif err = p.ReadTCP(rr); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s read proto:%v\\n\", ch.Key, p)\n\t\t}\n\t\tif p.Op == protocol.OpHeartbeat {\n\t\t\ttr.Set(trd, hb)\n\t\t\tp.Op = protocol.OpHeartbeatReply\n\t\t\tp.Body = nil\n\t\t\t// NOTE: send server heartbeat for a long time\n\t\t\tif now := time.Now(); now.Sub(lastHb) > serverHeartbeat {\n\t\t\t\tif err1 := s.Heartbeat(ctx, ch.Mid, ch.Key); err1 == nil {\n\t\t\t\t\tlastHb = now\n\t\t\t\t}\n\t\t\t}\n\t\t\tif conf.Conf.Debug {\n\t\t\t\tlog.Infof(\"tcp heartbeat receive key:%s, mid:%d\", ch.Key, ch.Mid)\n\t\t\t}\n\t\t\tstep++\n\t\t} else {\n\t\t\tif err = s.Operate(ctx, p, ch, b); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s process proto:%v\\n\", ch.Key, p)\n\t\t}\n\t\tch.CliProto.SetAdv()\n\t\tch.Signal()\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s signal\\n\", ch.Key)\n\t\t}\n\t}\n\tif white {\n\t\twhitelist.Printf(\"key: %s server tcp error(%v)\\n\", ch.Key, err)\n\t}\n\tif err != nil && err != io.EOF && !strings.Contains(err.Error(), \"closed\") {\n\t\tlog.Errorf(\"key: %s server tcp failed error(%v)\", ch.Key, err)\n\t}\n\tb.Del(ch)\n\ttr.Del(trd)\n\trp.Put(rb)\n\tconn.Close()\n\tch.Close()\n\tif err = s.Disconnect(ctx, ch.Mid, ch.Key); err != nil {\n\t\tlog.Errorf(\"key: %s mid: %d operator do disconnect error(%v)\", ch.Key, ch.Mid, err)\n\t}\n\tif white {\n\t\twhitelist.Printf(\"key: %s mid: %d disconnect error(%v)\\n\", ch.Key, ch.Mid, err)\n\t}\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"tcp disconnected key: %s mid: %d\", ch.Key, ch.Mid)\n\t}\n}\n\n// dispatch accepts connections on the listener and serves requests\n// for each incoming connection.  dispatch blocks; the caller typically\n// invokes it in a go statement.\nfunc (s *Server) dispatchTCP(conn *net.TCPConn, wr *bufio.Writer, wp *bytes.Pool, wb *bytes.Buffer, ch *Channel) {\n\tvar (\n\t\terr    error\n\t\tfinish bool\n\t\tonline int32\n\t\twhite  = whitelist.Contains(ch.Mid)\n\t)\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"key: %s start dispatch tcp goroutine\", ch.Key)\n\t}\n\tfor {\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s wait proto ready\\n\", ch.Key)\n\t\t}\n\t\tvar p = ch.Ready()\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s proto ready\\n\", ch.Key)\n\t\t}\n\t\tif conf.Conf.Debug {\n\t\t\tlog.Infof(\"key:%s dispatch msg:%v\", ch.Key, *p)\n\t\t}\n\t\tswitch p {\n\t\tcase protocol.ProtoFinish:\n\t\t\tif white {\n\t\t\t\twhitelist.Printf(\"key: %s receive proto finish\\n\", ch.Key)\n\t\t\t}\n\t\t\tif conf.Conf.Debug {\n\t\t\t\tlog.Infof(\"key: %s wakeup exit dispatch goroutine\", ch.Key)\n\t\t\t}\n\t\t\tfinish = true\n\t\t\tgoto failed\n\t\tcase protocol.ProtoReady:\n\t\t\t// fetch message from svrbox(client send)\n\t\t\tfor {\n\t\t\t\tif p, err = ch.CliProto.Get(); err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif white {\n\t\t\t\t\twhitelist.Printf(\"key: %s start write client proto%v\\n\", ch.Key, p)\n\t\t\t\t}\n\t\t\t\tif p.Op == protocol.OpHeartbeatReply {\n\t\t\t\t\tif ch.Room != nil {\n\t\t\t\t\t\tonline = ch.Room.OnlineNum()\n\t\t\t\t\t}\n\t\t\t\t\tif err = p.WriteTCPHeart(wr, online); err != nil {\n\t\t\t\t\t\tgoto failed\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif err = p.WriteTCP(wr); err != nil {\n\t\t\t\t\t\tgoto failed\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif white {\n\t\t\t\t\twhitelist.Printf(\"key: %s write client proto%v\\n\", ch.Key, p)\n\t\t\t\t}\n\t\t\t\tp.Body = nil // avoid memory leak\n\t\t\t\tch.CliProto.GetAdv()\n\t\t\t}\n\t\tdefault:\n\t\t\tif white {\n\t\t\t\twhitelist.Printf(\"key: %s start write server proto%v\\n\", ch.Key, p)\n\t\t\t}\n\t\t\t// server send\n\t\t\tif err = p.WriteTCP(wr); err != nil {\n\t\t\t\tgoto failed\n\t\t\t}\n\t\t\tif white {\n\t\t\t\twhitelist.Printf(\"key: %s write server proto%v\\n\", ch.Key, p)\n\t\t\t}\n\t\t\tif conf.Conf.Debug {\n\t\t\t\tlog.Infof(\"tcp sent a message key:%s mid:%d proto:%+v\", ch.Key, ch.Mid, p)\n\t\t\t}\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s start flush \\n\", ch.Key)\n\t\t}\n\t\t// only hungry flush response\n\t\tif err = wr.Flush(); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s flush\\n\", ch.Key)\n\t\t}\n\t}\nfailed:\n\tif white {\n\t\twhitelist.Printf(\"key: %s dispatch tcp error(%v)\\n\", ch.Key, err)\n\t}\n\tif err != nil {\n\t\tlog.Errorf(\"key: %s dispatch tcp error(%v)\", ch.Key, err)\n\t}\n\tconn.Close()\n\twp.Put(wb)\n\t// must ensure all channel message discard, for reader won't blocking Signal\n\tfor !finish {\n\t\tfinish = (ch.Ready() == protocol.ProtoFinish)\n\t}\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"key: %s dispatch goroutine exit\", ch.Key)\n\t}\n}\n\n// auth for goim handshake with client, use rsa & aes.\nfunc (s *Server) authTCP(ctx context.Context, rr *bufio.Reader, wr *bufio.Writer, p *protocol.Proto) (mid int64, key, rid string, accepts []int32, hb time.Duration, err error) {\n\tfor {\n\t\tif err = p.ReadTCP(rr); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif p.Op == protocol.OpAuth {\n\t\t\tbreak\n\t\t} else {\n\t\t\tlog.Errorf(\"tcp request operation(%d) not auth\", p.Op)\n\t\t}\n\t}\n\tif mid, key, rid, accepts, hb, err = s.Connect(ctx, p, \"\"); err != nil {\n\t\tlog.Errorf(\"authTCP.Connect(key:%v).err(%v)\", key, err)\n\t\treturn\n\t}\n\tp.Op = protocol.OpAuthReply\n\tp.Body = nil\n\tif err = p.WriteTCP(wr); err != nil {\n\t\tlog.Errorf(\"authTCP.WriteTCP(key:%v).err(%v)\", key, err)\n\t\treturn\n\t}\n\terr = wr.Flush()\n\treturn\n}\n"
  },
  {
    "path": "internal/comet/server_websocket.go",
    "content": "package comet\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/internal/comet/conf\"\n\t\"github.com/Terry-Mao/goim/pkg/bytes\"\n\txtime \"github.com/Terry-Mao/goim/pkg/time\"\n\t\"github.com/Terry-Mao/goim/pkg/websocket\"\n\tlog \"github.com/golang/glog\"\n)\n\n// InitWebsocket listen all tcp.bind and start accept connections.\nfunc InitWebsocket(server *Server, addrs []string, accept int) (err error) {\n\tvar (\n\t\tbind     string\n\t\tlistener *net.TCPListener\n\t\taddr     *net.TCPAddr\n\t)\n\tfor _, bind = range addrs {\n\t\tif addr, err = net.ResolveTCPAddr(\"tcp\", bind); err != nil {\n\t\t\tlog.Errorf(\"net.ResolveTCPAddr(tcp, %s) error(%v)\", bind, err)\n\t\t\treturn\n\t\t}\n\t\tif listener, err = net.ListenTCP(\"tcp\", addr); err != nil {\n\t\t\tlog.Errorf(\"net.ListenTCP(tcp, %s) error(%v)\", bind, err)\n\t\t\treturn\n\t\t}\n\t\tlog.Infof(\"start ws listen: %s\", bind)\n\t\t// split N core accept\n\t\tfor i := 0; i < accept; i++ {\n\t\t\tgo acceptWebsocket(server, listener)\n\t\t}\n\t}\n\treturn\n}\n\n// InitWebsocketWithTLS init websocket with tls.\nfunc InitWebsocketWithTLS(server *Server, addrs []string, certFile, privateFile string, accept int) (err error) {\n\tvar (\n\t\tbind     string\n\t\tlistener net.Listener\n\t\tcert     tls.Certificate\n\t\tcerts    []tls.Certificate\n\t)\n\tcertFiles := strings.Split(certFile, \",\")\n\tprivateFiles := strings.Split(privateFile, \",\")\n\tfor i := range certFiles {\n\t\tcert, err = tls.LoadX509KeyPair(certFiles[i], privateFiles[i])\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Error loading certificate. error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t\tcerts = append(certs, cert)\n\t}\n\ttlsCfg := &tls.Config{Certificates: certs}\n\ttlsCfg.BuildNameToCertificate()\n\tfor _, bind = range addrs {\n\t\tif listener, err = tls.Listen(\"tcp\", bind, tlsCfg); err != nil {\n\t\t\tlog.Errorf(\"net.ListenTCP(tcp, %s) error(%v)\", bind, err)\n\t\t\treturn\n\t\t}\n\t\tlog.Infof(\"start wss listen: %s\", bind)\n\t\t// split N core accept\n\t\tfor i := 0; i < accept; i++ {\n\t\t\tgo acceptWebsocketWithTLS(server, listener)\n\t\t}\n\t}\n\treturn\n}\n\n// Accept accepts connections on the listener and serves requests\n// for each incoming connection.  Accept blocks; the caller typically\n// invokes it in a go statement.\nfunc acceptWebsocket(server *Server, lis *net.TCPListener) {\n\tvar (\n\t\tconn *net.TCPConn\n\t\terr  error\n\t\tr    int\n\t)\n\tfor {\n\t\tif conn, err = lis.AcceptTCP(); err != nil {\n\t\t\t// if listener close then return\n\t\t\tlog.Errorf(\"listener.Accept(%s) error(%v)\", lis.Addr().String(), err)\n\t\t\treturn\n\t\t}\n\t\tif err = conn.SetKeepAlive(server.c.TCP.KeepAlive); err != nil {\n\t\t\tlog.Errorf(\"conn.SetKeepAlive() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t\tif err = conn.SetReadBuffer(server.c.TCP.Rcvbuf); err != nil {\n\t\t\tlog.Errorf(\"conn.SetReadBuffer() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t\tif err = conn.SetWriteBuffer(server.c.TCP.Sndbuf); err != nil {\n\t\t\tlog.Errorf(\"conn.SetWriteBuffer() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t\tgo serveWebsocket(server, conn, r)\n\t\tif r++; r == maxInt {\n\t\t\tr = 0\n\t\t}\n\t}\n}\n\n// Accept accepts connections on the listener and serves requests\n// for each incoming connection.  Accept blocks; the caller typically\n// invokes it in a go statement.\nfunc acceptWebsocketWithTLS(server *Server, lis net.Listener) {\n\tvar (\n\t\tconn net.Conn\n\t\terr  error\n\t\tr    int\n\t)\n\tfor {\n\t\tif conn, err = lis.Accept(); err != nil {\n\t\t\t// if listener close then return\n\t\t\tlog.Errorf(\"listener.Accept(\\\"%s\\\") error(%v)\", lis.Addr().String(), err)\n\t\t\treturn\n\t\t}\n\t\tgo serveWebsocket(server, conn, r)\n\t\tif r++; r == maxInt {\n\t\t\tr = 0\n\t\t}\n\t}\n}\n\nfunc serveWebsocket(s *Server, conn net.Conn, r int) {\n\tvar (\n\t\t// timer\n\t\ttr = s.round.Timer(r)\n\t\trp = s.round.Reader(r)\n\t\twp = s.round.Writer(r)\n\t)\n\tif conf.Conf.Debug {\n\t\t// ip addr\n\t\tlAddr := conn.LocalAddr().String()\n\t\trAddr := conn.RemoteAddr().String()\n\t\tlog.Infof(\"start tcp serve \\\"%s\\\" with \\\"%s\\\"\", lAddr, rAddr)\n\t}\n\ts.ServeWebsocket(conn, rp, wp, tr)\n}\n\n// ServeWebsocket serve a websocket connection.\nfunc (s *Server) ServeWebsocket(conn net.Conn, rp, wp *bytes.Pool, tr *xtime.Timer) {\n\tvar (\n\t\terr     error\n\t\trid     string\n\t\taccepts []int32\n\t\thb      time.Duration\n\t\twhite   bool\n\t\tp       *protocol.Proto\n\t\tb       *Bucket\n\t\ttrd     *xtime.TimerData\n\t\tlastHB  = time.Now()\n\t\trb      = rp.Get()\n\t\tch      = NewChannel(s.c.Protocol.CliProto, s.c.Protocol.SvrProto)\n\t\trr      = &ch.Reader\n\t\twr      = &ch.Writer\n\t\tws      *websocket.Conn // websocket\n\t\treq     *websocket.Request\n\t)\n\t// reader\n\tch.Reader.ResetBuffer(conn, rb.Bytes())\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\t// handshake\n\tstep := 0\n\ttrd = tr.Add(time.Duration(s.c.Protocol.HandshakeTimeout), func() {\n\t\t// NOTE: fix close block for tls\n\t\t_ = conn.SetDeadline(time.Now().Add(time.Millisecond * 100))\n\t\t_ = conn.Close()\n\t\tlog.Errorf(\"key: %s remoteIP: %s step: %d ws handshake timeout\", ch.Key, conn.RemoteAddr().String(), step)\n\t})\n\t// websocket\n\tch.IP, _, _ = net.SplitHostPort(conn.RemoteAddr().String())\n\tstep = 1\n\tif req, err = websocket.ReadRequest(rr); err != nil || req.RequestURI != \"/sub\" {\n\t\tconn.Close()\n\t\ttr.Del(trd)\n\t\trp.Put(rb)\n\t\tif err != io.EOF {\n\t\t\tlog.Errorf(\"http.ReadRequest(rr) error(%v)\", err)\n\t\t}\n\t\treturn\n\t}\n\t// writer\n\twb := wp.Get()\n\tch.Writer.ResetBuffer(conn, wb.Bytes())\n\tstep = 2\n\tif ws, err = websocket.Upgrade(conn, rr, wr, req); err != nil {\n\t\tconn.Close()\n\t\ttr.Del(trd)\n\t\trp.Put(rb)\n\t\twp.Put(wb)\n\t\tif err != io.EOF {\n\t\t\tlog.Errorf(\"websocket.NewServerConn error(%v)\", err)\n\t\t}\n\t\treturn\n\t}\n\t// must not setadv, only used in auth\n\tstep = 3\n\tif p, err = ch.CliProto.Set(); err == nil {\n\t\tif ch.Mid, ch.Key, rid, accepts, hb, err = s.authWebsocket(ctx, ws, p, req.Header.Get(\"Cookie\")); err == nil {\n\t\t\tch.Watch(accepts...)\n\t\t\tb = s.Bucket(ch.Key)\n\t\t\terr = b.Put(rid, ch)\n\t\t\tif conf.Conf.Debug {\n\t\t\t\tlog.Infof(\"websocket connected key:%s mid:%d proto:%+v\", ch.Key, ch.Mid, p)\n\t\t\t}\n\t\t}\n\t}\n\tstep = 4\n\tif err != nil {\n\t\tws.Close()\n\t\trp.Put(rb)\n\t\twp.Put(wb)\n\t\ttr.Del(trd)\n\t\tif err != io.EOF && err != websocket.ErrMessageClose {\n\t\t\tlog.Errorf(\"key: %s remoteIP: %s step: %d ws handshake failed error(%v)\", ch.Key, conn.RemoteAddr().String(), step, err)\n\t\t}\n\t\treturn\n\t}\n\ttrd.Key = ch.Key\n\ttr.Set(trd, hb)\n\twhite = whitelist.Contains(ch.Mid)\n\tif white {\n\t\twhitelist.Printf(\"key: %s[%s] auth\\n\", ch.Key, rid)\n\t}\n\t// handshake ok start dispatch goroutine\n\tstep = 5\n\tgo s.dispatchWebsocket(ws, wp, wb, ch)\n\tserverHeartbeat := s.RandServerHearbeat()\n\tfor {\n\t\tif p, err = ch.CliProto.Set(); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s start read proto\\n\", ch.Key)\n\t\t}\n\t\tif err = p.ReadWebsocket(ws); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s read proto:%v\\n\", ch.Key, p)\n\t\t}\n\t\tif p.Op == protocol.OpHeartbeat {\n\t\t\ttr.Set(trd, hb)\n\t\t\tp.Op = protocol.OpHeartbeatReply\n\t\t\tp.Body = nil\n\t\t\t// NOTE: send server heartbeat for a long time\n\t\t\tif now := time.Now(); now.Sub(lastHB) > serverHeartbeat {\n\t\t\t\tif err1 := s.Heartbeat(ctx, ch.Mid, ch.Key); err1 == nil {\n\t\t\t\t\tlastHB = now\n\t\t\t\t}\n\t\t\t}\n\t\t\tif conf.Conf.Debug {\n\t\t\t\tlog.Infof(\"websocket heartbeat receive key:%s, mid:%d\", ch.Key, ch.Mid)\n\t\t\t}\n\t\t\tstep++\n\t\t} else {\n\t\t\tif err = s.Operate(ctx, p, ch, b); err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s process proto:%v\\n\", ch.Key, p)\n\t\t}\n\t\tch.CliProto.SetAdv()\n\t\tch.Signal()\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s signal\\n\", ch.Key)\n\t\t}\n\t}\n\tif white {\n\t\twhitelist.Printf(\"key: %s server tcp error(%v)\\n\", ch.Key, err)\n\t}\n\tif err != nil && err != io.EOF && err != websocket.ErrMessageClose && !strings.Contains(err.Error(), \"closed\") {\n\t\tlog.Errorf(\"key: %s server ws failed error(%v)\", ch.Key, err)\n\t}\n\tb.Del(ch)\n\ttr.Del(trd)\n\tws.Close()\n\tch.Close()\n\trp.Put(rb)\n\tif err = s.Disconnect(ctx, ch.Mid, ch.Key); err != nil {\n\t\tlog.Errorf(\"key: %s operator do disconnect error(%v)\", ch.Key, err)\n\t}\n\tif white {\n\t\twhitelist.Printf(\"key: %s disconnect error(%v)\\n\", ch.Key, err)\n\t}\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"websocket disconnected key: %s mid:%d\", ch.Key, ch.Mid)\n\t}\n}\n\n// dispatch accepts connections on the listener and serves requests\n// for each incoming connection.  dispatch blocks; the caller typically\n// invokes it in a go statement.\nfunc (s *Server) dispatchWebsocket(ws *websocket.Conn, wp *bytes.Pool, wb *bytes.Buffer, ch *Channel) {\n\tvar (\n\t\terr    error\n\t\tfinish bool\n\t\tonline int32\n\t\twhite  = whitelist.Contains(ch.Mid)\n\t)\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"key: %s start dispatch tcp goroutine\", ch.Key)\n\t}\n\tfor {\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s wait proto ready\\n\", ch.Key)\n\t\t}\n\t\tvar p = ch.Ready()\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s proto ready\\n\", ch.Key)\n\t\t}\n\t\tif conf.Conf.Debug {\n\t\t\tlog.Infof(\"key:%s dispatch msg:%s\", ch.Key, p.Body)\n\t\t}\n\t\tswitch p {\n\t\tcase protocol.ProtoFinish:\n\t\t\tif white {\n\t\t\t\twhitelist.Printf(\"key: %s receive proto finish\\n\", ch.Key)\n\t\t\t}\n\t\t\tif conf.Conf.Debug {\n\t\t\t\tlog.Infof(\"key: %s wakeup exit dispatch goroutine\", ch.Key)\n\t\t\t}\n\t\t\tfinish = true\n\t\t\tgoto failed\n\t\tcase protocol.ProtoReady:\n\t\t\t// fetch message from svrbox(client send)\n\t\t\tfor {\n\t\t\t\tif p, err = ch.CliProto.Get(); err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif white {\n\t\t\t\t\twhitelist.Printf(\"key: %s start write client proto%v\\n\", ch.Key, p)\n\t\t\t\t}\n\t\t\t\tif p.Op == protocol.OpHeartbeatReply {\n\t\t\t\t\tif ch.Room != nil {\n\t\t\t\t\t\tonline = ch.Room.OnlineNum()\n\t\t\t\t\t}\n\t\t\t\t\tif err = p.WriteWebsocketHeart(ws, online); err != nil {\n\t\t\t\t\t\tgoto failed\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif err = p.WriteWebsocket(ws); err != nil {\n\t\t\t\t\t\tgoto failed\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif white {\n\t\t\t\t\twhitelist.Printf(\"key: %s write client proto%v\\n\", ch.Key, p)\n\t\t\t\t}\n\t\t\t\tp.Body = nil // avoid memory leak\n\t\t\t\tch.CliProto.GetAdv()\n\t\t\t}\n\t\tdefault:\n\t\t\tif white {\n\t\t\t\twhitelist.Printf(\"key: %s start write server proto%v\\n\", ch.Key, p)\n\t\t\t}\n\t\t\tif err = p.WriteWebsocket(ws); err != nil {\n\t\t\t\tgoto failed\n\t\t\t}\n\t\t\tif white {\n\t\t\t\twhitelist.Printf(\"key: %s write server proto%v\\n\", ch.Key, p)\n\t\t\t}\n\t\t\tif conf.Conf.Debug {\n\t\t\t\tlog.Infof(\"websocket sent a message key:%s mid:%d proto:%+v\", ch.Key, ch.Mid, p)\n\t\t\t}\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s start flush \\n\", ch.Key)\n\t\t}\n\t\t// only hungry flush response\n\t\tif err = ws.Flush(); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif white {\n\t\t\twhitelist.Printf(\"key: %s flush\\n\", ch.Key)\n\t\t}\n\t}\nfailed:\n\tif white {\n\t\twhitelist.Printf(\"key: %s dispatch tcp error(%v)\\n\", ch.Key, err)\n\t}\n\tif err != nil && err != io.EOF && err != websocket.ErrMessageClose {\n\t\tlog.Errorf(\"key: %s dispatch ws error(%v)\", ch.Key, err)\n\t}\n\tws.Close()\n\twp.Put(wb)\n\t// must ensure all channel message discard, for reader won't blocking Signal\n\tfor !finish {\n\t\tfinish = (ch.Ready() == protocol.ProtoFinish)\n\t}\n\tif conf.Conf.Debug {\n\t\tlog.Infof(\"key: %s dispatch goroutine exit\", ch.Key)\n\t}\n}\n\n// auth for goim handshake with client, use rsa & aes.\nfunc (s *Server) authWebsocket(ctx context.Context, ws *websocket.Conn, p *protocol.Proto, cookie string) (mid int64, key, rid string, accepts []int32, hb time.Duration, err error) {\n\tfor {\n\t\tif err = p.ReadWebsocket(ws); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif p.Op == protocol.OpAuth {\n\t\t\tbreak\n\t\t} else {\n\t\t\tlog.Errorf(\"ws request operation(%d) not auth\", p.Op)\n\t\t}\n\t}\n\tif mid, key, rid, accepts, hb, err = s.Connect(ctx, p, cookie); err != nil {\n\t\treturn\n\t}\n\tp.Op = protocol.OpAuthReply\n\tp.Body = nil\n\tif err = p.WriteWebsocket(ws); err != nil {\n\t\treturn\n\t}\n\terr = ws.Flush()\n\treturn\n}\n"
  },
  {
    "path": "internal/comet/whitelist.go",
    "content": "package comet\n\nimport (\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/Terry-Mao/goim/internal/comet/conf\"\n)\n\nvar whitelist *Whitelist\n\n// Whitelist .\ntype Whitelist struct {\n\tlog  *log.Logger\n\tlist map[int64]struct{} // whitelist for debug\n}\n\n// InitWhitelist a whitelist struct.\nfunc InitWhitelist(c *conf.Whitelist) (err error) {\n\tvar (\n\t\tmid int64\n\t\tf   *os.File\n\t)\n\tif f, err = os.OpenFile(c.WhiteLog, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644); err == nil {\n\t\twhitelist = new(Whitelist)\n\t\twhitelist.log = log.New(f, \"\", log.LstdFlags)\n\t\twhitelist.list = make(map[int64]struct{})\n\t\tfor _, mid = range c.Whitelist {\n\t\t\twhitelist.list[mid] = struct{}{}\n\t\t}\n\t}\n\treturn\n}\n\n// Contains whitelist contains a mid or not.\nfunc (w *Whitelist) Contains(mid int64) (ok bool) {\n\tif mid > 0 {\n\t\t_, ok = w.list[mid]\n\t}\n\treturn\n}\n\n// Printf calls l.Output to print to the logger.\nfunc (w *Whitelist) Printf(format string, v ...interface{}) {\n\tw.log.Printf(format, v...)\n}\n"
  },
  {
    "path": "internal/job/comet.go",
    "content": "package job\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/Terry-Mao/goim/api/comet\"\n\t\"github.com/Terry-Mao/goim/internal/job/conf\"\n\t\"github.com/bilibili/discovery/naming\"\n\n\tlog \"github.com/golang/glog\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/keepalive\"\n)\n\nvar (\n\t// grpc options\n\tgrpcKeepAliveTime    = time.Duration(10) * time.Second\n\tgrpcKeepAliveTimeout = time.Duration(3) * time.Second\n\tgrpcBackoffMaxDelay  = time.Duration(3) * time.Second\n\tgrpcMaxSendMsgSize   = 1 << 24\n\tgrpcMaxCallMsgSize   = 1 << 24\n)\n\nconst (\n\t// grpc options\n\tgrpcInitialWindowSize     = 1 << 24\n\tgrpcInitialConnWindowSize = 1 << 24\n)\n\nfunc newCometClient(addr string) (comet.CometClient, error) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second))\n\tdefer cancel()\n\tconn, err := grpc.DialContext(ctx, addr,\n\t\t[]grpc.DialOption{\n\t\t\tgrpc.WithInsecure(),\n\t\t\tgrpc.WithInitialWindowSize(grpcInitialWindowSize),\n\t\t\tgrpc.WithInitialConnWindowSize(grpcInitialConnWindowSize),\n\t\t\tgrpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcMaxCallMsgSize)),\n\t\t\tgrpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcMaxSendMsgSize)),\n\t\t\tgrpc.WithBackoffMaxDelay(grpcBackoffMaxDelay),\n\t\t\tgrpc.WithKeepaliveParams(keepalive.ClientParameters{\n\t\t\t\tTime:                grpcKeepAliveTime,\n\t\t\t\tTimeout:             grpcKeepAliveTimeout,\n\t\t\t\tPermitWithoutStream: true,\n\t\t\t}),\n\t\t}...,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn comet.NewCometClient(conn), err\n}\n\n// Comet is a comet.\ntype Comet struct {\n\tserverID      string\n\tclient        comet.CometClient\n\tpushChan      []chan *comet.PushMsgReq\n\troomChan      []chan *comet.BroadcastRoomReq\n\tbroadcastChan chan *comet.BroadcastReq\n\tpushChanNum   uint64\n\troomChanNum   uint64\n\troutineSize   uint64\n\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\n// NewComet new a comet.\nfunc NewComet(in *naming.Instance, c *conf.Comet) (*Comet, error) {\n\tcmt := &Comet{\n\t\tserverID:      in.Hostname,\n\t\tpushChan:      make([]chan *comet.PushMsgReq, c.RoutineSize),\n\t\troomChan:      make([]chan *comet.BroadcastRoomReq, c.RoutineSize),\n\t\tbroadcastChan: make(chan *comet.BroadcastReq, c.RoutineSize),\n\t\troutineSize:   uint64(c.RoutineSize),\n\t}\n\tvar grpcAddr string\n\tfor _, addrs := range in.Addrs {\n\t\tu, err := url.Parse(addrs)\n\t\tif err == nil && u.Scheme == \"grpc\" {\n\t\t\tgrpcAddr = u.Host\n\t\t}\n\t}\n\tif grpcAddr == \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid grpc address:%v\", in.Addrs)\n\t}\n\tvar err error\n\tif cmt.client, err = newCometClient(grpcAddr); err != nil {\n\t\treturn nil, err\n\t}\n\tcmt.ctx, cmt.cancel = context.WithCancel(context.Background())\n\n\tfor i := 0; i < c.RoutineSize; i++ {\n\t\tcmt.pushChan[i] = make(chan *comet.PushMsgReq, c.RoutineChan)\n\t\tcmt.roomChan[i] = make(chan *comet.BroadcastRoomReq, c.RoutineChan)\n\t\tgo cmt.process(cmt.pushChan[i], cmt.roomChan[i], cmt.broadcastChan)\n\t}\n\treturn cmt, nil\n}\n\n// Push push a user message.\nfunc (c *Comet) Push(arg *comet.PushMsgReq) (err error) {\n\tidx := atomic.AddUint64(&c.pushChanNum, 1) % c.routineSize\n\tc.pushChan[idx] <- arg\n\treturn\n}\n\n// BroadcastRoom broadcast a room message.\nfunc (c *Comet) BroadcastRoom(arg *comet.BroadcastRoomReq) (err error) {\n\tidx := atomic.AddUint64(&c.roomChanNum, 1) % c.routineSize\n\tc.roomChan[idx] <- arg\n\treturn\n}\n\n// Broadcast broadcast a message.\nfunc (c *Comet) Broadcast(arg *comet.BroadcastReq) (err error) {\n\tc.broadcastChan <- arg\n\treturn\n}\n\nfunc (c *Comet) process(pushChan chan *comet.PushMsgReq, roomChan chan *comet.BroadcastRoomReq, broadcastChan chan *comet.BroadcastReq) {\n\tfor {\n\t\tselect {\n\t\tcase broadcastArg := <-broadcastChan:\n\t\t\t_, err := c.client.Broadcast(context.Background(), &comet.BroadcastReq{\n\t\t\t\tProto:   broadcastArg.Proto,\n\t\t\t\tProtoOp: broadcastArg.ProtoOp,\n\t\t\t\tSpeed:   broadcastArg.Speed,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"c.client.Broadcast(%s, reply) serverId:%s error(%v)\", broadcastArg, c.serverID, err)\n\t\t\t}\n\t\tcase roomArg := <-roomChan:\n\t\t\t_, err := c.client.BroadcastRoom(context.Background(), &comet.BroadcastRoomReq{\n\t\t\t\tRoomID: roomArg.RoomID,\n\t\t\t\tProto:  roomArg.Proto,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"c.client.BroadcastRoom(%s, reply) serverId:%s error(%v)\", roomArg, c.serverID, err)\n\t\t\t}\n\t\tcase pushArg := <-pushChan:\n\t\t\t_, err := c.client.PushMsg(context.Background(), &comet.PushMsgReq{\n\t\t\t\tKeys:    pushArg.Keys,\n\t\t\t\tProto:   pushArg.Proto,\n\t\t\t\tProtoOp: pushArg.ProtoOp,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"c.client.PushMsg(%s, reply) serverId:%s error(%v)\", pushArg, c.serverID, err)\n\t\t\t}\n\t\tcase <-c.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Close close the resources.\nfunc (c *Comet) Close() (err error) {\n\tfinish := make(chan bool)\n\tgo func() {\n\t\tfor {\n\t\t\tn := len(c.broadcastChan)\n\t\t\tfor _, ch := range c.pushChan {\n\t\t\t\tn += len(ch)\n\t\t\t}\n\t\t\tfor _, ch := range c.roomChan {\n\t\t\t\tn += len(ch)\n\t\t\t}\n\t\t\tif n == 0 {\n\t\t\t\tfinish <- true\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttime.Sleep(time.Second)\n\t\t}\n\t}()\n\tselect {\n\tcase <-finish:\n\t\tlog.Info(\"close comet finish\")\n\tcase <-time.After(5 * time.Second):\n\t\terr = fmt.Errorf(\"close comet(server:%s push:%d room:%d broadcast:%d) timeout\", c.serverID, len(c.pushChan), len(c.roomChan), len(c.broadcastChan))\n\t}\n\tc.cancel()\n\treturn\n}\n"
  },
  {
    "path": "internal/job/conf/conf.go",
    "content": "package conf\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/bilibili/discovery/naming\"\n\t\"github.com/BurntSushi/toml\"\n\txtime \"github.com/Terry-Mao/goim/pkg/time\"\n)\n\nvar (\n\tconfPath  string\n\tregion    string\n\tzone      string\n\tdeployEnv string\n\thost      string\n\t// Conf config\n\tConf *Config\n)\n\nfunc init() {\n\tvar (\n\t\tdefHost, _ = os.Hostname()\n\t)\n\tflag.StringVar(&confPath, \"conf\", \"job-example.toml\", \"default config path\")\n\tflag.StringVar(&region, \"region\", os.Getenv(\"REGION\"), \"avaliable region. or use REGION env variable, value: sh etc.\")\n\tflag.StringVar(&zone, \"zone\", os.Getenv(\"ZONE\"), \"avaliable zone. or use ZONE env variable, value: sh001/sh002 etc.\")\n\tflag.StringVar(&deployEnv, \"deploy.env\", os.Getenv(\"DEPLOY_ENV\"), \"deploy env. or use DEPLOY_ENV env variable, value: dev/fat1/uat/pre/prod etc.\")\n\tflag.StringVar(&host, \"host\", defHost, \"machine hostname. or use default machine hostname.\")\n}\n\n// Init init config.\nfunc Init() (err error) {\n\tConf = Default()\n\t_, err = toml.DecodeFile(confPath, &Conf)\n\treturn\n}\n\n// Default new a config with specified defualt value.\nfunc Default() *Config {\n\treturn &Config{\n\t\tEnv:       &Env{Region: region, Zone: zone, DeployEnv: deployEnv, Host: host},\n\t\tDiscovery: &naming.Config{Region: region, Zone: zone, Env: deployEnv, Host: host},\n\t\tComet:     &Comet{RoutineChan: 1024, RoutineSize: 32},\n\t\tRoom: &Room{\n\t\t\tBatch:  20,\n\t\t\tSignal: xtime.Duration(time.Second),\n\t\t\tIdle:   xtime.Duration(time.Minute * 15),\n\t\t},\n\t}\n}\n\n// Config is job config.\ntype Config struct {\n\tEnv       *Env\n\tKafka     *Kafka\n\tDiscovery *naming.Config\n\tComet     *Comet\n\tRoom      *Room\n}\n\n// Room is room config.\ntype Room struct {\n\tBatch  int\n\tSignal xtime.Duration\n\tIdle   xtime.Duration\n}\n\n// Comet is comet config.\ntype Comet struct {\n\tRoutineChan int\n\tRoutineSize int\n}\n\n// Kafka is kafka config.\ntype Kafka struct {\n\tTopic   string\n\tGroup   string\n\tBrokers []string\n}\n\n// Env is env config.\ntype Env struct {\n\tRegion    string\n\tZone      string\n\tDeployEnv string\n\tHost      string\n}\n"
  },
  {
    "path": "internal/job/job.go",
    "content": "package job\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\tpb \"github.com/Terry-Mao/goim/api/logic\"\n\t\"github.com/Terry-Mao/goim/internal/job/conf\"\n\t\"github.com/bilibili/discovery/naming\"\n\t\"github.com/golang/protobuf/proto\"\n\n\tcluster \"github.com/bsm/sarama-cluster\"\n\tlog \"github.com/golang/glog\"\n)\n\n// Job is push job.\ntype Job struct {\n\tc            *conf.Config\n\tconsumer     *cluster.Consumer\n\tcometServers map[string]*Comet\n\n\trooms      map[string]*Room\n\troomsMutex sync.RWMutex\n}\n\n// New new a push job.\nfunc New(c *conf.Config) *Job {\n\tj := &Job{\n\t\tc:        c,\n\t\tconsumer: newKafkaSub(c.Kafka),\n\t\trooms:    make(map[string]*Room),\n\t}\n\tj.watchComet(c.Discovery)\n\treturn j\n}\n\nfunc newKafkaSub(c *conf.Kafka) *cluster.Consumer {\n\tconfig := cluster.NewConfig()\n\tconfig.Consumer.Return.Errors = true\n\tconfig.Group.Return.Notifications = true\n\tconsumer, err := cluster.NewConsumer(c.Brokers, c.Group, []string{c.Topic}, config)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn consumer\n}\n\n// Close close resounces.\nfunc (j *Job) Close() error {\n\tif j.consumer != nil {\n\t\treturn j.consumer.Close()\n\t}\n\treturn nil\n}\n\n// Consume messages, watch signals\nfunc (j *Job) Consume() {\n\tfor {\n\t\tselect {\n\t\tcase err := <-j.consumer.Errors():\n\t\t\tlog.Errorf(\"consumer error(%v)\", err)\n\t\tcase n := <-j.consumer.Notifications():\n\t\t\tlog.Infof(\"consumer rebalanced(%v)\", n)\n\t\tcase msg, ok := <-j.consumer.Messages():\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tj.consumer.MarkOffset(msg, \"\")\n\t\t\t// process push message\n\t\t\tpushMsg := new(pb.PushMsg)\n\t\t\tif err := proto.Unmarshal(msg.Value, pushMsg); err != nil {\n\t\t\t\tlog.Errorf(\"proto.Unmarshal(%v) error(%v)\", msg, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err := j.push(context.Background(), pushMsg); err != nil {\n\t\t\t\tlog.Errorf(\"j.push(%v) error(%v)\", pushMsg, err)\n\t\t\t}\n\t\t\tlog.Infof(\"consume: %s/%d/%d\\t%s\\t%+v\", msg.Topic, msg.Partition, msg.Offset, msg.Key, pushMsg)\n\t\t}\n\t}\n}\n\nfunc (j *Job) watchComet(c *naming.Config) {\n\tdis := naming.New(c)\n\tresolver := dis.Build(\"goim.comet\")\n\tevent := resolver.Watch()\n\tselect {\n\tcase _, ok := <-event:\n\t\tif !ok {\n\t\t\tpanic(\"watchComet init failed\")\n\t\t}\n\t\tif ins, ok := resolver.Fetch(); ok {\n\t\t\tif err := j.newAddress(ins.Instances); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tlog.Infof(\"watchComet init newAddress:%+v\", ins)\n\t\t}\n\tcase <-time.After(10 * time.Second):\n\t\tlog.Error(\"watchComet init instances timeout\")\n\t}\n\tgo func() {\n\t\tfor {\n\t\t\tif _, ok := <-event; !ok {\n\t\t\t\tlog.Info(\"watchComet exit\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tins, ok := resolver.Fetch()\n\t\t\tif ok {\n\t\t\t\tif err := j.newAddress(ins.Instances); err != nil {\n\t\t\t\t\tlog.Errorf(\"watchComet newAddress(%+v) error(%+v)\", ins, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlog.Infof(\"watchComet change newAddress:%+v\", ins)\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (j *Job) newAddress(insMap map[string][]*naming.Instance) error {\n\tins := insMap[j.c.Env.Zone]\n\tif len(ins) == 0 {\n\t\treturn fmt.Errorf(\"watchComet instance is empty\")\n\t}\n\tcomets := map[string]*Comet{}\n\tfor _, in := range ins {\n\t\tif old, ok := j.cometServers[in.Hostname]; ok {\n\t\t\tcomets[in.Hostname] = old\n\t\t\tcontinue\n\t\t}\n\t\tc, err := NewComet(in, j.c.Comet)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"watchComet NewComet(%+v) error(%v)\", in, err)\n\t\t\treturn err\n\t\t}\n\t\tcomets[in.Hostname] = c\n\t\tlog.Infof(\"watchComet AddComet grpc:%+v\", in)\n\t}\n\tfor key, old := range j.cometServers {\n\t\tif _, ok := comets[key]; !ok {\n\t\t\told.cancel()\n\t\t\tlog.Infof(\"watchComet DelComet:%s\", key)\n\t\t}\n\t}\n\tj.cometServers = comets\n\treturn nil\n}\n"
  },
  {
    "path": "internal/job/push.go",
    "content": "package job\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/Terry-Mao/goim/api/comet\"\n\tpb \"github.com/Terry-Mao/goim/api/logic\"\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/pkg/bytes\"\n\tlog \"github.com/golang/glog\"\n)\n\nfunc (j *Job) push(ctx context.Context, pushMsg *pb.PushMsg) (err error) {\n\tswitch pushMsg.Type {\n\tcase pb.PushMsg_PUSH:\n\t\terr = j.pushKeys(pushMsg.Operation, pushMsg.Server, pushMsg.Keys, pushMsg.Msg)\n\tcase pb.PushMsg_ROOM:\n\t\terr = j.getRoom(pushMsg.Room).Push(pushMsg.Operation, pushMsg.Msg)\n\tcase pb.PushMsg_BROADCAST:\n\t\terr = j.broadcast(pushMsg.Operation, pushMsg.Msg, pushMsg.Speed)\n\tdefault:\n\t\terr = fmt.Errorf(\"no match push type: %s\", pushMsg.Type)\n\t}\n\treturn\n}\n\n// pushKeys push a message to a batch of subkeys.\nfunc (j *Job) pushKeys(operation int32, serverID string, subKeys []string, body []byte) (err error) {\n\tbuf := bytes.NewWriterSize(len(body) + 64)\n\tp := &protocol.Proto{\n\t\tVer:  1,\n\t\tOp:   operation,\n\t\tBody: body,\n\t}\n\tp.WriteTo(buf)\n\tp.Body = buf.Buffer()\n\tp.Op = protocol.OpRaw\n\tvar args = comet.PushMsgReq{\n\t\tKeys:    subKeys,\n\t\tProtoOp: operation,\n\t\tProto:   p,\n\t}\n\tif c, ok := j.cometServers[serverID]; ok {\n\t\tif err = c.Push(&args); err != nil {\n\t\t\tlog.Errorf(\"c.Push(%v) serverID:%s error(%v)\", args, serverID, err)\n\t\t}\n\t\tlog.Infof(\"pushKey:%s comets:%d\", serverID, len(j.cometServers))\n\t}\n\treturn\n}\n\n// broadcast broadcast a message to all.\nfunc (j *Job) broadcast(operation int32, body []byte, speed int32) (err error) {\n\tbuf := bytes.NewWriterSize(len(body) + 64)\n\tp := &protocol.Proto{\n\t\tVer:  1,\n\t\tOp:   operation,\n\t\tBody: body,\n\t}\n\tp.WriteTo(buf)\n\tp.Body = buf.Buffer()\n\tp.Op = protocol.OpRaw\n\tcomets := j.cometServers\n\tspeed /= int32(len(comets))\n\tvar args = comet.BroadcastReq{\n\t\tProtoOp: operation,\n\t\tProto:   p,\n\t\tSpeed:   speed,\n\t}\n\tfor serverID, c := range comets {\n\t\tif err = c.Broadcast(&args); err != nil {\n\t\t\tlog.Errorf(\"c.Broadcast(%v) serverID:%s error(%v)\", args, serverID, err)\n\t\t}\n\t}\n\tlog.Infof(\"broadcast comets:%d\", len(comets))\n\treturn\n}\n\n// broadcastRoomRawBytes broadcast aggregation messages to room.\nfunc (j *Job) broadcastRoomRawBytes(roomID string, body []byte) (err error) {\n\targs := comet.BroadcastRoomReq{\n\t\tRoomID: roomID,\n\t\tProto: &protocol.Proto{\n\t\t\tVer:  1,\n\t\t\tOp:   protocol.OpRaw,\n\t\t\tBody: body,\n\t\t},\n\t}\n\tcomets := j.cometServers\n\tfor serverID, c := range comets {\n\t\tif err = c.BroadcastRoom(&args); err != nil {\n\t\t\tlog.Errorf(\"c.BroadcastRoom(%v) roomID:%s serverID:%s error(%v)\", args, roomID, serverID, err)\n\t\t}\n\t}\n\tlog.Infof(\"broadcastRoom comets:%d\", len(comets))\n\treturn\n}\n"
  },
  {
    "path": "internal/job/room.go",
    "content": "package job\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/internal/job/conf\"\n\t\"github.com/Terry-Mao/goim/pkg/bytes\"\n\tlog \"github.com/golang/glog\"\n)\n\nvar (\n\t// ErrComet commet error.\n\tErrComet = errors.New(\"comet rpc is not available\")\n\t// ErrCometFull comet chan full.\n\tErrCometFull = errors.New(\"comet proto chan full\")\n\t// ErrRoomFull room chan full.\n\tErrRoomFull = errors.New(\"room proto chan full\")\n\n\troomReadyProto = new(protocol.Proto)\n)\n\n// Room room.\ntype Room struct {\n\tc     *conf.Room\n\tjob   *Job\n\tid    string\n\tproto chan *protocol.Proto\n}\n\n// NewRoom new a room struct, store channel room info.\nfunc NewRoom(job *Job, id string, c *conf.Room) (r *Room) {\n\tr = &Room{\n\t\tc:     c,\n\t\tid:    id,\n\t\tjob:   job,\n\t\tproto: make(chan *protocol.Proto, c.Batch*2),\n\t}\n\tgo r.pushproc(c.Batch, time.Duration(c.Signal))\n\treturn\n}\n\n// Push push msg to the room, if chan full discard it.\nfunc (r *Room) Push(op int32, msg []byte) (err error) {\n\tvar p = &protocol.Proto{\n\t\tVer:  1,\n\t\tOp:   op,\n\t\tBody: msg,\n\t}\n\tselect {\n\tcase r.proto <- p:\n\tdefault:\n\t\terr = ErrRoomFull\n\t}\n\treturn\n}\n\n// pushproc merge proto and push msgs in batch.\nfunc (r *Room) pushproc(batch int, sigTime time.Duration) {\n\tvar (\n\t\tn    int\n\t\tlast time.Time\n\t\tp    *protocol.Proto\n\t\tbuf  = bytes.NewWriterSize(int(protocol.MaxBodySize))\n\t)\n\tlog.Infof(\"start room:%s goroutine\", r.id)\n\ttd := time.AfterFunc(sigTime, func() {\n\t\tselect {\n\t\tcase r.proto <- roomReadyProto:\n\t\tdefault:\n\t\t}\n\t})\n\tdefer td.Stop()\n\tfor {\n\t\tif p = <-r.proto; p == nil {\n\t\t\tbreak // exit\n\t\t} else if p != roomReadyProto {\n\t\t\t// merge buffer ignore error, always nil\n\t\t\tp.WriteTo(buf)\n\t\t\tif n++; n == 1 {\n\t\t\t\tlast = time.Now()\n\t\t\t\ttd.Reset(sigTime)\n\t\t\t\tcontinue\n\t\t\t} else if n < batch {\n\t\t\t\tif sigTime > time.Since(last) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif n == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t_ = r.job.broadcastRoomRawBytes(r.id, buf.Buffer())\n\t\t// TODO use reset buffer\n\t\t// after push to room channel, renew a buffer, let old buffer gc\n\t\tbuf = bytes.NewWriterSize(buf.Size())\n\t\tn = 0\n\t\tif r.c.Idle != 0 {\n\t\t\ttd.Reset(time.Duration(r.c.Idle))\n\t\t} else {\n\t\t\ttd.Reset(time.Minute)\n\t\t}\n\t}\n\tr.job.delRoom(r.id)\n\tlog.Infof(\"room:%s goroutine exit\", r.id)\n}\n\nfunc (j *Job) delRoom(roomID string) {\n\tj.roomsMutex.Lock()\n\tdelete(j.rooms, roomID)\n\tj.roomsMutex.Unlock()\n}\n\nfunc (j *Job) getRoom(roomID string) *Room {\n\tj.roomsMutex.RLock()\n\troom, ok := j.rooms[roomID]\n\tj.roomsMutex.RUnlock()\n\tif !ok {\n\t\tj.roomsMutex.Lock()\n\t\tif room, ok = j.rooms[roomID]; !ok {\n\t\t\troom = NewRoom(j, roomID, j.c.Room)\n\t\t\tj.rooms[roomID] = room\n\t\t}\n\t\tj.roomsMutex.Unlock()\n\t\tlog.Infof(\"new a room:%s active:%d\", roomID, len(j.rooms))\n\t}\n\treturn room\n}\n"
  },
  {
    "path": "internal/logic/balancer.go",
    "content": "package logic\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/bilibili/discovery/naming\"\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n\tlog \"github.com/golang/glog\"\n)\n\nconst (\n\t_minWeight = 1\n\t_maxWeight = 1 << 20\n\t_maxNodes  = 5\n)\n\ntype weightedNode struct {\n\tregion        string\n\thostname      string\n\taddrs         []string\n\tfixedWeight   int64\n\tcurrentWeight int64\n\tcurrentConns  int64\n\tupdated       int64\n}\n\nfunc (w *weightedNode) String() string {\n\treturn fmt.Sprintf(\"region:%s fixedWeight:%d, currentWeight:%d, currentConns:%d\", w.region, w.fixedWeight, w.currentWeight, w.currentConns)\n}\n\nfunc (w *weightedNode) chosen() {\n\tw.currentConns++\n}\n\nfunc (w *weightedNode) reset() {\n\tw.currentWeight = 0\n}\n\nfunc (w *weightedNode) calculateWeight(totalWeight, totalConns int64, gainWeight float64) {\n\tfixedWeight := float64(w.fixedWeight) * gainWeight\n\ttotalWeight += int64(fixedWeight) - w.fixedWeight\n\tif totalConns > 0 {\n\t\tweightRatio := fixedWeight / float64(totalWeight)\n\t\tvar connRatio float64\n\t\tif totalConns != 0 {\n\t\t\tconnRatio = float64(w.currentConns) / float64(totalConns) * 0.5\n\t\t}\n\t\tdiff := weightRatio - connRatio\n\t\tmultiple := diff * float64(totalConns)\n\t\tfloor := math.Floor(multiple)\n\t\tif floor-multiple >= -0.5 {\n\t\t\tw.currentWeight = int64(fixedWeight + floor)\n\t\t} else {\n\t\t\tw.currentWeight = int64(fixedWeight + math.Ceil(multiple))\n\t\t}\n\t\tif diff < 0 {\n\t\t\t// we always return the max from minWeight and calculated Current weight\n\t\t\tif _minWeight > w.currentWeight {\n\t\t\t\tw.currentWeight = _minWeight\n\t\t\t}\n\t\t} else {\n\t\t\t// we always return the min from maxWeight and calculated Current weight\n\t\t\tif _maxWeight < w.currentWeight {\n\t\t\t\tw.currentWeight = _maxWeight\n\t\t\t}\n\t\t}\n\t} else {\n\t\tw.reset()\n\t}\n}\n\n// LoadBalancer load balancer.\ntype LoadBalancer struct {\n\ttotalConns  int64\n\ttotalWeight int64\n\tnodes       map[string]*weightedNode\n\tnodesMutex  sync.Mutex\n}\n\n// NewLoadBalancer new a load balancer.\nfunc NewLoadBalancer() *LoadBalancer {\n\tlb := &LoadBalancer{\n\t\tnodes: make(map[string]*weightedNode),\n\t}\n\treturn lb\n}\n\n// Size return node size.\nfunc (lb *LoadBalancer) Size() int {\n\treturn len(lb.nodes)\n}\n\nfunc (lb *LoadBalancer) weightedNodes(region string, regionWeight float64) (nodes []*weightedNode) {\n\tfor _, n := range lb.nodes {\n\t\tvar gainWeight = float64(1.0)\n\t\tif n.region == region {\n\t\t\tgainWeight *= regionWeight\n\t\t}\n\t\tn.calculateWeight(lb.totalWeight, lb.totalConns, gainWeight)\n\t\tnodes = append(nodes, n)\n\t}\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\treturn nodes[i].currentWeight > nodes[j].currentWeight\n\t})\n\tif len(nodes) > 0 {\n\t\tnodes[0].chosen()\n\t\tlb.totalConns++\n\t}\n\treturn\n}\n\n// NodeAddrs return node addrs.\nfunc (lb *LoadBalancer) NodeAddrs(region, domain string, regionWeight float64) (domains, addrs []string) {\n\tlb.nodesMutex.Lock()\n\tnodes := lb.weightedNodes(region, regionWeight)\n\tlb.nodesMutex.Unlock()\n\tfor i, n := range nodes {\n\t\tif i == _maxNodes {\n\t\t\tbreak\n\t\t}\n\t\tdomains = append(domains, n.hostname+domain)\n\t\taddrs = append(addrs, n.addrs...)\n\t}\n\treturn\n}\n\n// Update update server nodes.\nfunc (lb *LoadBalancer) Update(ins []*naming.Instance) {\n\tvar (\n\t\ttotalConns  int64\n\t\ttotalWeight int64\n\t\tnodes       = make(map[string]*weightedNode, len(ins))\n\t)\n\tif len(ins) == 0 || float32(len(ins))/float32(len(lb.nodes)) < 0.5 {\n\t\tlog.Errorf(\"load balancer update src:%d target:%d less than half\", len(lb.nodes), len(ins))\n\t\treturn\n\t}\n\tlb.nodesMutex.Lock()\n\tfor _, in := range ins {\n\t\tif old, ok := lb.nodes[in.Hostname]; ok && old.updated == in.LastTs {\n\t\t\tnodes[in.Hostname] = old\n\t\t\ttotalConns += old.currentConns\n\t\t\ttotalWeight += old.fixedWeight\n\t\t} else {\n\t\t\tmeta := in.Metadata\n\t\t\tweight, err := strconv.ParseInt(meta[model.MetaWeight], 10, 32)\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"instance(%+v) strconv.ParseInt(weight:%s) error(%v)\", in, meta[model.MetaWeight], err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconns, err := strconv.ParseInt(meta[model.MetaConnCount], 10, 32)\n\t\t\tif err != nil {\n\t\t\t\tlog.Errorf(\"instance(%+v) strconv.ParseInt(conns:%s) error(%v)\", in, meta[model.MetaConnCount], err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tnodes[in.Hostname] = &weightedNode{\n\t\t\t\tregion:       in.Region,\n\t\t\t\thostname:     in.Hostname,\n\t\t\t\tfixedWeight:  weight,\n\t\t\t\tcurrentConns: conns,\n\t\t\t\taddrs:        strings.Split(meta[model.MetaAddrs], \",\"),\n\t\t\t\tupdated:      in.LastTs,\n\t\t\t}\n\t\t\ttotalConns += conns\n\t\t\ttotalWeight += weight\n\t\t}\n\t}\n\tlb.nodes = nodes\n\tlb.totalConns = totalConns\n\tlb.totalWeight = totalWeight\n\tlb.nodesMutex.Unlock()\n}\n"
  },
  {
    "path": "internal/logic/balancer_test.go",
    "content": "package logic\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/bilibili/discovery/naming\"\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n)\n\nfunc TestWeightedNode(t *testing.T) {\n\tnodes := []*weightedNode{\n\t\t&weightedNode{fixedWeight: 1, currentWeight: 1, currentConns: 1000000},\n\t\t&weightedNode{fixedWeight: 2, currentWeight: 1, currentConns: 1000000},\n\t\t&weightedNode{fixedWeight: 3, currentWeight: 1, currentConns: 1000000},\n\t}\n\tfor i := 0; i < 100; i++ {\n\t\tfor _, n := range nodes {\n\t\t\tn.calculateWeight(6, nodes[0].currentConns+nodes[1].currentConns+nodes[2].currentConns, 1.0)\n\t\t}\n\t\tsort.Slice(nodes, func(i, j int) bool {\n\t\t\treturn nodes[i].currentWeight > nodes[j].currentWeight\n\t\t})\n\t\tnodes[0].chosen()\n\t}\n\tft := float64(nodes[0].fixedWeight + nodes[1].fixedWeight + nodes[2].fixedWeight)\n\tct := float64(nodes[0].currentConns + nodes[1].currentConns + nodes[2].currentConns)\n\tfor _, n := range nodes {\n\t\tt.Logf(\"match ratio %d:%d\", int(float64(n.fixedWeight)/ft*100*0.6), int(float64(n.currentConns)/ct*100))\n\t}\n}\n\nfunc TestLoadBalancer(t *testing.T) {\n\tss := []*naming.Instance{\n\t\t&naming.Instance{\n\t\t\tRegion:   \"bj\",\n\t\t\tHostname: \"01\",\n\t\t\tMetadata: map[string]string{\n\t\t\t\tmodel.MetaWeight:    \"10\",\n\t\t\t\tmodel.MetaConnCount: \"240590\",\n\t\t\t\tmodel.MetaIPCount:   \"10\",\n\t\t\t\tmodel.MetaAddrs:     \"ip_bj\",\n\t\t\t},\n\t\t},\n\t\t&naming.Instance{\n\t\t\tRegion:   \"sh\",\n\t\t\tHostname: \"02\",\n\t\t\tMetadata: map[string]string{\n\t\t\t\tmodel.MetaWeight:    \"10\",\n\t\t\t\tmodel.MetaConnCount: \"375420\",\n\t\t\t\tmodel.MetaIPCount:   \"10\",\n\t\t\t\tmodel.MetaAddrs:     \"ip_sh\",\n\t\t\t},\n\t\t},\n\t\t&naming.Instance{\n\t\t\tRegion:   \"gz\",\n\t\t\tHostname: \"03\",\n\t\t\tMetadata: map[string]string{\n\t\t\t\tmodel.MetaWeight:    \"10\",\n\t\t\t\tmodel.MetaConnCount: \"293430\",\n\t\t\t\tmodel.MetaIPCount:   \"10\",\n\t\t\t\tmodel.MetaAddrs:     \"ip_gz\",\n\t\t\t},\n\t\t},\n\t}\n\tlb := NewLoadBalancer()\n\tlb.Update(ss)\n\tfor i := 0; i < 5; i++ {\n\t\tt.Log(lb.NodeAddrs(\"sh\", \".test\", 1.6))\n\t}\n}\n"
  },
  {
    "path": "internal/logic/conf/conf.go",
    "content": "package conf\n\nimport (\n\t\"flag\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/bilibili/discovery/naming\"\n\txtime \"github.com/Terry-Mao/goim/pkg/time\"\n\n\t\"github.com/BurntSushi/toml\"\n)\n\nvar (\n\tconfPath  string\n\tregion    string\n\tzone      string\n\tdeployEnv string\n\thost      string\n\tweight    int64\n\n\t// Conf config\n\tConf *Config\n)\n\nfunc init() {\n\tvar (\n\t\tdefHost, _   = os.Hostname()\n\t\tdefWeight, _ = strconv.ParseInt(os.Getenv(\"WEIGHT\"), 10, 32)\n\t)\n\tflag.StringVar(&confPath, \"conf\", \"logic-example.toml\", \"default config path\")\n\tflag.StringVar(&region, \"region\", os.Getenv(\"REGION\"), \"avaliable region. or use REGION env variable, value: sh etc.\")\n\tflag.StringVar(&zone, \"zone\", os.Getenv(\"ZONE\"), \"avaliable zone. or use ZONE env variable, value: sh001/sh002 etc.\")\n\tflag.StringVar(&deployEnv, \"deploy.env\", os.Getenv(\"DEPLOY_ENV\"), \"deploy env. or use DEPLOY_ENV env variable, value: dev/fat1/uat/pre/prod etc.\")\n\tflag.StringVar(&host, \"host\", defHost, \"machine hostname. or use default machine hostname.\")\n\tflag.Int64Var(&weight, \"weight\", defWeight, \"load balancing weight, or use WEIGHT env variable, value: 10 etc.\")\n}\n\n// Init init config.\nfunc Init() (err error) {\n\tConf = Default()\n\t_, err = toml.DecodeFile(confPath, &Conf)\n\treturn\n}\n\n// Default new a config with specified defualt value.\nfunc Default() *Config {\n\treturn &Config{\n\t\tEnv:       &Env{Region: region, Zone: zone, DeployEnv: deployEnv, Host: host, Weight: weight},\n\t\tDiscovery: &naming.Config{Region: region, Zone: zone, Env: deployEnv, Host: host},\n\t\tHTTPServer: &HTTPServer{\n\t\t\tNetwork:      \"tcp\",\n\t\t\tAddr:         \"3111\",\n\t\t\tReadTimeout:  xtime.Duration(time.Second),\n\t\t\tWriteTimeout: xtime.Duration(time.Second),\n\t\t},\n\t\tRPCClient: &RPCClient{Dial: xtime.Duration(time.Second), Timeout: xtime.Duration(time.Second)},\n\t\tRPCServer: &RPCServer{\n\t\t\tNetwork:           \"tcp\",\n\t\t\tAddr:              \"3119\",\n\t\t\tTimeout:           xtime.Duration(time.Second),\n\t\t\tIdleTimeout:       xtime.Duration(time.Second * 60),\n\t\t\tMaxLifeTime:       xtime.Duration(time.Hour * 2),\n\t\t\tForceCloseWait:    xtime.Duration(time.Second * 20),\n\t\t\tKeepAliveInterval: xtime.Duration(time.Second * 60),\n\t\t\tKeepAliveTimeout:  xtime.Duration(time.Second * 20),\n\t\t},\n\t\tBackoff: &Backoff{MaxDelay: 300, BaseDelay: 3, Factor: 1.8, Jitter: 1.3},\n\t}\n}\n\n// Config config.\ntype Config struct {\n\tEnv        *Env\n\tDiscovery  *naming.Config\n\tRPCClient  *RPCClient\n\tRPCServer  *RPCServer\n\tHTTPServer *HTTPServer\n\tKafka      *Kafka\n\tRedis      *Redis\n\tNode       *Node\n\tBackoff    *Backoff\n\tRegions    map[string][]string\n}\n\n// Env is env config.\ntype Env struct {\n\tRegion    string\n\tZone      string\n\tDeployEnv string\n\tHost      string\n\tWeight    int64\n}\n\n// Node node config.\ntype Node struct {\n\tDefaultDomain string\n\tHostDomain    string\n\tTCPPort       int\n\tWSPort        int\n\tWSSPort       int\n\tHeartbeatMax  int\n\tHeartbeat     xtime.Duration\n\tRegionWeight  float64\n}\n\n// Backoff backoff.\ntype Backoff struct {\n\tMaxDelay  int32\n\tBaseDelay int32\n\tFactor    float32\n\tJitter    float32\n}\n\n// Redis .\ntype Redis struct {\n\tNetwork      string\n\tAddr         string\n\tAuth         string\n\tActive       int\n\tIdle         int\n\tDialTimeout  xtime.Duration\n\tReadTimeout  xtime.Duration\n\tWriteTimeout xtime.Duration\n\tIdleTimeout  xtime.Duration\n\tExpire       xtime.Duration\n}\n\n// Kafka .\ntype Kafka struct {\n\tTopic   string\n\tBrokers []string\n}\n\n// RPCClient is RPC client config.\ntype RPCClient struct {\n\tDial    xtime.Duration\n\tTimeout xtime.Duration\n}\n\n// RPCServer is RPC server config.\ntype RPCServer struct {\n\tNetwork           string\n\tAddr              string\n\tTimeout           xtime.Duration\n\tIdleTimeout       xtime.Duration\n\tMaxLifeTime       xtime.Duration\n\tForceCloseWait    xtime.Duration\n\tKeepAliveInterval xtime.Duration\n\tKeepAliveTimeout  xtime.Duration\n}\n\n// HTTPServer is http server config.\ntype HTTPServer struct {\n\tNetwork      string\n\tAddr         string\n\tReadTimeout  xtime.Duration\n\tWriteTimeout xtime.Duration\n}\n"
  },
  {
    "path": "internal/logic/conn.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n\tlog \"github.com/golang/glog\"\n\t\"github.com/google/uuid\"\n)\n\n// Connect connected a conn.\nfunc (l *Logic) Connect(c context.Context, server, cookie string, token []byte) (mid int64, key, roomID string, accepts []int32, hb int64, err error) {\n\tvar params struct {\n\t\tMid      int64   `json:\"mid\"`\n\t\tKey      string  `json:\"key\"`\n\t\tRoomID   string  `json:\"room_id\"`\n\t\tPlatform string  `json:\"platform\"`\n\t\tAccepts  []int32 `json:\"accepts\"`\n\t}\n\tif err = json.Unmarshal(token, &params); err != nil {\n\t\tlog.Errorf(\"json.Unmarshal(%s) error(%v)\", token, err)\n\t\treturn\n\t}\n\tmid = params.Mid\n\troomID = params.RoomID\n\taccepts = params.Accepts\n\thb = int64(l.c.Node.Heartbeat) * int64(l.c.Node.HeartbeatMax)\n\tif key = params.Key; key == \"\" {\n\t\tkey = uuid.New().String()\n\t}\n\tif err = l.dao.AddMapping(c, mid, key, server); err != nil {\n\t\tlog.Errorf(\"l.dao.AddMapping(%d,%s,%s) error(%v)\", mid, key, server, err)\n\t}\n\tlog.Infof(\"conn connected key:%s server:%s mid:%d token:%s\", key, server, mid, token)\n\treturn\n}\n\n// Disconnect disconnect a conn.\nfunc (l *Logic) Disconnect(c context.Context, mid int64, key, server string) (has bool, err error) {\n\tif has, err = l.dao.DelMapping(c, mid, key, server); err != nil {\n\t\tlog.Errorf(\"l.dao.DelMapping(%d,%s) error(%v)\", mid, key, server)\n\t\treturn\n\t}\n\tlog.Infof(\"conn disconnected key:%s server:%s mid:%d\", key, server, mid)\n\treturn\n}\n\n// Heartbeat heartbeat a conn.\nfunc (l *Logic) Heartbeat(c context.Context, mid int64, key, server string) (err error) {\n\thas, err := l.dao.ExpireMapping(c, mid, key)\n\tif err != nil {\n\t\tlog.Errorf(\"l.dao.ExpireMapping(%d,%s,%s) error(%v)\", mid, key, server, err)\n\t\treturn\n\t}\n\tif !has {\n\t\tif err = l.dao.AddMapping(c, mid, key, server); err != nil {\n\t\t\tlog.Errorf(\"l.dao.AddMapping(%d,%s,%s) error(%v)\", mid, key, server, err)\n\t\t\treturn\n\t\t}\n\t}\n\tlog.Infof(\"conn heartbeat key:%s server:%s mid:%d\", key, server, mid)\n\treturn\n}\n\n// RenewOnline renew a server online.\nfunc (l *Logic) RenewOnline(c context.Context, server string, roomCount map[string]int32) (map[string]int32, error) {\n\tonline := &model.Online{\n\t\tServer:    server,\n\t\tRoomCount: roomCount,\n\t\tUpdated:   time.Now().Unix(),\n\t}\n\tif err := l.dao.AddServerOnline(context.Background(), server, online); err != nil {\n\t\treturn nil, err\n\t}\n\treturn l.roomCount, nil\n}\n\n// Receive receive a message.\nfunc (l *Logic) Receive(c context.Context, mid int64, proto *protocol.Proto) (err error) {\n\tlog.Infof(\"receive mid:%d message:%+v\", mid, proto)\n\treturn\n}\n"
  },
  {
    "path": "internal/logic/conn_test.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/Terry-Mao/goim/api/protocol\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestConnect(t *testing.T) {\n\tvar (\n\t\tserver    = \"test_server\"\n\t\tserverKey = \"test_server_key\"\n\t\tcookie    = \"\"\n\t\ttoken     = []byte(`{\"mid\":1, \"key\":\"test_server_key\", \"room_id\":\"test://test_room\", \"platform\":\"web\", \"accepts\":[1000,1001,1002]}`)\n\t\tol        = map[string]int32{\"test://test_room\": 100}\n\t\tc         = context.Background()\n\t)\n\t// connect\n\tmid, key, roomID, accepts, hb, err := lg.Connect(c, server, cookie, token)\n\tassert.Nil(t, err)\n\tassert.Equal(t, serverKey, key)\n\tassert.Equal(t, roomID, \"test://test_room\")\n\tassert.Equal(t, len(accepts), 3)\n\tassert.NotZero(t, hb)\n\tt.Log(mid, key, roomID, accepts, err)\n\t// heartbeat\n\terr = lg.Heartbeat(c, mid, key, server)\n\tassert.Nil(t, err)\n\t// disconnect\n\thas, err := lg.Disconnect(c, mid, key, server)\n\tassert.Nil(t, err)\n\tassert.Equal(t, true, has)\n\t// renew\n\tonline, err := lg.RenewOnline(c, server, ol)\n\tassert.Nil(t, err)\n\tassert.NotNil(t, online)\n\t// message\n\terr = lg.Receive(c, mid, &protocol.Proto{})\n\tassert.Nil(t, err)\n}\n"
  },
  {
    "path": "internal/logic/dao/dao.go",
    "content": "package dao\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/Terry-Mao/goim/internal/logic/conf\"\n\t\"github.com/gomodule/redigo/redis\"\n\tkafka \"gopkg.in/Shopify/sarama.v1\"\n)\n\n// Dao dao.\ntype Dao struct {\n\tc           *conf.Config\n\tkafkaPub    kafka.SyncProducer\n\tredis       *redis.Pool\n\tredisExpire int32\n}\n\n// New new a dao and return.\nfunc New(c *conf.Config) *Dao {\n\td := &Dao{\n\t\tc:           c,\n\t\tkafkaPub:    newKafkaPub(c.Kafka),\n\t\tredis:       newRedis(c.Redis),\n\t\tredisExpire: int32(time.Duration(c.Redis.Expire) / time.Second),\n\t}\n\treturn d\n}\n\nfunc newKafkaPub(c *conf.Kafka) kafka.SyncProducer {\n\tkc := kafka.NewConfig()\n\tkc.Producer.RequiredAcks = kafka.WaitForAll // Wait for all in-sync replicas to ack the message\n\tkc.Producer.Retry.Max = 10                  // Retry up to 10 times to produce the message\n\tkc.Producer.Return.Successes = true\n\tpub, err := kafka.NewSyncProducer(c.Brokers, kc)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn pub\n}\n\nfunc newRedis(c *conf.Redis) *redis.Pool {\n\treturn &redis.Pool{\n\t\tMaxIdle:     c.Idle,\n\t\tMaxActive:   c.Active,\n\t\tIdleTimeout: time.Duration(c.IdleTimeout),\n\t\tDial: func() (redis.Conn, error) {\n\t\t\tconn, err := redis.Dial(c.Network, c.Addr,\n\t\t\t\tredis.DialConnectTimeout(time.Duration(c.DialTimeout)),\n\t\t\t\tredis.DialReadTimeout(time.Duration(c.ReadTimeout)),\n\t\t\t\tredis.DialWriteTimeout(time.Duration(c.WriteTimeout)),\n\t\t\t\tredis.DialPassword(c.Auth),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn conn, nil\n\t\t},\n\t}\n}\n\n// Close close the resource.\nfunc (d *Dao) Close() error {\n\treturn d.redis.Close()\n}\n\n// Ping dao ping.\nfunc (d *Dao) Ping(c context.Context) error {\n\treturn d.pingRedis(c)\n}\n"
  },
  {
    "path": "internal/logic/dao/dao_test.go",
    "content": "package dao\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/Terry-Mao/goim/internal/logic/conf\"\n)\n\nvar (\n\td *Dao\n)\n\nfunc TestMain(m *testing.M) {\n\tif err := flag.Set(\"conf\", \"../../../cmd/logic/logic-example.toml\"); err != nil {\n\t\tpanic(err)\n\t}\n\tflag.Parse()\n\tif err := conf.Init(); err != nil {\n\t\tpanic(err)\n\t}\n\td = New(conf.Conf)\n\tif err := d.Ping(context.TODO()); err != nil {\n\t\tos.Exit(-1)\n\t}\n\tif err := d.Close(); err != nil {\n\t\tos.Exit(-1)\n\t}\n\tif err := d.Ping(context.TODO()); err == nil {\n\t\tos.Exit(-1)\n\t}\n\td = New(conf.Conf)\n\tos.Exit(m.Run())\n}\n"
  },
  {
    "path": "internal/logic/dao/kafka.go",
    "content": "package dao\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\n\tpb \"github.com/Terry-Mao/goim/api/logic\"\n\tlog \"github.com/golang/glog\"\n\t\"github.com/golang/protobuf/proto\"\n\tsarama \"gopkg.in/Shopify/sarama.v1\"\n)\n\n// PushMsg push a message to databus.\nfunc (d *Dao) PushMsg(c context.Context, op int32, server string, keys []string, msg []byte) (err error) {\n\tpushMsg := &pb.PushMsg{\n\t\tType:      pb.PushMsg_PUSH,\n\t\tOperation: op,\n\t\tServer:    server,\n\t\tKeys:      keys,\n\t\tMsg:       msg,\n\t}\n\tb, err := proto.Marshal(pushMsg)\n\tif err != nil {\n\t\treturn\n\t}\n\tm := &sarama.ProducerMessage{\n\t\tKey:   sarama.StringEncoder(keys[0]),\n\t\tTopic: d.c.Kafka.Topic,\n\t\tValue: sarama.ByteEncoder(b),\n\t}\n\tif _, _, err = d.kafkaPub.SendMessage(m); err != nil {\n\t\tlog.Errorf(\"PushMsg.send(push pushMsg:%v) error(%v)\", pushMsg, err)\n\t}\n\treturn\n}\n\n// BroadcastRoomMsg push a message to databus.\nfunc (d *Dao) BroadcastRoomMsg(c context.Context, op int32, room string, msg []byte) (err error) {\n\tpushMsg := &pb.PushMsg{\n\t\tType:      pb.PushMsg_ROOM,\n\t\tOperation: op,\n\t\tRoom:      room,\n\t\tMsg:       msg,\n\t}\n\tb, err := proto.Marshal(pushMsg)\n\tif err != nil {\n\t\treturn\n\t}\n\tm := &sarama.ProducerMessage{\n\t\tKey:   sarama.StringEncoder(room),\n\t\tTopic: d.c.Kafka.Topic,\n\t\tValue: sarama.ByteEncoder(b),\n\t}\n\tif _, _, err = d.kafkaPub.SendMessage(m); err != nil {\n\t\tlog.Errorf(\"PushMsg.send(broadcast_room pushMsg:%v) error(%v)\", pushMsg, err)\n\t}\n\treturn\n}\n\n// BroadcastMsg push a message to databus.\nfunc (d *Dao) BroadcastMsg(c context.Context, op, speed int32, msg []byte) (err error) {\n\tpushMsg := &pb.PushMsg{\n\t\tType:      pb.PushMsg_BROADCAST,\n\t\tOperation: op,\n\t\tSpeed:     speed,\n\t\tMsg:       msg,\n\t}\n\tb, err := proto.Marshal(pushMsg)\n\tif err != nil {\n\t\treturn\n\t}\n\tm := &sarama.ProducerMessage{\n\t\tKey:   sarama.StringEncoder(strconv.FormatInt(int64(op), 10)),\n\t\tTopic: d.c.Kafka.Topic,\n\t\tValue: sarama.ByteEncoder(b),\n\t}\n\tif _, _, err = d.kafkaPub.SendMessage(m); err != nil {\n\t\tlog.Errorf(\"PushMsg.send(broadcast pushMsg:%v) error(%v)\", pushMsg, err)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "internal/logic/dao/kafka_test.go",
    "content": "package dao\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDaoPushMsg(t *testing.T) {\n\tvar (\n\t\tc      = context.Background()\n\t\top     = int32(100)\n\t\tserver = \"test\"\n\t\tmsg    = []byte(\"msg\")\n\t\tkeys   = []string{\"key\"}\n\t)\n\terr := d.PushMsg(c, op, server, keys, msg)\n\tassert.Nil(t, err)\n}\n\nfunc TestDaoBroadcastRoomMsg(t *testing.T) {\n\tvar (\n\t\tc    = context.Background()\n\t\top   = int32(100)\n\t\troom = \"test://1\"\n\t\tmsg  = []byte(\"msg\")\n\t)\n\terr := d.BroadcastRoomMsg(c, op, room, msg)\n\tassert.Nil(t, err)\n}\n\nfunc TestDaoBroadcastMsg(t *testing.T) {\n\tvar (\n\t\tc     = context.Background()\n\t\top    = int32(100)\n\t\tspeed = int32(0)\n\t\tmsg   = []byte(\"\")\n\t)\n\terr := d.BroadcastMsg(c, op, speed, msg)\n\tassert.Nil(t, err)\n}\n"
  },
  {
    "path": "internal/logic/dao/redis.go",
    "content": "package dao\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n\tlog \"github.com/golang/glog\"\n\t\"github.com/gomodule/redigo/redis\"\n\n\t\"github.com/zhenjl/cityhash\"\n)\n\nconst (\n\t_prefixMidServer    = \"mid_%d\" // mid -> key:server\n\t_prefixKeyServer    = \"key_%s\" // key -> server\n\t_prefixServerOnline = \"ol_%s\"  // server -> online\n)\n\nfunc keyMidServer(mid int64) string {\n\treturn fmt.Sprintf(_prefixMidServer, mid)\n}\n\nfunc keyKeyServer(key string) string {\n\treturn fmt.Sprintf(_prefixKeyServer, key)\n}\n\nfunc keyServerOnline(key string) string {\n\treturn fmt.Sprintf(_prefixServerOnline, key)\n}\n\n// pingRedis check redis connection.\nfunc (d *Dao) pingRedis(c context.Context) (err error) {\n\tconn := d.redis.Get()\n\t_, err = conn.Do(\"SET\", \"PING\", \"PONG\")\n\tconn.Close()\n\treturn\n}\n\n// AddMapping add a mapping.\n// Mapping:\n//\tmid -> key_server\n//\tkey -> server\nfunc (d *Dao) AddMapping(c context.Context, mid int64, key, server string) (err error) {\n\tconn := d.redis.Get()\n\tdefer conn.Close()\n\tvar n = 2\n\tif mid > 0 {\n\t\tif err = conn.Send(\"HSET\", keyMidServer(mid), key, server); err != nil {\n\t\t\tlog.Errorf(\"conn.Send(HSET %d,%s,%s) error(%v)\", mid, server, key, err)\n\t\t\treturn\n\t\t}\n\t\tif err = conn.Send(\"EXPIRE\", keyMidServer(mid), d.redisExpire); err != nil {\n\t\t\tlog.Errorf(\"conn.Send(EXPIRE %d,%s,%s) error(%v)\", mid, key, server, err)\n\t\t\treturn\n\t\t}\n\t\tn += 2\n\t}\n\tif err = conn.Send(\"SET\", keyKeyServer(key), server); err != nil {\n\t\tlog.Errorf(\"conn.Send(HSET %d,%s,%s) error(%v)\", mid, server, key, err)\n\t\treturn\n\t}\n\tif err = conn.Send(\"EXPIRE\", keyKeyServer(key), d.redisExpire); err != nil {\n\t\tlog.Errorf(\"conn.Send(EXPIRE %d,%s,%s) error(%v)\", mid, key, server, err)\n\t\treturn\n\t}\n\tif err = conn.Flush(); err != nil {\n\t\tlog.Errorf(\"conn.Flush() error(%v)\", err)\n\t\treturn\n\t}\n\tfor i := 0; i < n; i++ {\n\t\tif _, err = conn.Receive(); err != nil {\n\t\t\tlog.Errorf(\"conn.Receive() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\n// ExpireMapping expire a mapping.\nfunc (d *Dao) ExpireMapping(c context.Context, mid int64, key string) (has bool, err error) {\n\tconn := d.redis.Get()\n\tdefer conn.Close()\n\tvar n = 1\n\tif mid > 0 {\n\t\tif err = conn.Send(\"EXPIRE\", keyMidServer(mid), d.redisExpire); err != nil {\n\t\t\tlog.Errorf(\"conn.Send(EXPIRE %d,%s) error(%v)\", mid, key, err)\n\t\t\treturn\n\t\t}\n\t\tn++\n\t}\n\tif err = conn.Send(\"EXPIRE\", keyKeyServer(key), d.redisExpire); err != nil {\n\t\tlog.Errorf(\"conn.Send(EXPIRE %d,%s) error(%v)\", mid, key, err)\n\t\treturn\n\t}\n\tif err = conn.Flush(); err != nil {\n\t\tlog.Errorf(\"conn.Flush() error(%v)\", err)\n\t\treturn\n\t}\n\tfor i := 0; i < n; i++ {\n\t\tif has, err = redis.Bool(conn.Receive()); err != nil {\n\t\t\tlog.Errorf(\"conn.Receive() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\n// DelMapping del a mapping.\nfunc (d *Dao) DelMapping(c context.Context, mid int64, key, server string) (has bool, err error) {\n\tconn := d.redis.Get()\n\tdefer conn.Close()\n\tn := 1\n\tif mid > 0 {\n\t\tif err = conn.Send(\"HDEL\", keyMidServer(mid), key); err != nil {\n\t\t\tlog.Errorf(\"conn.Send(HDEL %d,%s,%s) error(%v)\", mid, key, server, err)\n\t\t\treturn\n\t\t}\n\t\tn++\n\t}\n\tif err = conn.Send(\"DEL\", keyKeyServer(key)); err != nil {\n\t\tlog.Errorf(\"conn.Send(HDEL %d,%s,%s) error(%v)\", mid, key, server, err)\n\t\treturn\n\t}\n\tif err = conn.Flush(); err != nil {\n\t\tlog.Errorf(\"conn.Flush() error(%v)\", err)\n\t\treturn\n\t}\n\tfor i := 0; i < n; i++ {\n\t\tif has, err = redis.Bool(conn.Receive()); err != nil {\n\t\t\tlog.Errorf(\"conn.Receive() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\n// ServersByKeys get a server by key.\nfunc (d *Dao) ServersByKeys(c context.Context, keys []string) (res []string, err error) {\n\tconn := d.redis.Get()\n\tdefer conn.Close()\n\tvar args []interface{}\n\tfor _, key := range keys {\n\t\targs = append(args, keyKeyServer(key))\n\t}\n\tif res, err = redis.Strings(conn.Do(\"MGET\", args...)); err != nil {\n\t\tlog.Errorf(\"conn.Do(MGET %v) error(%v)\", args, err)\n\t}\n\treturn\n}\n\n// KeysByMids get a key server by mid.\nfunc (d *Dao) KeysByMids(c context.Context, mids []int64) (ress map[string]string, olMids []int64, err error) {\n\tconn := d.redis.Get()\n\tdefer conn.Close()\n\tress = make(map[string]string)\n\tfor _, mid := range mids {\n\t\tif err = conn.Send(\"HGETALL\", keyMidServer(mid)); err != nil {\n\t\t\tlog.Errorf(\"conn.Do(HGETALL %d) error(%v)\", mid, err)\n\t\t\treturn\n\t\t}\n\t}\n\tif err = conn.Flush(); err != nil {\n\t\tlog.Errorf(\"conn.Flush() error(%v)\", err)\n\t\treturn\n\t}\n\tfor idx := 0; idx < len(mids); idx++ {\n\t\tvar (\n\t\t\tres map[string]string\n\t\t)\n\t\tif res, err = redis.StringMap(conn.Receive()); err != nil {\n\t\t\tlog.Errorf(\"conn.Receive() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t\tif len(res) > 0 {\n\t\t\tolMids = append(olMids, mids[idx])\n\t\t}\n\t\tfor k, v := range res {\n\t\t\tress[k] = v\n\t\t}\n\t}\n\treturn\n}\n\n// AddServerOnline add a server online.\nfunc (d *Dao) AddServerOnline(c context.Context, server string, online *model.Online) (err error) {\n\troomsMap := map[uint32]map[string]int32{}\n\tfor room, count := range online.RoomCount {\n\t\trMap := roomsMap[cityhash.CityHash32([]byte(room), uint32(len(room)))%64]\n\t\tif rMap == nil {\n\t\t\trMap = make(map[string]int32)\n\t\t\troomsMap[cityhash.CityHash32([]byte(room), uint32(len(room)))%64] = rMap\n\t\t}\n\t\trMap[room] = count\n\t}\n\tkey := keyServerOnline(server)\n\tfor hashKey, value := range roomsMap {\n\t\terr = d.addServerOnline(c, key, strconv.FormatInt(int64(hashKey), 10), &model.Online{RoomCount: value, Server: online.Server, Updated: online.Updated})\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\nfunc (d *Dao) addServerOnline(c context.Context, key string, hashKey string, online *model.Online) (err error) {\n\tconn := d.redis.Get()\n\tdefer conn.Close()\n\tb, _ := json.Marshal(online)\n\tif err = conn.Send(\"HSET\", key, hashKey, b); err != nil {\n\t\tlog.Errorf(\"conn.Send(SET %s,%s) error(%v)\", key, hashKey, err)\n\t\treturn\n\t}\n\tif err = conn.Send(\"EXPIRE\", key, d.redisExpire); err != nil {\n\t\tlog.Errorf(\"conn.Send(EXPIRE %s) error(%v)\", key, err)\n\t\treturn\n\t}\n\tif err = conn.Flush(); err != nil {\n\t\tlog.Errorf(\"conn.Flush() error(%v)\", err)\n\t\treturn\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif _, err = conn.Receive(); err != nil {\n\t\t\tlog.Errorf(\"conn.Receive() error(%v)\", err)\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\n// ServerOnline get a server online.\nfunc (d *Dao) ServerOnline(c context.Context, server string) (online *model.Online, err error) {\n\tonline = &model.Online{RoomCount: map[string]int32{}}\n\tkey := keyServerOnline(server)\n\tfor i := 0; i < 64; i++ {\n\t\tol, err := d.serverOnline(c, key, strconv.FormatInt(int64(i), 10))\n\t\tif err == nil && ol != nil {\n\t\t\tonline.Server = ol.Server\n\t\t\tif ol.Updated > online.Updated {\n\t\t\t\tonline.Updated = ol.Updated\n\t\t\t}\n\t\t\tfor room, count := range ol.RoomCount {\n\t\t\t\tonline.RoomCount[room] = count\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\nfunc (d *Dao) serverOnline(c context.Context, key string, hashKey string) (online *model.Online, err error) {\n\tconn := d.redis.Get()\n\tdefer conn.Close()\n\tb, err := redis.Bytes(conn.Do(\"HGET\", key, hashKey))\n\tif err != nil {\n\t\tif err != redis.ErrNil {\n\t\t\tlog.Errorf(\"conn.Do(HGET %s %s) error(%v)\", key, hashKey, err)\n\t\t}\n\t\treturn\n\t}\n\tonline = new(model.Online)\n\tif err = json.Unmarshal(b, online); err != nil {\n\t\tlog.Errorf(\"serverOnline json.Unmarshal(%s) error(%v)\", b, err)\n\t\treturn\n\t}\n\treturn\n}\n\n// DelServerOnline del a server online.\nfunc (d *Dao) DelServerOnline(c context.Context, server string) (err error) {\n\tconn := d.redis.Get()\n\tdefer conn.Close()\n\tkey := keyServerOnline(server)\n\tif _, err = conn.Do(\"DEL\", key); err != nil {\n\t\tlog.Errorf(\"conn.Do(DEL %s) error(%v)\", key, err)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "internal/logic/dao/redis_test.go",
    "content": "package dao\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDaopingRedis(t *testing.T) {\n\terr := d.pingRedis(context.Background())\n\tassert.Nil(t, err)\n}\n\nfunc TestDaoAddMapping(t *testing.T) {\n\tvar (\n\t\tc      = context.Background()\n\t\tmid    = int64(1)\n\t\tkey    = \"test_key\"\n\t\tserver = \"test_server\"\n\t)\n\terr := d.AddMapping(c, 0, \"test\", server)\n\tassert.Nil(t, err)\n\terr = d.AddMapping(c, mid, key, server)\n\tassert.Nil(t, err)\n\n\thas, err := d.ExpireMapping(c, 0, \"test\")\n\tassert.Nil(t, err)\n\tassert.NotEqual(t, false, has)\n\thas, err = d.ExpireMapping(c, mid, key)\n\tassert.Nil(t, err)\n\tassert.NotEqual(t, false, has)\n\n\tres, err := d.ServersByKeys(c, []string{key})\n\tassert.Nil(t, err)\n\tassert.Equal(t, server, res[0])\n\n\tress, mids, err := d.KeysByMids(c, []int64{mid})\n\tassert.Nil(t, err)\n\tassert.Equal(t, server, ress[key])\n\tassert.Equal(t, mid, mids[0])\n\n\thas, err = d.DelMapping(c, 0, \"test\", server)\n\tassert.Nil(t, err)\n\tassert.NotEqual(t, false, has)\n\thas, err = d.DelMapping(c, mid, key, server)\n\tassert.Nil(t, err)\n\tassert.NotEqual(t, false, has)\n}\n\nfunc TestDaoAddServerOnline(t *testing.T) {\n\tvar (\n\t\tc      = context.Background()\n\t\tserver = \"test_server\"\n\t\tonline = &model.Online{\n\t\t\tRoomCount: map[string]int32{\"room\": 10},\n\t\t}\n\t)\n\terr := d.AddServerOnline(c, server, online)\n\tassert.Nil(t, err)\n\n\tr, err := d.ServerOnline(c, server)\n\tassert.Nil(t, err)\n\tassert.Equal(t, online.RoomCount[\"room\"], r.RoomCount[\"room\"])\n\n\terr = d.DelServerOnline(c, server)\n\tassert.Nil(t, err)\n}\n"
  },
  {
    "path": "internal/logic/dao/size_coverage.out",
    "content": "mode: set\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:21.31,29.2 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:31.52,38.16 7 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:41.2,41.12 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:38.16,39.13 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:44.42,49.36 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:49.36,56.18 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:59.4,59.20 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:56.18,58.5 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:65.29,67.2 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/dao.go:70.45,72.2 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:14.106,23.16 3 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:26.2,31.55 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:34.2,34.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:23.16,25.3 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:31.55,33.3 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:38.98,46.16 3 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:49.2,54.55 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:57.2,57.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:46.16,48.3 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:54.55,56.3 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:61.100,70.16 3 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:73.2,78.55 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:81.2,81.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:70.16,72.3 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/kafka.go:78.55,80.3 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:23.37,25.2 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:27.38,29.2 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:31.41,33.2 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:36.56,41.2 4 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:47.88,51.13 4 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:62.2,62.67 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:66.2,66.77 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:70.2,70.36 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:74.2,74.25 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:80.2,80.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:51.13,52.74 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:56.3,56.78 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:60.3,60.9 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:52.74,55.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:56.78,59.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:62.67,65.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:66.77,69.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:70.36,73.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:74.25,75.42 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:75.42,78.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:84.93,88.13 4 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:95.2,95.77 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:99.2,99.36 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:103.2,103.25 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:109.2,109.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:88.13,89.78 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:93.3,93.6 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:89.78,92.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:95.77,98.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:99.36,102.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:103.25,104.56 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:104.56,107.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:113.98,117.13 4 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:124.2,124.59 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:128.2,128.36 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:132.2,132.25 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:138.2,138.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:117.13,118.66 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:122.3,122.6 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:118.66,121.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:124.59,127.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:128.36,131.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:132.25,133.56 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:133.56,136.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:142.89,146.27 4 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:149.2,149.68 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:152.2,152.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:146.27,148.3 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:149.68,151.3 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:156.111,160.27 4 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:166.2,166.36 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:170.2,170.39 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:185.2,185.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:160.27,161.64 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:161.64,164.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:166.36,169.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:170.39,174.61 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:178.3,178.19 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:181.3,181.25 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:174.61,177.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:178.19,180.4 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:181.25,183.4 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:189.99,191.44 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:199.2,200.39 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:206.2,206.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:191.44,193.18 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:197.3,197.21 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:193.18,196.4 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:200.39,202.17 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:202.17,204.4 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:209.112,213.58 4 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:217.2,217.63 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:221.2,221.36 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:225.2,225.25 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:231.2,231.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:213.58,216.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:217.63,220.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:221.36,224.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:225.25,226.42 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:226.42,229.4 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:235.96,238.26 3 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:250.2,250.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:238.26,240.30 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:240.30,242.35 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:245.4,245.42 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:242.35,244.5 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:245.42,247.5 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:253.109,257.16 4 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:263.2,264.49 2 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:268.2,268.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:257.16,258.26 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:261.3,261.9 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:258.26,260.4 1 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:264.49,267.3 2 0\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:272.77,276.46 4 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:279.2,279.8 1 1\ngithub.com/Terry-Mao/goim/internal/logic/dao/redis.go:276.46,278.3 1 0\n"
  },
  {
    "path": "internal/logic/grpc/server.go",
    "content": "package grpc\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\tpb \"github.com/Terry-Mao/goim/api/logic\"\n\t\"github.com/Terry-Mao/goim/internal/logic\"\n\t\"github.com/Terry-Mao/goim/internal/logic/conf\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/keepalive\"\n\n\t// use gzip decoder\n\t_ \"google.golang.org/grpc/encoding/gzip\"\n)\n\n// New logic grpc server\nfunc New(c *conf.RPCServer, l *logic.Logic) *grpc.Server {\n\tkeepParams := grpc.KeepaliveParams(keepalive.ServerParameters{\n\t\tMaxConnectionIdle:     time.Duration(c.IdleTimeout),\n\t\tMaxConnectionAgeGrace: time.Duration(c.ForceCloseWait),\n\t\tTime:                  time.Duration(c.KeepAliveInterval),\n\t\tTimeout:               time.Duration(c.KeepAliveTimeout),\n\t\tMaxConnectionAge:      time.Duration(c.MaxLifeTime),\n\t})\n\tsrv := grpc.NewServer(keepParams)\n\tpb.RegisterLogicServer(srv, &server{l})\n\tlis, err := net.Listen(c.Network, c.Addr)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tgo func() {\n\t\tif err := srv.Serve(lis); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\treturn srv\n}\n\ntype server struct {\n\tsrv *logic.Logic\n}\n\nvar _ pb.LogicServer = &server{}\n\n// Connect connect a conn.\nfunc (s *server) Connect(ctx context.Context, req *pb.ConnectReq) (*pb.ConnectReply, error) {\n\tmid, key, room, accepts, hb, err := s.srv.Connect(ctx, req.Server, req.Cookie, req.Token)\n\tif err != nil {\n\t\treturn &pb.ConnectReply{}, err\n\t}\n\treturn &pb.ConnectReply{Mid: mid, Key: key, RoomID: room, Accepts: accepts, Heartbeat: hb}, nil\n}\n\n// Disconnect disconnect a conn.\nfunc (s *server) Disconnect(ctx context.Context, req *pb.DisconnectReq) (*pb.DisconnectReply, error) {\n\thas, err := s.srv.Disconnect(ctx, req.Mid, req.Key, req.Server)\n\tif err != nil {\n\t\treturn &pb.DisconnectReply{}, err\n\t}\n\treturn &pb.DisconnectReply{Has: has}, nil\n}\n\n// Heartbeat beartbeat a conn.\nfunc (s *server) Heartbeat(ctx context.Context, req *pb.HeartbeatReq) (*pb.HeartbeatReply, error) {\n\tif err := s.srv.Heartbeat(ctx, req.Mid, req.Key, req.Server); err != nil {\n\t\treturn &pb.HeartbeatReply{}, err\n\t}\n\treturn &pb.HeartbeatReply{}, nil\n}\n\n// RenewOnline renew server online.\nfunc (s *server) RenewOnline(ctx context.Context, req *pb.OnlineReq) (*pb.OnlineReply, error) {\n\tallRoomCount, err := s.srv.RenewOnline(ctx, req.Server, req.RoomCount)\n\tif err != nil {\n\t\treturn &pb.OnlineReply{}, err\n\t}\n\treturn &pb.OnlineReply{AllRoomCount: allRoomCount}, nil\n}\n\n// Receive receive a message.\nfunc (s *server) Receive(ctx context.Context, req *pb.ReceiveReq) (*pb.ReceiveReply, error) {\n\tif err := s.srv.Receive(ctx, req.Mid, req.Proto); err != nil {\n\t\treturn &pb.ReceiveReply{}, err\n\t}\n\treturn &pb.ReceiveReply{}, nil\n}\n\n// nodes return nodes.\nfunc (s *server) Nodes(ctx context.Context, req *pb.NodesReq) (*pb.NodesReply, error) {\n\treturn s.srv.NodesWeighted(ctx, req.Platform, req.ClientIP), nil\n}\n"
  },
  {
    "path": "internal/logic/http/middleware.go",
    "content": "package http\n\nimport (\n\t\"fmt\"\n\t\"net/http/httputil\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\tlog \"github.com/golang/glog\"\n)\n\nfunc loggerHandler(c *gin.Context) {\n\t// Start timer\n\tstart := time.Now()\n\tpath := c.Request.URL.Path\n\traw := c.Request.URL.RawQuery\n\tmethod := c.Request.Method\n\n\t// Process request\n\tc.Next()\n\n\t// Stop timer\n\tend := time.Now()\n\tlatency := end.Sub(start)\n\tstatusCode := c.Writer.Status()\n\tecode := c.GetInt(contextErrCode)\n\tclientIP := c.ClientIP()\n\tif raw != \"\" {\n\t\tpath = path + \"?\" + raw\n\t}\n\tlog.Infof(\"METHOD:%s | PATH:%s | CODE:%d | IP:%s | TIME:%d | ECODE:%d\", method, path, statusCode, clientIP, latency/time.Millisecond, ecode)\n}\n\nfunc recoverHandler(c *gin.Context) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tconst size = 64 << 10\n\t\t\tbuf := make([]byte, size)\n\t\t\tbuf = buf[:runtime.Stack(buf, false)]\n\t\t\thttprequest, _ := httputil.DumpRequest(c.Request, false)\n\t\t\tpnc := fmt.Sprintf(\"[Recovery] %s panic recovered:\\n%s\\n%s\\n%s\", time.Now().Format(\"2006-01-02 15:04:05\"), string(httprequest), err, buf)\n\t\t\tfmt.Print(pnc)\n\t\t\tlog.Error(pnc)\n\t\t\tc.AbortWithStatus(500)\n\t\t}\n\t}()\n\tc.Next()\n}\n"
  },
  {
    "path": "internal/logic/http/nodes.go",
    "content": "package http\n\nimport (\n\t\"context\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc (s *Server) nodesWeighted(c *gin.Context) {\n\tvar arg struct {\n\t\tPlatform string `form:\"platform\"`\n\t}\n\tif err := c.BindQuery(&arg); err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\tres := s.logic.NodesWeighted(c, arg.Platform, c.ClientIP())\n\tresult(c, res, OK)\n}\n\nfunc (s *Server) nodesInstances(c *gin.Context) {\n\tres := s.logic.NodesInstances(context.TODO())\n\tresult(c, res, OK)\n}\n"
  },
  {
    "path": "internal/logic/http/online.go",
    "content": "package http\n\nimport (\n\t\"context\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc (s *Server) onlineTop(c *gin.Context) {\n\tvar arg struct {\n\t\tType  string `form:\"type\" binding:\"required\"`\n\t\tLimit int    `form:\"limit\" binding:\"required\"`\n\t}\n\tif err := c.BindQuery(&arg); err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\tres, err := s.logic.OnlineTop(c, arg.Type, arg.Limit)\n\tif err != nil {\n\t\tresult(c, nil, RequestErr)\n\t\treturn\n\t}\n\tresult(c, res, OK)\n}\n\nfunc (s *Server) onlineRoom(c *gin.Context) {\n\tvar arg struct {\n\t\tType  string   `form:\"type\" binding:\"required\"`\n\t\tRooms []string `form:\"rooms\" binding:\"required\"`\n\t}\n\tif err := c.BindQuery(&arg); err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\tres, err := s.logic.OnlineRoom(c, arg.Type, arg.Rooms)\n\tif err != nil {\n\t\tresult(c, nil, RequestErr)\n\t\treturn\n\t}\n\tresult(c, res, OK)\n}\n\nfunc (s *Server) onlineTotal(c *gin.Context) {\n\tipCount, connCount := s.logic.OnlineTotal(context.TODO())\n\tres := map[string]interface{}{\n\t\t\"ip_count\":   ipCount,\n\t\t\"conn_count\": connCount,\n\t}\n\tresult(c, res, OK)\n}\n"
  },
  {
    "path": "internal/logic/http/push.go",
    "content": "package http\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc (s *Server) pushKeys(c *gin.Context) {\n\tvar arg struct {\n\t\tOp   int32    `form:\"operation\"`\n\t\tKeys []string `form:\"keys\"`\n\t}\n\tif err := c.BindQuery(&arg); err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\t// read message\n\tmsg, err := ioutil.ReadAll(c.Request.Body)\n\tif err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\tif err = s.logic.PushKeys(context.TODO(), arg.Op, arg.Keys, msg); err != nil {\n\t\tresult(c, nil, RequestErr)\n\t\treturn\n\t}\n\tresult(c, nil, OK)\n}\n\nfunc (s *Server) pushMids(c *gin.Context) {\n\tvar arg struct {\n\t\tOp   int32   `form:\"operation\"`\n\t\tMids []int64 `form:\"mids\"`\n\t}\n\tif err := c.BindQuery(&arg); err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\t// read message\n\tmsg, err := ioutil.ReadAll(c.Request.Body)\n\tif err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\tif err = s.logic.PushMids(context.TODO(), arg.Op, arg.Mids, msg); err != nil {\n\t\terrors(c, ServerErr, err.Error())\n\t\treturn\n\t}\n\tresult(c, nil, OK)\n}\n\nfunc (s *Server) pushRoom(c *gin.Context) {\n\tvar arg struct {\n\t\tOp   int32  `form:\"operation\" binding:\"required\"`\n\t\tType string `form:\"type\" binding:\"required\"`\n\t\tRoom string `form:\"room\" binding:\"required\"`\n\t}\n\tif err := c.BindQuery(&arg); err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\t// read message\n\tmsg, err := ioutil.ReadAll(c.Request.Body)\n\tif err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\tif err = s.logic.PushRoom(c, arg.Op, arg.Type, arg.Room, msg); err != nil {\n\t\terrors(c, ServerErr, err.Error())\n\t\treturn\n\t}\n\tresult(c, nil, OK)\n}\n\nfunc (s *Server) pushAll(c *gin.Context) {\n\tvar arg struct {\n\t\tOp    int32 `form:\"operation\" binding:\"required\"`\n\t\tSpeed int32 `form:\"speed\"`\n\t}\n\tif err := c.BindQuery(&arg); err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\tmsg, err := ioutil.ReadAll(c.Request.Body)\n\tif err != nil {\n\t\terrors(c, RequestErr, err.Error())\n\t\treturn\n\t}\n\tif err = s.logic.PushAll(c, arg.Op, arg.Speed, msg); err != nil {\n\t\terrors(c, ServerErr, err.Error())\n\t\treturn\n\t}\n\tresult(c, nil, OK)\n}\n"
  },
  {
    "path": "internal/logic/http/result.go",
    "content": "package http\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\nconst (\n\t// OK ok\n\tOK = 0\n\t// RequestErr request error\n\tRequestErr = -400\n\t// ServerErr server error\n\tServerErr = -500\n\n\tcontextErrCode = \"context/err/code\"\n)\n\ntype resp struct {\n\tCode    int         `json:\"code\"`\n\tMessage string      `json:\"message\"`\n\tData    interface{} `json:\"data,omitempty\"`\n}\n\nfunc errors(c *gin.Context, code int, msg string) {\n\tc.Set(contextErrCode, code)\n\tc.JSON(200, resp{\n\t\tCode:    code,\n\t\tMessage: msg,\n\t})\n}\n\nfunc result(c *gin.Context, data interface{}, code int) {\n\tc.Set(contextErrCode, code)\n\tc.JSON(200, resp{\n\t\tCode: code,\n\t\tData: data,\n\t})\n}\n"
  },
  {
    "path": "internal/logic/http/server.go",
    "content": "package http\n\nimport (\n\t\"github.com/Terry-Mao/goim/internal/logic\"\n\t\"github.com/Terry-Mao/goim/internal/logic/conf\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// Server is http server.\ntype Server struct {\n\tengine *gin.Engine\n\tlogic  *logic.Logic\n}\n\n// New new a http server.\nfunc New(c *conf.HTTPServer, l *logic.Logic) *Server {\n\tengine := gin.New()\n\tengine.Use(loggerHandler, recoverHandler)\n\tgo func() {\n\t\tif err := engine.Run(c.Addr); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\ts := &Server{\n\t\tengine: engine,\n\t\tlogic:  l,\n\t}\n\ts.initRouter()\n\treturn s\n}\n\nfunc (s *Server) initRouter() {\n\tgroup := s.engine.Group(\"/goim\")\n\tgroup.POST(\"/push/keys\", s.pushKeys)\n\tgroup.POST(\"/push/mids\", s.pushMids)\n\tgroup.POST(\"/push/room\", s.pushRoom)\n\tgroup.POST(\"/push/all\", s.pushAll)\n\tgroup.GET(\"/online/top\", s.onlineTop)\n\tgroup.GET(\"/online/room\", s.onlineRoom)\n\tgroup.GET(\"/online/total\", s.onlineTotal)\n\tgroup.GET(\"/nodes/weighted\", s.nodesWeighted)\n\tgroup.GET(\"/nodes/instances\", s.nodesInstances)\n}\n\n// Close close the server.\nfunc (s *Server) Close() {\n\n}\n"
  },
  {
    "path": "internal/logic/logic.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/Terry-Mao/goim/internal/logic/conf\"\n\t\"github.com/Terry-Mao/goim/internal/logic/dao\"\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n\t\"github.com/bilibili/discovery/naming\"\n\tlog \"github.com/golang/glog\"\n)\n\nconst (\n\t_onlineTick     = time.Second * 10\n\t_onlineDeadline = time.Minute * 5\n)\n\n// Logic struct\ntype Logic struct {\n\tc   *conf.Config\n\tdis *naming.Discovery\n\tdao *dao.Dao\n\t// online\n\ttotalIPs   int64\n\ttotalConns int64\n\troomCount  map[string]int32\n\t// load balancer\n\tnodes        []*naming.Instance\n\tloadBalancer *LoadBalancer\n\tregions      map[string]string // province -> region\n}\n\n// New init\nfunc New(c *conf.Config) (l *Logic) {\n\tl = &Logic{\n\t\tc:            c,\n\t\tdao:          dao.New(c),\n\t\tdis:          naming.New(c.Discovery),\n\t\tloadBalancer: NewLoadBalancer(),\n\t\tregions:      make(map[string]string),\n\t}\n\tl.initRegions()\n\tl.initNodes()\n\t_ = l.loadOnline()\n\tgo l.onlineproc()\n\treturn l\n}\n\n// Ping ping resources is ok.\nfunc (l *Logic) Ping(c context.Context) (err error) {\n\treturn l.dao.Ping(c)\n}\n\n// Close close resources.\nfunc (l *Logic) Close() {\n\tl.dao.Close()\n}\n\nfunc (l *Logic) initRegions() {\n\tfor region, ps := range l.c.Regions {\n\t\tfor _, province := range ps {\n\t\t\tl.regions[province] = region\n\t\t}\n\t}\n}\n\nfunc (l *Logic) initNodes() {\n\tres := l.dis.Build(\"goim.comet\")\n\tevent := res.Watch()\n\tselect {\n\tcase _, ok := <-event:\n\t\tif ok {\n\t\t\tl.newNodes(res)\n\t\t} else {\n\t\t\tpanic(\"discovery watch failed\")\n\t\t}\n\tcase <-time.After(10 * time.Second):\n\t\tlog.Error(\"discovery start timeout\")\n\t}\n\tgo func() {\n\t\tfor {\n\t\t\tif _, ok := <-event; !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tl.newNodes(res)\n\t\t}\n\t}()\n}\n\nfunc (l *Logic) newNodes(res naming.Resolver) {\n\tif zoneIns, ok := res.Fetch(); ok {\n\t\tvar (\n\t\t\ttotalConns int64\n\t\t\ttotalIPs   int64\n\t\t\tallIns     []*naming.Instance\n\t\t)\n\t\tfor _, zins := range zoneIns.Instances {\n\t\t\tfor _, ins := range zins {\n\t\t\t\tif ins.Metadata == nil {\n\t\t\t\t\tlog.Errorf(\"node instance metadata is empty(%+v)\", ins)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toffline, err := strconv.ParseBool(ins.Metadata[model.MetaOffline])\n\t\t\t\tif err != nil || offline {\n\t\t\t\t\tlog.Warningf(\"strconv.ParseBool(offline:%t) error(%v)\", offline, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconns, err := strconv.ParseInt(ins.Metadata[model.MetaConnCount], 10, 32)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Errorf(\"strconv.ParseInt(conns:%d) error(%v)\", conns, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tips, err := strconv.ParseInt(ins.Metadata[model.MetaIPCount], 10, 32)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Errorf(\"strconv.ParseInt(ips:%d) error(%v)\", ips, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttotalConns += conns\n\t\t\t\ttotalIPs += ips\n\t\t\t\tallIns = append(allIns, ins)\n\t\t\t}\n\t\t}\n\t\tl.totalConns = totalConns\n\t\tl.totalIPs = totalIPs\n\t\tl.nodes = allIns\n\t\tl.loadBalancer.Update(allIns)\n\t}\n}\n\nfunc (l *Logic) onlineproc() {\n\tfor {\n\t\ttime.Sleep(_onlineTick)\n\t\tif err := l.loadOnline(); err != nil {\n\t\t\tlog.Errorf(\"onlineproc error(%v)\", err)\n\t\t}\n\t}\n}\n\nfunc (l *Logic) loadOnline() (err error) {\n\tvar (\n\t\troomCount = make(map[string]int32)\n\t)\n\tfor _, server := range l.nodes {\n\t\tvar online *model.Online\n\t\tonline, err = l.dao.ServerOnline(context.Background(), server.Hostname)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif time.Since(time.Unix(online.Updated, 0)) > _onlineDeadline {\n\t\t\t_ = l.dao.DelServerOnline(context.Background(), server.Hostname)\n\t\t\tcontinue\n\t\t}\n\t\tfor roomID, count := range online.RoomCount {\n\t\t\troomCount[roomID] += count\n\t\t}\n\t}\n\tl.roomCount = roomCount\n\treturn\n}\n"
  },
  {
    "path": "internal/logic/logic_test.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/Terry-Mao/goim/internal/logic/conf\"\n)\n\nvar (\n\tlg *Logic\n)\n\nfunc TestMain(m *testing.M) {\n\tif err := flag.Set(\"conf\", \"../../cmd/logic/logic-example.toml\"); err != nil {\n\t\tpanic(err)\n\t}\n\tflag.Parse()\n\tif err := conf.Init(); err != nil {\n\t\tpanic(err)\n\t}\n\tlg = New(conf.Conf)\n\tif err := lg.Ping(context.TODO()); err != nil {\n\t\tpanic(err)\n\t}\n\tos.Exit(m.Run())\n}\n"
  },
  {
    "path": "internal/logic/model/metadata.go",
    "content": "package model\n\nconst (\n\t// MetaWeight meta weight\n\tMetaWeight = \"weight\"\n\t// MetaOffline meta offline\n\tMetaOffline = \"offline\"\n\t// MetaAddrs meta public ip addrs\n\tMetaAddrs = \"addrs\"\n\t// MetaIPCount meta ip count\n\tMetaIPCount = \"ip_count\"\n\t// MetaConnCount meta conn count\n\tMetaConnCount = \"conn_count\"\n\n\t// PlatformWeb platform web\n\tPlatformWeb = \"web\"\n)\n"
  },
  {
    "path": "internal/logic/model/online.go",
    "content": "package model\n\n// Online ip and room online.\ntype Online struct {\n\tServer    string           `json:\"server\"`\n\tRoomCount map[string]int32 `json:\"room_count\"`\n\tUpdated   int64            `json:\"updated\"`\n}\n\n// Top top sorted.\ntype Top struct {\n\tRoomID string `json:\"room_id\"`\n\tCount  int32  `json:\"count\"`\n}\n"
  },
  {
    "path": "internal/logic/model/room.go",
    "content": "package model\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n)\n\n// EncodeRoomKey encode a room key.\nfunc EncodeRoomKey(typ string, room string) string {\n\treturn fmt.Sprintf(\"%s://%s\", typ, room)\n}\n\n// DecodeRoomKey decode room key.\nfunc DecodeRoomKey(key string) (string, string, error) {\n\tu, err := url.Parse(key)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\treturn u.Scheme, u.Host, nil\n}\n"
  },
  {
    "path": "internal/logic/nodes.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tpb \"github.com/Terry-Mao/goim/api/logic\"\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n\t\"github.com/bilibili/discovery/naming\"\n\tlog \"github.com/golang/glog\"\n)\n\n// NodesInstances get servers info.\nfunc (l *Logic) NodesInstances(c context.Context) (res []*naming.Instance) {\n\treturn l.nodes\n}\n\n// NodesWeighted get node list.\nfunc (l *Logic) NodesWeighted(c context.Context, platform, clientIP string) *pb.NodesReply {\n\treply := &pb.NodesReply{\n\t\tDomain:       l.c.Node.DefaultDomain,\n\t\tTcpPort:      int32(l.c.Node.TCPPort),\n\t\tWsPort:       int32(l.c.Node.WSPort),\n\t\tWssPort:      int32(l.c.Node.WSSPort),\n\t\tHeartbeat:    int32(time.Duration(l.c.Node.Heartbeat) / time.Second),\n\t\tHeartbeatMax: int32(l.c.Node.HeartbeatMax),\n\t\tBackoff: &pb.Backoff{\n\t\t\tMaxDelay:  l.c.Backoff.MaxDelay,\n\t\t\tBaseDelay: l.c.Backoff.BaseDelay,\n\t\t\tFactor:    l.c.Backoff.Factor,\n\t\t\tJitter:    l.c.Backoff.Jitter,\n\t\t},\n\t}\n\tdomains, addrs := l.nodeAddrs(c, clientIP)\n\tif platform == model.PlatformWeb {\n\t\treply.Nodes = domains\n\t} else {\n\t\treply.Nodes = addrs\n\t}\n\tif len(reply.Nodes) == 0 {\n\t\treply.Nodes = []string{l.c.Node.DefaultDomain}\n\t}\n\treturn reply\n}\n\nfunc (l *Logic) nodeAddrs(c context.Context, clientIP string) (domains, addrs []string) {\n\tvar (\n\t\tregion string\n\t)\n\tprovince, err := l.location(c, clientIP)\n\tif err == nil {\n\t\tregion = l.regions[province]\n\t}\n\tlog.Infof(\"nodeAddrs clientIP:%s region:%s province:%s domains:%v addrs:%v\", clientIP, region, province, domains, addrs)\n\treturn l.loadBalancer.NodeAddrs(region, l.c.Node.HostDomain, l.c.Node.RegionWeight)\n}\n\n// location find a geolocation of an IP address including province, region and country.\nfunc (l *Logic) location(c context.Context, clientIP string) (province string, err error) {\n\t// province: config mapping\n\treturn\n}\n"
  },
  {
    "path": "internal/logic/nodes_test.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/bilibili/discovery/naming\"\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNodes(t *testing.T) {\n\tvar (\n\t\tc        = context.TODO()\n\t\tclientIP = \"127.0.0.1\"\n\t)\n\tlg.nodes = make([]*naming.Instance, 0)\n\tins := lg.NodesInstances(c)\n\tassert.NotNil(t, ins)\n\tnodes := lg.NodesWeighted(c, model.PlatformWeb, clientIP)\n\tassert.NotNil(t, nodes)\n\tnodes = lg.NodesWeighted(c, \"android\", clientIP)\n\tassert.NotNil(t, nodes)\n}\n"
  },
  {
    "path": "internal/logic/online.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n)\n\nvar (\n\t_emptyTops = make([]*model.Top, 0)\n)\n\n// OnlineTop get the top online.\nfunc (l *Logic) OnlineTop(c context.Context, typ string, n int) (tops []*model.Top, err error) {\n\tfor key, cnt := range l.roomCount {\n\t\tif strings.HasPrefix(key, typ) {\n\t\t\t_, roomID, err := model.DecodeRoomKey(key)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttop := &model.Top{\n\t\t\t\tRoomID: roomID,\n\t\t\t\tCount:  cnt,\n\t\t\t}\n\t\t\ttops = append(tops, top)\n\t\t}\n\t}\n\tsort.Slice(tops, func(i, j int) bool {\n\t\treturn tops[i].Count > tops[j].Count\n\t})\n\tif len(tops) > n {\n\t\ttops = tops[:n]\n\t}\n\tif len(tops) == 0 {\n\t\ttops = _emptyTops\n\t}\n\treturn\n}\n\n// OnlineRoom get rooms online.\nfunc (l *Logic) OnlineRoom(c context.Context, typ string, rooms []string) (res map[string]int32, err error) {\n\tres = make(map[string]int32, len(rooms))\n\tfor _, room := range rooms {\n\t\tres[room] = l.roomCount[model.EncodeRoomKey(typ, room)]\n\t}\n\treturn\n}\n\n// OnlineTotal get all online.\nfunc (l *Logic) OnlineTotal(c context.Context) (int64, int64) {\n\treturn l.totalIPs, l.totalConns\n}\n"
  },
  {
    "path": "internal/logic/online_test.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestOnline(t *testing.T) {\n\tvar (\n\t\tc     = context.TODO()\n\t\ttyp   = \"test\"\n\t\tn     = 2\n\t\trooms = []string{\"room_01\", \"room_02\", \"room_03\"}\n\t)\n\tlg.totalIPs = 100\n\tlg.totalConns = 200\n\tlg.roomCount = map[string]int32{\n\t\t\"test://room_01\": 100,\n\t\t\"test://room_02\": 200,\n\t\t\"test://room_03\": 300,\n\t}\n\ttops, err := lg.OnlineTop(c, typ, n)\n\tassert.Nil(t, err)\n\tassert.Equal(t, len(tops), 2)\n\tonlines, err := lg.OnlineRoom(c, typ, rooms)\n\tassert.Nil(t, err)\n\tassert.Equal(t, onlines[\"room_01\"], int32(100))\n\tassert.Equal(t, onlines[\"room_02\"], int32(200))\n\tassert.Equal(t, onlines[\"room_03\"], int32(300))\n\tips, conns := lg.OnlineTotal(c)\n\tassert.Equal(t, ips, int64(100))\n\tassert.Equal(t, conns, int64(200))\n}\n"
  },
  {
    "path": "internal/logic/push.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\n\t\"github.com/Terry-Mao/goim/internal/logic/model\"\n\n\tlog \"github.com/golang/glog\"\n)\n\n// PushKeys push a message by keys.\nfunc (l *Logic) PushKeys(c context.Context, op int32, keys []string, msg []byte) (err error) {\n\tservers, err := l.dao.ServersByKeys(c, keys)\n\tif err != nil {\n\t\treturn\n\t}\n\tpushKeys := make(map[string][]string)\n\tfor i, key := range keys {\n\t\tserver := servers[i]\n\t\tif server != \"\" && key != \"\" {\n\t\t\tpushKeys[server] = append(pushKeys[server], key)\n\t\t}\n\t}\n\tfor server := range pushKeys {\n\t\tif err = l.dao.PushMsg(c, op, server, pushKeys[server], msg); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\n// PushMids push a message by mid.\nfunc (l *Logic) PushMids(c context.Context, op int32, mids []int64, msg []byte) (err error) {\n\tkeyServers, _, err := l.dao.KeysByMids(c, mids)\n\tif err != nil {\n\t\treturn\n\t}\n\tkeys := make(map[string][]string)\n\tfor key, server := range keyServers {\n\t\tif key == \"\" || server == \"\" {\n\t\t\tlog.Warningf(\"push key:%s server:%s is empty\", key, server)\n\t\t\tcontinue\n\t\t}\n\t\tkeys[server] = append(keys[server], key)\n\t}\n\tfor server, keys := range keys {\n\t\tif err = l.dao.PushMsg(c, op, server, keys, msg); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\n// PushRoom push a message by room.\nfunc (l *Logic) PushRoom(c context.Context, op int32, typ, room string, msg []byte) (err error) {\n\treturn l.dao.BroadcastRoomMsg(c, op, model.EncodeRoomKey(typ, room), msg)\n}\n\n// PushAll push a message to all.\nfunc (l *Logic) PushAll(c context.Context, op, speed int32, msg []byte) (err error) {\n\treturn l.dao.BroadcastMsg(c, op, speed, msg)\n}\n"
  },
  {
    "path": "internal/logic/push_test.go",
    "content": "package logic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestPushKeys(t *testing.T) {\n\tvar (\n\t\tc    = context.TODO()\n\t\top   = int32(100)\n\t\tkeys = []string{\"test_key\"}\n\t\tmsg  = []byte(\"hello\")\n\t)\n\terr := lg.PushKeys(c, op, keys, msg)\n\tassert.Nil(t, err)\n}\n\nfunc TestPushMids(t *testing.T) {\n\tvar (\n\t\tc    = context.TODO()\n\t\top   = int32(100)\n\t\tmids = []int64{1, 2, 3}\n\t\tmsg  = []byte(\"hello\")\n\t)\n\terr := lg.PushMids(c, op, mids, msg)\n\tassert.Nil(t, err)\n}\n\nfunc TestPushRoom(t *testing.T) {\n\tvar (\n\t\tc    = context.TODO()\n\t\top   = int32(100)\n\t\ttyp  = \"test\"\n\t\troom = \"test_room\"\n\t\tmsg  = []byte(\"hello\")\n\t)\n\terr := lg.PushRoom(c, op, typ, room, msg)\n\tassert.Nil(t, err)\n}\n\nfunc TestPushAll(t *testing.T) {\n\tvar (\n\t\tc     = context.TODO()\n\t\top    = int32(100)\n\t\tspeed = int32(100)\n\t\tmsg   = []byte(\"hello\")\n\t)\n\terr := lg.PushAll(c, op, speed, msg)\n\tassert.Nil(t, err)\n}\n"
  },
  {
    "path": "pkg/bufio/bufio.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// Package bufio implements buffered I/O.  It wraps an io.Reader or io.Writer\n// object, creating another object (Reader or Writer) that also implements\n// the interface but provides buffering and some help for textual I/O.\npackage bufio\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n)\n\nconst (\n\tdefaultBufSize = 4096\n)\n\nvar (\n\t// ErrInvalidUnreadByte invalid use of UnreadByete\n\tErrInvalidUnreadByte = errors.New(\"bufio: invalid use of UnreadByte\")\n\t// ErrInvalidUnreadRune invalid use of UnreadRune\n\tErrInvalidUnreadRune = errors.New(\"bufio: invalid use of UnreadRune\")\n\t// ErrBufferFull buffer full\n\tErrBufferFull = errors.New(\"bufio: buffer full\")\n\t// ErrNegativeCount negative count\n\tErrNegativeCount = errors.New(\"bufio: negative count\")\n)\n\n// Buffered input.\n\n// Reader implements buffering for an io.Reader object.\ntype Reader struct {\n\tbuf  []byte\n\trd   io.Reader // reader provided by the client\n\tr, w int       // buf read and write positions\n\terr  error\n}\n\nconst minReadBufferSize = 16\nconst maxConsecutiveEmptyReads = 100\n\n// NewReaderSize returns a new Reader whose buffer has at least the specified\n// size. If the argument io.Reader is already a Reader with large enough\n// size, it returns the underlying Reader.\nfunc NewReaderSize(rd io.Reader, size int) *Reader {\n\t// Is it already a Reader?\n\tb, ok := rd.(*Reader)\n\tif ok && len(b.buf) >= size {\n\t\treturn b\n\t}\n\tif size < minReadBufferSize {\n\t\tsize = minReadBufferSize\n\t}\n\tr := new(Reader)\n\tr.reset(make([]byte, size), rd)\n\treturn r\n}\n\n// NewReader returns a new Reader whose buffer has the default size.\nfunc NewReader(rd io.Reader) *Reader {\n\treturn NewReaderSize(rd, defaultBufSize)\n}\n\n// Reset discards any buffered data, resets all state, and switches\n// the buffered reader to read from r.\nfunc (b *Reader) Reset(r io.Reader) {\n\tb.reset(b.buf, r)\n}\n\n// ResetBuffer discards any buffered data, resets all state, and switches\n// the buffered reader to read from r.\nfunc (b *Reader) ResetBuffer(r io.Reader, buf []byte) {\n\tb.reset(buf, r)\n}\n\nfunc (b *Reader) reset(buf []byte, r io.Reader) {\n\t*b = Reader{\n\t\tbuf: buf,\n\t\trd:  r,\n\t}\n}\n\nvar errNegativeRead = errors.New(\"bufio: reader returned negative count from Read\")\n\n// fill reads a new chunk into the buffer.\nfunc (b *Reader) fill() {\n\t// Slide existing data to beginning.\n\tif b.r > 0 {\n\t\tcopy(b.buf, b.buf[b.r:b.w])\n\t\tb.w -= b.r\n\t\tb.r = 0\n\t}\n\n\tif b.w >= len(b.buf) {\n\t\tpanic(\"bufio: tried to fill full buffer\")\n\t}\n\n\t// Read new data: try a limited number of times.\n\tfor i := maxConsecutiveEmptyReads; i > 0; i-- {\n\t\tn, err := b.rd.Read(b.buf[b.w:])\n\t\tif n < 0 {\n\t\t\tpanic(errNegativeRead)\n\t\t}\n\t\tb.w += n\n\t\tif err != nil {\n\t\t\tb.err = err\n\t\t\treturn\n\t\t}\n\t\tif n > 0 {\n\t\t\treturn\n\t\t}\n\t}\n\tb.err = io.ErrNoProgress\n}\n\nfunc (b *Reader) readErr() error {\n\terr := b.err\n\tb.err = nil\n\treturn err\n}\n\n// Peek returns the next n bytes without advancing the reader. The bytes stop\n// being valid at the next read call. If Peek returns fewer than n bytes, it\n// also returns an error explaining why the read is short. The error is\n// ErrBufferFull if n is larger than b's buffer size.\nfunc (b *Reader) Peek(n int) ([]byte, error) {\n\tif n < 0 {\n\t\treturn nil, ErrNegativeCount\n\t}\n\tif n > len(b.buf) {\n\t\treturn nil, ErrBufferFull\n\t}\n\t// 0 <= n <= len(b.buf)\n\tfor b.w-b.r < n && b.err == nil {\n\t\tb.fill() // b.w-b.r < len(b.buf) => buffer is not full\n\t}\n\n\tvar err error\n\tif avail := b.w - b.r; avail < n {\n\t\t// not enough data in buffer\n\t\tn = avail\n\t\terr = b.readErr()\n\t\tif err == nil {\n\t\t\terr = ErrBufferFull\n\t\t}\n\t}\n\treturn b.buf[b.r : b.r+n], err\n}\n\n// Pop returns the next n bytes with advancing the reader. The bytes stop\n// being valid at the next read call. If Pop returns fewer than n bytes, it\n// also returns an error explaining why the read is short. The error is\n// ErrBufferFull if n is larger than b's buffer size.\nfunc (b *Reader) Pop(n int) ([]byte, error) {\n\td, err := b.Peek(n)\n\tif err == nil {\n\t\tb.r += n\n\t\treturn d, err\n\t}\n\treturn nil, err\n}\n\n// Discard skips the next n bytes, returning the number of bytes discarded.\n//\n// If Discard skips fewer than n bytes, it also returns an error.\n// If 0 <= n <= b.Buffered(), Discard is guaranteed to succeed without\n// reading from the underlying io.Reader.\nfunc (b *Reader) Discard(n int) (discarded int, err error) {\n\tif n < 0 {\n\t\treturn 0, ErrNegativeCount\n\t}\n\tif n == 0 {\n\t\treturn\n\t}\n\tremain := n\n\tfor {\n\t\tskip := b.Buffered()\n\t\tif skip == 0 {\n\t\t\tb.fill()\n\t\t\tskip = b.Buffered()\n\t\t}\n\t\tif skip > remain {\n\t\t\tskip = remain\n\t\t}\n\t\tb.r += skip\n\t\tremain -= skip\n\t\tif remain == 0 {\n\t\t\treturn n, nil\n\t\t}\n\t\tif b.err != nil {\n\t\t\treturn n - remain, b.readErr()\n\t\t}\n\t}\n}\n\n// Read reads data into p.\n// It returns the number of bytes read into p.\n// It calls Read at most once on the underlying Reader,\n// hence n may be less than len(p).\n// At EOF, the count will be zero and err will be io.EOF.\nfunc (b *Reader) Read(p []byte) (n int, err error) {\n\tn = len(p)\n\tif n == 0 {\n\t\treturn 0, b.readErr()\n\t}\n\tif b.r == b.w {\n\t\tif b.err != nil {\n\t\t\treturn 0, b.readErr()\n\t\t}\n\t\tif len(p) >= len(b.buf) {\n\t\t\t// Large read, empty buffer.\n\t\t\t// Read directly into p to avoid copy.\n\t\t\tn, b.err = b.rd.Read(p)\n\t\t\tif n < 0 {\n\t\t\t\tpanic(errNegativeRead)\n\t\t\t}\n\t\t\treturn n, b.readErr()\n\t\t}\n\t\tb.fill() // buffer is empty\n\t\tif b.r == b.w {\n\t\t\treturn 0, b.readErr()\n\t\t}\n\t}\n\n\t// copy as much as we can\n\tn = copy(p, b.buf[b.r:b.w])\n\tb.r += n\n\treturn n, nil\n}\n\n// ReadByte reads and returns a single byte.\n// If no byte is available, returns an error.\nfunc (b *Reader) ReadByte() (c byte, err error) {\n\t//b.lastRuneSize = -1\n\tfor b.r == b.w {\n\t\tif b.err != nil {\n\t\t\treturn 0, b.readErr()\n\t\t}\n\t\tb.fill() // buffer is empty\n\t}\n\tc = b.buf[b.r]\n\tb.r++\n\t//b.lastByte = int(c)\n\treturn c, nil\n}\n\n// ReadSlice reads until the first occurrence of delim in the input,\n// returning a slice pointing at the bytes in the buffer.\n// The bytes stop being valid at the next read.\n// If ReadSlice encounters an error before finding a delimiter,\n// it returns all the data in the buffer and the error itself (often io.EOF).\n// ReadSlice fails with error ErrBufferFull if the buffer fills without a delim.\n// Because the data returned from ReadSlice will be overwritten\n// by the next I/O operation, most clients should use\n// ReadBytes or ReadString instead.\n// ReadSlice returns err != nil if and only if line does not end in delim.\nfunc (b *Reader) ReadSlice(delim byte) (line []byte, err error) {\n\tfor {\n\t\t// Search buffer.\n\t\tif i := bytes.IndexByte(b.buf[b.r:b.w], delim); i >= 0 {\n\t\t\tline = b.buf[b.r : b.r+i+1]\n\t\t\tb.r += i + 1\n\t\t\tbreak\n\t\t}\n\n\t\t// Pending error?\n\t\tif b.err != nil {\n\t\t\tline = b.buf[b.r:b.w]\n\t\t\tb.r = b.w\n\t\t\terr = b.readErr()\n\t\t\tbreak\n\t\t}\n\n\t\t// Buffer full?\n\t\tif b.Buffered() >= len(b.buf) {\n\t\t\tb.r = b.w\n\t\t\tline = b.buf\n\t\t\terr = ErrBufferFull\n\t\t\tbreak\n\t\t}\n\n\t\tb.fill() // buffer is not full\n\t}\n\treturn\n}\n\n// ReadLine is a low-level line-reading primitive. Most callers should use\n// ReadBytes('\\n') or ReadString('\\n') instead or use a Scanner.\n//\n// ReadLine tries to return a single line, not including the end-of-line bytes.\n// If the line was too long for the buffer then isPrefix is set and the\n// beginning of the line is returned. The rest of the line will be returned\n// from future calls. isPrefix will be false when returning the last fragment\n// of the line. The returned buffer is only valid until the next call to\n// ReadLine. ReadLine either returns a non-nil line or it returns an error,\n// never both.\n//\n// The text returned from ReadLine does not include the line end (\"\\r\\n\" or \"\\n\").\n// No indication or error is given if the input ends without a final line end.\n// Calling UnreadByte after ReadLine will always unread the last byte read\n// (possibly a character belonging to the line end) even if that byte is not\n// part of the line returned by ReadLine.\nfunc (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {\n\tline, err = b.ReadSlice('\\n')\n\tif err == ErrBufferFull {\n\t\t// Handle the case where \"\\r\\n\" straddles the buffer.\n\t\tif len(line) > 0 && line[len(line)-1] == '\\r' {\n\t\t\t// Put the '\\r' back on buf and drop it from line.\n\t\t\t// Let the next call to ReadLine check for \"\\r\\n\".\n\t\t\tif b.r == 0 {\n\t\t\t\t// should be unreachable\n\t\t\t\tpanic(\"bufio: tried to rewind past start of buffer\")\n\t\t\t}\n\t\t\tb.r--\n\t\t\tline = line[:len(line)-1]\n\t\t}\n\t\treturn line, true, nil\n\t}\n\n\tif len(line) == 0 {\n\t\tif err != nil {\n\t\t\tline = nil\n\t\t}\n\t\treturn\n\t}\n\terr = nil\n\n\tif line[len(line)-1] == '\\n' {\n\t\tdrop := 1\n\t\tif len(line) > 1 && line[len(line)-2] == '\\r' {\n\t\t\tdrop = 2\n\t\t}\n\t\tline = line[:len(line)-drop]\n\t}\n\treturn\n}\n\n// Buffered returns the number of bytes that can be read from the current buffer.\nfunc (b *Reader) Buffered() int { return b.w - b.r }\n\n// buffered output\n\n// Writer implements buffering for an io.Writer object.\n// If an error occurs writing to a Writer, no more data will be\n// accepted and all subsequent writes will return the error.\n// After all data has been written, the client should call the\n// Flush method to guarantee all data has been forwarded to\n// the underlying io.Writer.\ntype Writer struct {\n\terr error\n\tbuf []byte\n\tn   int\n\twr  io.Writer\n}\n\n// NewWriterSize returns a new Writer whose buffer has at least the specified\n// size. If the argument io.Writer is already a Writer with large enough\n// size, it returns the underlying Writer.\nfunc NewWriterSize(w io.Writer, size int) *Writer {\n\t// Is it already a Writer?\n\tb, ok := w.(*Writer)\n\tif ok && len(b.buf) >= size {\n\t\treturn b\n\t}\n\tif size <= 0 {\n\t\tsize = defaultBufSize\n\t}\n\treturn &Writer{\n\t\tbuf: make([]byte, size),\n\t\twr:  w,\n\t}\n}\n\n// NewWriter returns a new Writer whose buffer has the default size.\nfunc NewWriter(w io.Writer) *Writer {\n\treturn NewWriterSize(w, defaultBufSize)\n}\n\n// Reset discards any unflushed buffered data, clears any error, and\n// resets b to write its output to w.\nfunc (b *Writer) Reset(w io.Writer) {\n\tb.err = nil\n\tb.n = 0\n\tb.wr = w\n}\n\n// ResetBuffer discards any unflushed buffered data, clears any error, and\n// resets b to write its output to w.\nfunc (b *Writer) ResetBuffer(w io.Writer, buf []byte) {\n\tb.buf = buf\n\tb.err = nil\n\tb.n = 0\n\tb.wr = w\n}\n\n// Flush writes any buffered data to the underlying io.Writer.\nfunc (b *Writer) Flush() error {\n\terr := b.flush()\n\treturn err\n}\n\nfunc (b *Writer) flush() error {\n\tif b.err != nil {\n\t\treturn b.err\n\t}\n\tif b.n == 0 {\n\t\treturn nil\n\t}\n\tn, err := b.wr.Write(b.buf[0:b.n])\n\tif n < b.n && err == nil {\n\t\terr = io.ErrShortWrite\n\t}\n\tif err != nil {\n\t\tif n > 0 && n < b.n {\n\t\t\tcopy(b.buf[0:b.n-n], b.buf[n:b.n])\n\t\t}\n\t\tb.n -= n\n\t\tb.err = err\n\t\treturn err\n\t}\n\tb.n = 0\n\treturn nil\n}\n\n// Available returns how many bytes are unused in the buffer.\nfunc (b *Writer) Available() int { return len(b.buf) - b.n }\n\n// Buffered returns the number of bytes that have been written into the current buffer.\nfunc (b *Writer) Buffered() int { return b.n }\n\n// Write writes the contents of p into the buffer.\n// It returns the number of bytes written.\n// If nn < len(p), it also returns an error explaining\n// why the write is short.\nfunc (b *Writer) Write(p []byte) (nn int, err error) {\n\tfor len(p) > b.Available() && b.err == nil {\n\t\tvar n int\n\t\tif b.Buffered() == 0 {\n\t\t\t// Large write, empty buffer.\n\t\t\t// Write directly from p to avoid copy.\n\t\t\tn, b.err = b.wr.Write(p)\n\t\t} else {\n\t\t\tn = copy(b.buf[b.n:], p)\n\t\t\tb.n += n\n\t\t\tb.flush()\n\t\t}\n\t\tnn += n\n\t\tp = p[n:]\n\t}\n\tif b.err != nil {\n\t\treturn nn, b.err\n\t}\n\tn := copy(b.buf[b.n:], p)\n\tb.n += n\n\tnn += n\n\treturn nn, nil\n}\n\n// WriteRaw writes the contents of p into the raw io.Writer without buffer.\n// It returns the number of bytes written.\n// If nn < len(p), it also returns an error explaining\n// why the write is short.\nfunc (b *Writer) WriteRaw(p []byte) (nn int, err error) {\n\tif b.err != nil {\n\t\treturn 0, b.err\n\t}\n\tif b.Buffered() == 0 {\n\t\t// if no buffer data, write raw writer\n\t\tnn, err = b.wr.Write(p)\n\t\tb.err = err\n\t} else {\n\t\tnn, err = b.Write(p)\n\t}\n\treturn\n}\n\n// Peek returns the next n bytes with advancing the writer. The bytes stop\n// being used at the next write call. If Peek returns fewer than n bytes, it\n// also returns an error explaining why the read is short. The error is\n// ErrBufferFull if n is larger than b's buffer size.\nfunc (b *Writer) Peek(n int) ([]byte, error) {\n\tif n < 0 {\n\t\treturn nil, ErrNegativeCount\n\t}\n\tif n > len(b.buf) {\n\t\treturn nil, ErrBufferFull\n\t}\n\tfor b.Available() < n && b.err == nil {\n\t\tb.flush()\n\t}\n\tif b.err != nil {\n\t\treturn nil, b.err\n\t}\n\td := b.buf[b.n : b.n+n]\n\tb.n += n\n\treturn d, nil\n}\n\n// WriteString writes a string.\n// It returns the number of bytes written.\n// If the count is less than len(s), it also returns an error explaining\n// why the write is short.\nfunc (b *Writer) WriteString(s string) (int, error) {\n\tnn := 0\n\tfor len(s) > b.Available() && b.err == nil {\n\t\tn := copy(b.buf[b.n:], s)\n\t\tb.n += n\n\t\tnn += n\n\t\ts = s[n:]\n\t\tb.flush()\n\t}\n\tif b.err != nil {\n\t\treturn nn, b.err\n\t}\n\tn := copy(b.buf[b.n:], s)\n\tb.n += n\n\tnn += n\n\treturn nn, nil\n}\n"
  },
  {
    "path": "pkg/bufio/bufio_test.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage bufio_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/iotest\"\n\t\"time\"\n\n\t. \"github.com/Terry-Mao/goim/pkg/bufio\"\n)\n\n// Reads from a reader and rot13s the result.\ntype rot13Reader struct {\n\tr io.Reader\n}\n\nfunc newRot13Reader(r io.Reader) *rot13Reader {\n\tr13 := new(rot13Reader)\n\tr13.r = r\n\treturn r13\n}\n\nfunc (r13 *rot13Reader) Read(p []byte) (int, error) {\n\tn, err := r13.r.Read(p)\n\tfor i := 0; i < n; i++ {\n\t\tc := p[i] | 0x20 // lowercase byte\n\t\tif 'a' <= c && c <= 'm' {\n\t\t\tp[i] += 13\n\t\t} else if 'n' <= c && c <= 'z' {\n\t\t\tp[i] -= 13\n\t\t}\n\t}\n\treturn n, err\n}\n\n// Call ReadByte to accumulate the text of a file\nfunc readBytes(buf *Reader) string {\n\tvar b [1000]byte\n\tnb := 0\n\tfor {\n\t\tc, err := buf.ReadByte()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err == nil {\n\t\t\tb[nb] = c\n\t\t\tnb++\n\t\t} else if err != iotest.ErrTimeout {\n\t\t\tpanic(\"Data: \" + err.Error())\n\t\t}\n\t}\n\treturn string(b[0:nb])\n}\n\nfunc TestReaderSimple(t *testing.T) {\n\tdata := \"hello world\"\n\tb := NewReader(strings.NewReader(data))\n\tif s := readBytes(b); s != \"hello world\" {\n\t\tt.Errorf(\"simple hello world test failed: got %q\", s)\n\t}\n\n\tb = NewReader(newRot13Reader(strings.NewReader(data)))\n\tif s := readBytes(b); s != \"uryyb jbeyq\" {\n\t\tt.Errorf(\"rot13 hello world test failed: got %q\", s)\n\t}\n}\n\ntype readMaker struct {\n\tname string\n\tfn   func(io.Reader) io.Reader\n}\n\nvar readMakers = []readMaker{\n\t{\"full\", func(r io.Reader) io.Reader { return r }},\n\t{\"byte\", iotest.OneByteReader},\n\t{\"half\", iotest.HalfReader},\n\t{\"data+err\", iotest.DataErrReader},\n\t{\"timeout\", iotest.TimeoutReader},\n}\n\n// Call Read to accumulate the text of a file\nfunc reads(buf *Reader, m int) string {\n\tvar b [1000]byte\n\tnb := 0\n\tfor {\n\t\tn, err := buf.Read(b[nb : nb+m])\n\t\tnb += n\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn string(b[0:nb])\n}\n\ntype bufReader struct {\n\tname string\n\tfn   func(*Reader) string\n}\n\nvar bufreaders = []bufReader{\n\t{\"1\", func(b *Reader) string { return reads(b, 1) }},\n\t{\"2\", func(b *Reader) string { return reads(b, 2) }},\n\t{\"3\", func(b *Reader) string { return reads(b, 3) }},\n\t{\"4\", func(b *Reader) string { return reads(b, 4) }},\n\t{\"5\", func(b *Reader) string { return reads(b, 5) }},\n\t{\"7\", func(b *Reader) string { return reads(b, 7) }},\n\t{\"bytes\", readBytes},\n}\n\nconst minReadBufferSize = 16\n\nvar bufsizes = []int{\n\t0, minReadBufferSize, 23, 32, 46, 64, 93, 128, 1024, 4096,\n}\n\nfunc TestReader(t *testing.T) {\n\tvar texts [31]string\n\tstr := \"\"\n\tall := \"\"\n\tfor i := 0; i < len(texts)-1; i++ {\n\t\ttexts[i] = str + \"\\n\"\n\t\tall += texts[i]\n\t\tstr += fmt.Sprintf(\"%x\", i%26+'a')\n\t}\n\ttexts[len(texts)-1] = all\n\n\tfor h := 0; h < len(texts); h++ {\n\t\ttext := texts[h]\n\t\tfor i := 0; i < len(readMakers); i++ {\n\t\t\tfor j := 0; j < len(bufreaders); j++ {\n\t\t\t\tfor k := 0; k < len(bufsizes); k++ {\n\t\t\t\t\treadmaker := readMakers[i]\n\t\t\t\t\tbufreader := bufreaders[j]\n\t\t\t\t\tbufsize := bufsizes[k]\n\t\t\t\t\tread := readmaker.fn(strings.NewReader(text))\n\t\t\t\t\tbuf := NewReaderSize(read, bufsize)\n\t\t\t\t\ts := bufreader.fn(buf)\n\t\t\t\t\tif s != text {\n\t\t\t\t\t\tt.Errorf(\"reader=%s fn=%s bufsize=%d want=%q got=%q\",\n\t\t\t\t\t\t\treadmaker.name, bufreader.name, bufsize, text, s)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype zeroReader struct{}\n\nfunc (zeroReader) Read(p []byte) (int, error) {\n\treturn 0, nil\n}\n\nfunc TestZeroReader(t *testing.T) {\n\tvar z zeroReader\n\tr := NewReader(z)\n\n\tc := make(chan error)\n\tgo func() {\n\t\t_, err := r.ReadByte()\n\t\tc <- err\n\t}()\n\n\tselect {\n\tcase err := <-c:\n\t\tif err == nil {\n\t\t\tt.Error(\"error expected\")\n\t\t} else if err != io.ErrNoProgress {\n\t\t\tt.Error(\"unexpected error:\", err)\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Error(\"test timed out (endless loop in ReadByte?)\")\n\t}\n}\n\nfunc TestWriter(t *testing.T) {\n\tvar data [8192]byte\n\n\tfor i := 0; i < len(data); i++ {\n\t\tdata[i] = byte(' ' + i%('~'-' '))\n\t}\n\tw := new(bytes.Buffer)\n\tfor i := 0; i < len(bufsizes); i++ {\n\t\tfor j := 0; j < len(bufsizes); j++ {\n\t\t\tnwrite := bufsizes[i]\n\t\t\tbs := bufsizes[j]\n\n\t\t\t// Write nwrite bytes using buffer size bs.\n\t\t\t// Check that the right amount makes it out\n\t\t\t// and that the data is correct.\n\n\t\t\tw.Reset()\n\t\t\tbuf := NewWriterSize(w, bs)\n\t\t\tcontext := fmt.Sprintf(\"nwrite=%d bufsize=%d\", nwrite, bs)\n\t\t\tn, e1 := buf.Write(data[0:nwrite])\n\t\t\tif e1 != nil || n != nwrite {\n\t\t\t\tt.Errorf(\"%s: buf.Write %d = %d, %v\", context, nwrite, n, e1)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif e := buf.Flush(); e != nil {\n\t\t\t\tt.Errorf(\"%s: buf.Flush = %v\", context, e)\n\t\t\t}\n\n\t\t\twritten := w.Bytes()\n\t\t\tif len(written) != nwrite {\n\t\t\t\tt.Errorf(\"%s: %d bytes written\", context, len(written))\n\t\t\t}\n\t\t\tfor l := 0; l < len(written); l++ {\n\t\t\t\tif written[i] != data[i] {\n\t\t\t\t\tt.Errorf(\"wrong bytes written\")\n\t\t\t\t\tt.Errorf(\"want=%q\", data[0:len(written)])\n\t\t\t\t\tt.Errorf(\"have=%q\", written)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Check that write errors are returned properly.\n\ntype errorWriterTest struct {\n\tn, m   int\n\terr    error\n\texpect error\n}\n\nfunc (w errorWriterTest) Write(p []byte) (int, error) {\n\treturn len(p) * w.n / w.m, w.err\n}\n\nvar errorWriterTests = []errorWriterTest{\n\t{0, 1, nil, io.ErrShortWrite},\n\t{1, 2, nil, io.ErrShortWrite},\n\t{1, 1, nil, nil},\n\t{0, 1, io.ErrClosedPipe, io.ErrClosedPipe},\n\t{1, 2, io.ErrClosedPipe, io.ErrClosedPipe},\n\t{1, 1, io.ErrClosedPipe, io.ErrClosedPipe},\n}\n\nfunc TestWriteErrors(t *testing.T) {\n\tfor _, w := range errorWriterTests {\n\t\tbuf := NewWriter(w)\n\t\t_, e := buf.Write([]byte(\"hello world\"))\n\t\tif e != nil {\n\t\t\tt.Errorf(\"Write hello to %v: %v\", w, e)\n\t\t\tcontinue\n\t\t}\n\t\t// Two flushes, to verify the error is sticky.\n\t\tfor i := 0; i < 2; i++ {\n\t\t\te = buf.Flush()\n\t\t\tif e != w.expect {\n\t\t\t\tt.Errorf(\"Flush %d/2 %v: got %v, wanted %v\", i+1, w, e, w.expect)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNewReaderSizeIdempotent(t *testing.T) {\n\tconst BufSize = 1000\n\tb := NewReaderSize(strings.NewReader(\"hello world\"), BufSize)\n\t// Does it recognize itself?\n\tb1 := NewReaderSize(b, BufSize)\n\tif b1 != b {\n\t\tt.Error(\"NewReaderSize did not detect underlying Reader\")\n\t}\n\t// Does it wrap if existing buffer is too small?\n\tb2 := NewReaderSize(b, 2*BufSize)\n\tif b2 == b {\n\t\tt.Error(\"NewReaderSize did not enlarge buffer\")\n\t}\n}\n\nfunc TestNewWriterSizeIdempotent(t *testing.T) {\n\tconst BufSize = 1000\n\tb := NewWriterSize(new(bytes.Buffer), BufSize)\n\t// Does it recognize itself?\n\tb1 := NewWriterSize(b, BufSize)\n\tif b1 != b {\n\t\tt.Error(\"NewWriterSize did not detect underlying Writer\")\n\t}\n\t// Does it wrap if existing buffer is too small?\n\tb2 := NewWriterSize(b, 2*BufSize)\n\tif b2 == b {\n\t\tt.Error(\"NewWriterSize did not enlarge buffer\")\n\t}\n}\n\nfunc TestWriteString(t *testing.T) {\n\tconst BufSize = 8\n\tbuf := new(bytes.Buffer)\n\tb := NewWriterSize(buf, BufSize)\n\t_, _ = b.WriteString(\"0\")                         // easy\n\t_, _ = b.WriteString(\"123456\")                    // still easy\n\t_, _ = b.WriteString(\"7890\")                      // easy after flush\n\t_, _ = b.WriteString(\"abcdefghijklmnopqrstuvwxy\") // hard\n\t_, _ = b.WriteString(\"z\")\n\tif err := b.Flush(); err != nil {\n\t\tt.Error(\"WriteString\", err)\n\t}\n\ts := \"01234567890abcdefghijklmnopqrstuvwxyz\"\n\tif buf.String() != s {\n\t\tt.Errorf(\"WriteString wants %q gets %q\", s, buf.String())\n\t}\n}\n\nfunc TestBufferFull(t *testing.T) {\n\tconst longString = \"And now, hello, world! It is the time for all good men to come to the aid of their party\"\n\tbuf := NewReaderSize(strings.NewReader(longString), minReadBufferSize)\n\tline, err := buf.ReadSlice('!')\n\tif string(line) != \"And now, hello, \" || err != ErrBufferFull {\n\t\tt.Errorf(\"first ReadSlice(,) = %q, %v\", line, err)\n\t}\n\tline, err = buf.ReadSlice('!')\n\tif string(line) != \"world!\" || err != nil {\n\t\tt.Errorf(\"second ReadSlice(,) = %q, %v\", line, err)\n\t}\n}\n\nfunc TestPeek(t *testing.T) {\n\tp := make([]byte, 10)\n\t// string is 16 (minReadBufferSize) long.\n\tbuf := NewReaderSize(strings.NewReader(\"abcdefghijklmnop\"), minReadBufferSize)\n\tif s, err := buf.Peek(1); string(s) != \"a\" || err != nil {\n\t\tt.Fatalf(\"want %q got %q, err=%v\", \"a\", string(s), err)\n\t}\n\tif s, err := buf.Peek(4); string(s) != \"abcd\" || err != nil {\n\t\tt.Fatalf(\"want %q got %q, err=%v\", \"abcd\", string(s), err)\n\t}\n\tif _, err := buf.Peek(-1); err != ErrNegativeCount {\n\t\tt.Fatalf(\"want ErrNegativeCount got %v\", err)\n\t}\n\tif _, err := buf.Peek(32); err != ErrBufferFull {\n\t\tt.Fatalf(\"want ErrBufFull got %v\", err)\n\t}\n\tif _, err := buf.Read(p[0:3]); string(p[0:3]) != \"abc\" || err != nil {\n\t\tt.Fatalf(\"want %q got %q, err=%v\", \"abc\", string(p[0:3]), err)\n\t}\n\tif s, err := buf.Peek(1); string(s) != \"d\" || err != nil {\n\t\tt.Fatalf(\"want %q got %q, err=%v\", \"d\", string(s), err)\n\t}\n\tif s, err := buf.Peek(2); string(s) != \"de\" || err != nil {\n\t\tt.Fatalf(\"want %q got %q, err=%v\", \"de\", string(s), err)\n\t}\n\tif _, err := buf.Read(p[0:3]); string(p[0:3]) != \"def\" || err != nil {\n\t\tt.Fatalf(\"want %q got %q, err=%v\", \"def\", string(p[0:3]), err)\n\t}\n\tif s, err := buf.Peek(4); string(s) != \"ghij\" || err != nil {\n\t\tt.Fatalf(\"want %q got %q, err=%v\", \"ghij\", string(s), err)\n\t}\n\tif _, err := buf.Read(p[0:]); string(p[0:]) != \"ghijklmnop\" || err != nil {\n\t\tt.Fatalf(\"want %q got %q, err=%v\", \"ghijklmnop\", string(p[0:minReadBufferSize]), err)\n\t}\n\tif s, err := buf.Peek(0); string(s) != \"\" || err != nil {\n\t\tt.Fatalf(\"want %q got %q, err=%v\", \"\", string(s), err)\n\t}\n\tif _, err := buf.Peek(1); err != io.EOF {\n\t\tt.Fatalf(\"want EOF got %v\", err)\n\t}\n\n\t// Test for issue 3022, not exposing a reader's error on a successful Peek.\n\tbuf = NewReaderSize(dataAndEOFReader(\"abcd\"), 32)\n\tif s, err := buf.Peek(2); string(s) != \"ab\" || err != nil {\n\t\tt.Errorf(`Peek(2) on \"abcd\", EOF = %q, %v; want \"ab\", nil`, string(s), err)\n\t}\n\tif s, err := buf.Peek(4); string(s) != \"abcd\" || err != nil {\n\t\tt.Errorf(`Peek(4) on \"abcd\", EOF = %q, %v; want \"abcd\", nil`, string(s), err)\n\t}\n\tif n, err := buf.Read(p[0:5]); string(p[0:n]) != \"abcd\" || err != nil {\n\t\tt.Fatalf(\"Read after peek = %q, %v; want abcd, EOF\", p[0:n], err)\n\t}\n\tif n, err := buf.Read(p[0:1]); string(p[0:n]) != \"\" || err != io.EOF {\n\t\tt.Fatalf(`second Read after peek = %q, %v; want \"\", EOF`, p[0:n], err)\n\t}\n}\n\ntype dataAndEOFReader string\n\nfunc (r dataAndEOFReader) Read(p []byte) (int, error) {\n\treturn copy(p, r), io.EOF\n}\n\nvar testOutput = []byte(\"0123456789abcdefghijklmnopqrstuvwxy\")\nvar testInput = []byte(\"012\\n345\\n678\\n9ab\\ncde\\nfgh\\nijk\\nlmn\\nopq\\nrst\\nuvw\\nxy\")\nvar testInputrn = []byte(\"012\\r\\n345\\r\\n678\\r\\n9ab\\r\\ncde\\r\\nfgh\\r\\nijk\\r\\nlmn\\r\\nopq\\r\\nrst\\r\\nuvw\\r\\nxy\\r\\n\\n\\r\\n\")\n\n// TestReader wraps a []byte and returns reads of a specific length.\ntype testReader struct {\n\tdata   []byte\n\tstride int\n}\n\nfunc (t *testReader) Read(buf []byte) (n int, err error) {\n\tn = t.stride\n\tif n > len(t.data) {\n\t\tn = len(t.data)\n\t}\n\tif n > len(buf) {\n\t\tn = len(buf)\n\t}\n\tcopy(buf, t.data)\n\tt.data = t.data[n:]\n\tif len(t.data) == 0 {\n\t\terr = io.EOF\n\t}\n\treturn\n}\n\nfunc testReadLine(t *testing.T, input []byte) {\n\t//for stride := 1; stride < len(input); stride++ {\n\tfor stride := 1; stride < 2; stride++ {\n\t\tdone := 0\n\t\treader := testReader{input, stride}\n\t\tl := NewReaderSize(&reader, len(input)+1)\n\t\tfor {\n\t\t\tline, isPrefix, err := l.ReadLine()\n\t\t\tif len(line) > 0 && err != nil {\n\t\t\t\tt.Errorf(\"ReadLine returned both data and error: %s\", err)\n\t\t\t}\n\t\t\tif isPrefix {\n\t\t\t\tt.Errorf(\"ReadLine returned prefix\")\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tif err != io.EOF {\n\t\t\t\t\tt.Fatalf(\"Got unknown error: %s\", err)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif want := testOutput[done : done+len(line)]; !bytes.Equal(want, line) {\n\t\t\t\tt.Errorf(\"Bad line at stride %d: want: %x got: %x\", stride, want, line)\n\t\t\t}\n\t\t\tdone += len(line)\n\t\t}\n\t\tif done != len(testOutput) {\n\t\t\tt.Errorf(\"ReadLine didn't return everything: got: %d, want: %d (stride: %d)\", done, len(testOutput), stride)\n\t\t}\n\t}\n}\n\nfunc TestReadLine(t *testing.T) {\n\ttestReadLine(t, testInput)\n\ttestReadLine(t, testInputrn)\n}\n\nfunc TestLineTooLong(t *testing.T) {\n\tdata := make([]byte, 0)\n\tfor i := 0; i < minReadBufferSize*5/2; i++ {\n\t\tdata = append(data, '0'+byte(i%10))\n\t}\n\tbuf := bytes.NewReader(data)\n\tl := NewReaderSize(buf, minReadBufferSize)\n\tline, isPrefix, err := l.ReadLine()\n\tif !isPrefix || !bytes.Equal(line, data[:minReadBufferSize]) || err != nil {\n\t\tt.Errorf(\"bad result for first line: got %q want %q %v\", line, data[:minReadBufferSize], err)\n\t}\n\tdata = data[len(line):]\n\tline, isPrefix, err = l.ReadLine()\n\tif !isPrefix || !bytes.Equal(line, data[:minReadBufferSize]) || err != nil {\n\t\tt.Errorf(\"bad result for second line: got %q want %q %v\", line, data[:minReadBufferSize], err)\n\t}\n\tdata = data[len(line):]\n\tline, isPrefix, err = l.ReadLine()\n\tif isPrefix || !bytes.Equal(line, data[:minReadBufferSize/2]) || err != nil {\n\t\tt.Errorf(\"bad result for third line: got %q want %q %v\", line, data[:minReadBufferSize/2], err)\n\t}\n\tline, isPrefix, err = l.ReadLine()\n\tif isPrefix || err == nil {\n\t\tt.Errorf(\"expected no more lines: %x %s\", line, err)\n\t}\n}\n\nfunc TestReadAfterLines(t *testing.T) {\n\tline1 := \"this is line1\"\n\trestData := \"this is line2\\nthis is line 3\\n\"\n\tinbuf := bytes.NewReader([]byte(line1 + \"\\n\" + restData))\n\toutbuf := new(bytes.Buffer)\n\tmaxLineLength := len(line1) + len(restData)/2\n\tl := NewReaderSize(inbuf, maxLineLength)\n\tline, isPrefix, err := l.ReadLine()\n\tif isPrefix || err != nil || string(line) != line1 {\n\t\tt.Errorf(\"bad result for first line: isPrefix=%v err=%v line=%q\", isPrefix, err, string(line))\n\t}\n\tn, err := io.Copy(outbuf, l)\n\tif int(n) != len(restData) || err != nil {\n\t\tt.Errorf(\"bad result for Read: n=%d err=%v\", n, err)\n\t}\n\tif outbuf.String() != restData {\n\t\tt.Errorf(\"bad result for Read: got %q; expected %q\", outbuf.String(), restData)\n\t}\n}\n\nfunc TestReadEmptyBuffer(t *testing.T) {\n\tl := NewReaderSize(new(bytes.Buffer), minReadBufferSize)\n\tline, isPrefix, err := l.ReadLine()\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF from ReadLine, got '%s' %t %s\", line, isPrefix, err)\n\t}\n}\n\nfunc TestLinesAfterRead(t *testing.T) {\n\tl := NewReaderSize(bytes.NewReader([]byte(\"foo\")), minReadBufferSize)\n\t_, err := ioutil.ReadAll(l)\n\tif err != nil {\n\t\tt.Error(err)\n\t\treturn\n\t}\n\n\tline, isPrefix, err := l.ReadLine()\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF from ReadLine, got '%s' %t %s\", line, isPrefix, err)\n\t}\n}\n\nfunc TestReadLineNonNilLineOrError(t *testing.T) {\n\tr := NewReader(strings.NewReader(\"line 1\\n\"))\n\tfor i := 0; i < 2; i++ {\n\t\tl, _, err := r.ReadLine()\n\t\tif l != nil && err != nil {\n\t\t\tt.Fatalf(\"on line %d/2; ReadLine=%#v, %v; want non-nil line or Error, but not both\",\n\t\t\t\ti+1, l, err)\n\t\t}\n\t}\n}\n\ntype readLineResult struct {\n\tline     []byte\n\tisPrefix bool\n\terr      error\n}\n\nvar readLineNewlinesTests = []struct {\n\tinput  string\n\texpect []readLineResult\n}{\n\t{\"012345678901234\\r\\n012345678901234\\r\\n\", []readLineResult{\n\t\t{[]byte(\"012345678901234\"), true, nil},\n\t\t{nil, false, nil},\n\t\t{[]byte(\"012345678901234\"), true, nil},\n\t\t{nil, false, nil},\n\t\t{nil, false, io.EOF},\n\t}},\n\t{\"0123456789012345\\r012345678901234\\r\", []readLineResult{\n\t\t{[]byte(\"0123456789012345\"), true, nil},\n\t\t{[]byte(\"\\r012345678901234\"), true, nil},\n\t\t{[]byte(\"\\r\"), false, nil},\n\t\t{nil, false, io.EOF},\n\t}},\n}\n\nfunc TestReadLineNewlines(t *testing.T) {\n\tfor _, e := range readLineNewlinesTests {\n\t\ttestReadLineNewlines(t, e.input, e.expect)\n\t}\n}\n\nfunc testReadLineNewlines(t *testing.T, input string, expect []readLineResult) {\n\tb := NewReaderSize(strings.NewReader(input), minReadBufferSize)\n\tfor i, e := range expect {\n\t\tline, isPrefix, err := b.ReadLine()\n\t\tif !bytes.Equal(line, e.line) {\n\t\t\tt.Errorf(\"%q call %d, line == %q, want %q\", input, i, line, e.line)\n\t\t\treturn\n\t\t}\n\t\tif isPrefix != e.isPrefix {\n\t\t\tt.Errorf(\"%q call %d, isPrefix == %v, want %v\", input, i, isPrefix, e.isPrefix)\n\t\t\treturn\n\t\t}\n\t\tif err != e.err {\n\t\t\tt.Errorf(\"%q call %d, err == %v, want %v\", input, i, err, e.err)\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// TestWriterReadFromCounts tests that using io.Copy to copy into a\n// bufio.Writer does not prematurely flush the buffer. For example, when\n// buffering writes to a network socket, excessive network writes should be\n// avoided.\nfunc TestWriterReadFromCounts(t *testing.T) {\n\tvar w0 writeCountingDiscard\n\tb0 := NewWriterSize(&w0, 1234)\n\t_, _ = b0.WriteString(strings.Repeat(\"x\", 1000))\n\tif w0 != 0 {\n\t\tt.Fatalf(\"write 1000 'x's: got %d writes, want 0\", w0)\n\t}\n\t_, _ = b0.WriteString(strings.Repeat(\"x\", 200))\n\tif w0 != 0 {\n\t\tt.Fatalf(\"write 1200 'x's: got %d writes, want 0\", w0)\n\t}\n\t_, _ = io.Copy(b0, onlyReader{strings.NewReader(strings.Repeat(\"x\", 30))})\n\tif w0 != 0 {\n\t\tt.Fatalf(\"write 1230 'x's: got %d writes, want 0\", w0)\n\t}\n\t_, _ = io.Copy(b0, onlyReader{strings.NewReader(strings.Repeat(\"x\", 9))})\n\tif w0 != 1 {\n\t\tt.Fatalf(\"write 1239 'x's: got %d writes, want 1\", w0)\n\t}\n\n\tvar w1 writeCountingDiscard\n\tb1 := NewWriterSize(&w1, 1234)\n\t_, _ = b1.WriteString(strings.Repeat(\"x\", 1200))\n\t_ = b1.Flush()\n\tif w1 != 1 {\n\t\tt.Fatalf(\"flush 1200 'x's: got %d writes, want 1\", w1)\n\t}\n\t_, _ = b1.WriteString(strings.Repeat(\"x\", 89))\n\tif w1 != 1 {\n\t\tt.Fatalf(\"write 1200 + 89 'x's: got %d writes, want 1\", w1)\n\t}\n\t_, _ = io.Copy(b1, onlyReader{strings.NewReader(strings.Repeat(\"x\", 700))})\n\tif w1 != 1 {\n\t\tt.Fatalf(\"write 1200 + 789 'x's: got %d writes, want 1\", w1)\n\t}\n\t_, _ = io.Copy(b1, onlyReader{strings.NewReader(strings.Repeat(\"x\", 600))})\n\tif w1 != 2 {\n\t\tt.Fatalf(\"write 1200 + 1389 'x's: got %d writes, want 2\", w1)\n\t}\n\t_ = b1.Flush()\n\tif w1 != 3 {\n\t\tt.Fatalf(\"flush 1200 + 1389 'x's: got %d writes, want 3\", w1)\n\t}\n}\n\n// A writeCountingDiscard is like ioutil.Discard and counts the number of times\n// Write is called on it.\ntype writeCountingDiscard int\n\nfunc (w *writeCountingDiscard) Write(p []byte) (int, error) {\n\t*w++\n\treturn len(p), nil\n}\n\ntype negativeReader int\n\nfunc (r *negativeReader) Read([]byte) (int, error) { return -1, nil }\n\nfunc TestNegativeRead(t *testing.T) {\n\t// should panic with a description pointing at the reader, not at itself.\n\t// (should NOT panic with slice index error, for example.)\n\tb := NewReader(new(negativeReader))\n\tdefer func() {\n\t\tswitch err := recover().(type) {\n\t\tcase nil:\n\t\t\tt.Fatal(\"read did not panic\")\n\t\tcase error:\n\t\t\tif !strings.Contains(err.Error(), \"reader returned negative count from Read\") {\n\t\t\t\tt.Fatalf(\"wrong panic: %v\", err)\n\t\t\t}\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected panic value: %T(%v)\", err, err)\n\t\t}\n\t}()\n\t_, _ = b.Read(make([]byte, 100))\n}\n\nvar errFake = errors.New(\"fake error\")\n\ntype errorThenGoodReader struct {\n\tdidErr bool\n\tnread  int\n}\n\nfunc (r *errorThenGoodReader) Read(p []byte) (int, error) {\n\tr.nread++\n\tif !r.didErr {\n\t\tr.didErr = true\n\t\treturn 0, errFake\n\t}\n\treturn len(p), nil\n}\n\nfunc TestReaderClearError(t *testing.T) {\n\tr := &errorThenGoodReader{}\n\tb := NewReader(r)\n\tbuf := make([]byte, 1)\n\tif _, err := b.Read(nil); err != nil {\n\t\tt.Fatalf(\"1st nil Read = %v; want nil\", err)\n\t}\n\tif _, err := b.Read(buf); err != errFake {\n\t\tt.Fatalf(\"1st Read = %v; want errFake\", err)\n\t}\n\tif _, err := b.Read(nil); err != nil {\n\t\tt.Fatalf(\"2nd nil Read = %v; want nil\", err)\n\t}\n\tif _, err := b.Read(buf); err != nil {\n\t\tt.Fatalf(\"3rd Read with buffer = %v; want nil\", err)\n\t}\n\tif r.nread != 2 {\n\t\tt.Errorf(\"num reads = %d; want 2\", r.nread)\n\t}\n}\n\nfunc TestReaderReset(t *testing.T) {\n\tr := NewReader(strings.NewReader(\"foo foo\"))\n\tbuf := make([]byte, 3)\n\t_, _ = r.Read(buf)\n\tif string(buf) != \"foo\" {\n\t\tt.Errorf(\"buf = %q; want foo\", buf)\n\t}\n\tr.Reset(strings.NewReader(\"bar bar\"))\n\tall, err := ioutil.ReadAll(r)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif string(all) != \"bar bar\" {\n\t\tt.Errorf(\"ReadAll = %q; want bar bar\", all)\n\t}\n}\n\nfunc TestWriterReset(t *testing.T) {\n\tvar buf1, buf2 bytes.Buffer\n\tw := NewWriter(&buf1)\n\t_, _ = w.WriteString(\"foo\")\n\tw.Reset(&buf2) // and not flushed\n\t_, _ = w.WriteString(\"bar\")\n\t_ = w.Flush()\n\tif buf1.String() != \"\" {\n\t\tt.Errorf(\"buf1 = %q; want empty\", buf1.String())\n\t}\n\tif buf2.String() != \"bar\" {\n\t\tt.Errorf(\"buf2 = %q; want bar\", buf2.String())\n\t}\n}\n\nfunc TestReaderDiscard(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tr        io.Reader\n\t\tbufSize  int // 0 means 16\n\t\tpeekSize int\n\n\t\tn int // input to Discard\n\n\t\twant    int   // from Discard\n\t\twantErr error // from Discard\n\n\t\twantBuffered int\n\t}{\n\t\t{\n\t\t\tname:         \"normal case\",\n\t\t\tr:            strings.NewReader(\"abcdefghijklmnopqrstuvwxyz\"),\n\t\t\tpeekSize:     16,\n\t\t\tn:            6,\n\t\t\twant:         6,\n\t\t\twantBuffered: 10,\n\t\t},\n\t\t{\n\t\t\tname:         \"discard causing read\",\n\t\t\tr:            strings.NewReader(\"abcdefghijklmnopqrstuvwxyz\"),\n\t\t\tn:            6,\n\t\t\twant:         6,\n\t\t\twantBuffered: 10,\n\t\t},\n\t\t{\n\t\t\tname:         \"discard all without peek\",\n\t\t\tr:            strings.NewReader(\"abcdefghijklmnopqrstuvwxyz\"),\n\t\t\tn:            26,\n\t\t\twant:         26,\n\t\t\twantBuffered: 0,\n\t\t},\n\t\t{\n\t\t\tname:         \"discard more than end\",\n\t\t\tr:            strings.NewReader(\"abcdefghijklmnopqrstuvwxyz\"),\n\t\t\tn:            27,\n\t\t\twant:         26,\n\t\t\twantErr:      io.EOF,\n\t\t\twantBuffered: 0,\n\t\t},\n\t\t// Any error from filling shouldn't show up until we\n\t\t// get past the valid bytes. Here we return we return 5 valid bytes at the same time\n\t\t// as an error, but test that we don't see the error from Discard.\n\t\t{\n\t\t\tname: \"fill error, discard less\",\n\t\t\tr: newScriptedReader(func(p []byte) (n int, err error) {\n\t\t\t\tif len(p) < 5 {\n\t\t\t\t\tpanic(\"unexpected small read\")\n\t\t\t\t}\n\t\t\t\treturn 5, errors.New(\"5-then-error\")\n\t\t\t}),\n\t\t\tn:            4,\n\t\t\twant:         4,\n\t\t\twantErr:      nil,\n\t\t\twantBuffered: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"fill error, discard equal\",\n\t\t\tr: newScriptedReader(func(p []byte) (n int, err error) {\n\t\t\t\tif len(p) < 5 {\n\t\t\t\t\tpanic(\"unexpected small read\")\n\t\t\t\t}\n\t\t\t\treturn 5, errors.New(\"5-then-error\")\n\t\t\t}),\n\t\t\tn:            5,\n\t\t\twant:         5,\n\t\t\twantErr:      nil,\n\t\t\twantBuffered: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"fill error, discard more\",\n\t\t\tr: newScriptedReader(func(p []byte) (n int, err error) {\n\t\t\t\tif len(p) < 5 {\n\t\t\t\t\tpanic(\"unexpected small read\")\n\t\t\t\t}\n\t\t\t\treturn 5, errors.New(\"5-then-error\")\n\t\t\t}),\n\t\t\tn:            6,\n\t\t\twant:         5,\n\t\t\twantErr:      errors.New(\"5-then-error\"),\n\t\t\twantBuffered: 0,\n\t\t},\n\t\t// Discard of 0 shouldn't cause a read:\n\t\t{\n\t\t\tname:         \"discard zero\",\n\t\t\tr:            newScriptedReader(), // will panic on Read\n\t\t\tn:            0,\n\t\t\twant:         0,\n\t\t\twantErr:      nil,\n\t\t\twantBuffered: 0,\n\t\t},\n\t\t{\n\t\t\tname:         \"discard negative\",\n\t\t\tr:            newScriptedReader(), // will panic on Read\n\t\t\tn:            -1,\n\t\t\twant:         0,\n\t\t\twantErr:      ErrNegativeCount,\n\t\t\twantBuffered: 0,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tbr := NewReaderSize(tt.r, tt.bufSize)\n\t\tif tt.peekSize > 0 {\n\t\t\tpeekBuf, err := br.Peek(tt.peekSize)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"%s: Peek(%d): %v\", tt.name, tt.peekSize, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(peekBuf) != tt.peekSize {\n\t\t\t\tt.Errorf(\"%s: len(Peek(%d)) = %v; want %v\", tt.name, tt.peekSize, len(peekBuf), tt.peekSize)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tdiscarded, err := br.Discard(tt.n)\n\t\tif ge, we := fmt.Sprint(err), fmt.Sprint(tt.wantErr); discarded != tt.want || ge != we {\n\t\t\tt.Errorf(\"%s: Discard(%d) = (%v, %v); want (%v, %v)\", tt.name, tt.n, discarded, ge, tt.want, we)\n\t\t\tcontinue\n\t\t}\n\t\tif bn := br.Buffered(); bn != tt.wantBuffered {\n\t\t\tt.Errorf(\"%s: after Discard, Buffered = %d; want %d\", tt.name, bn, tt.wantBuffered)\n\t\t}\n\t}\n\n}\n\n// An onlyReader only implements io.Reader, no matter what other methods the underlying implementation may have.\ntype onlyReader struct {\n\tio.Reader\n}\n\n// An onlyWriter only implements io.Writer, no matter what other methods the underlying implementation may have.\ntype onlyWriter struct {\n\tio.Writer\n}\n\n// A scriptedReader is an io.Reader that executes its steps sequentially.\ntype scriptedReader []func(p []byte) (n int, err error)\n\nfunc (sr *scriptedReader) Read(p []byte) (n int, err error) {\n\tif len(*sr) == 0 {\n\t\tpanic(\"too many Read calls on scripted Reader. No steps remain.\")\n\t}\n\tstep := (*sr)[0]\n\t*sr = (*sr)[1:]\n\treturn step(p)\n}\n\nfunc newScriptedReader(steps ...func(p []byte) (n int, err error)) io.Reader {\n\tsr := scriptedReader(steps)\n\treturn &sr\n}\n\nfunc BenchmarkReaderCopyOptimal(b *testing.B) {\n\t// Optimal case is where the underlying reader implements io.WriterTo\n\tsrcBuf := bytes.NewBuffer(make([]byte, 8192))\n\tsrc := NewReader(srcBuf)\n\tdstBuf := new(bytes.Buffer)\n\tdst := onlyWriter{dstBuf}\n\tfor i := 0; i < b.N; i++ {\n\t\tsrcBuf.Reset()\n\t\tsrc.Reset(srcBuf)\n\t\tdstBuf.Reset()\n\t\t_, _ = io.Copy(dst, src)\n\t}\n}\n\nfunc BenchmarkReaderCopyUnoptimal(b *testing.B) {\n\t// Unoptimal case is where the underlying reader doesn't implement io.WriterTo\n\tsrcBuf := bytes.NewBuffer(make([]byte, 8192))\n\tsrc := NewReader(onlyReader{srcBuf})\n\tdstBuf := new(bytes.Buffer)\n\tdst := onlyWriter{dstBuf}\n\tfor i := 0; i < b.N; i++ {\n\t\tsrcBuf.Reset()\n\t\tsrc.Reset(onlyReader{srcBuf})\n\t\tdstBuf.Reset()\n\t\t_, _ = io.Copy(dst, src)\n\t}\n}\n\nfunc BenchmarkReaderCopyNoWriteTo(b *testing.B) {\n\tsrcBuf := bytes.NewBuffer(make([]byte, 8192))\n\tsrcReader := NewReader(srcBuf)\n\tsrc := onlyReader{srcReader}\n\tdstBuf := new(bytes.Buffer)\n\tdst := onlyWriter{dstBuf}\n\tfor i := 0; i < b.N; i++ {\n\t\tsrcBuf.Reset()\n\t\tsrcReader.Reset(srcBuf)\n\t\tdstBuf.Reset()\n\t\t_, _ = io.Copy(dst, src)\n\t}\n}\n\nfunc BenchmarkWriterCopyOptimal(b *testing.B) {\n\t// Optimal case is where the underlying writer implements io.ReaderFrom\n\tsrcBuf := bytes.NewBuffer(make([]byte, 8192))\n\tsrc := onlyReader{srcBuf}\n\tdstBuf := new(bytes.Buffer)\n\tdst := NewWriter(dstBuf)\n\tfor i := 0; i < b.N; i++ {\n\t\tsrcBuf.Reset()\n\t\tdstBuf.Reset()\n\t\tdst.Reset(dstBuf)\n\t\t_, _ = io.Copy(dst, src)\n\t}\n}\n\nfunc BenchmarkWriterCopyUnoptimal(b *testing.B) {\n\tsrcBuf := bytes.NewBuffer(make([]byte, 8192))\n\tsrc := onlyReader{srcBuf}\n\tdstBuf := new(bytes.Buffer)\n\tdst := NewWriter(onlyWriter{dstBuf})\n\tfor i := 0; i < b.N; i++ {\n\t\tsrcBuf.Reset()\n\t\tdstBuf.Reset()\n\t\tdst.Reset(onlyWriter{dstBuf})\n\t\t_, _ = io.Copy(dst, src)\n\t}\n}\n\nfunc BenchmarkWriterCopyNoReadFrom(b *testing.B) {\n\tsrcBuf := bytes.NewBuffer(make([]byte, 8192))\n\tsrc := onlyReader{srcBuf}\n\tdstBuf := new(bytes.Buffer)\n\tdstWriter := NewWriter(dstBuf)\n\tdst := onlyWriter{dstWriter}\n\tfor i := 0; i < b.N; i++ {\n\t\tsrcBuf.Reset()\n\t\tdstBuf.Reset()\n\t\tdstWriter.Reset(dstBuf)\n\t\t_, _ = io.Copy(dst, src)\n\t}\n}\n\nfunc BenchmarkReaderEmpty(b *testing.B) {\n\tb.ReportAllocs()\n\tstr := strings.Repeat(\"x\", 16<<10)\n\tfor i := 0; i < b.N; i++ {\n\t\tbr := NewReader(strings.NewReader(str))\n\t\tn, err := io.Copy(ioutil.Discard, br)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tif n != int64(len(str)) {\n\t\t\tb.Fatal(\"wrong length\")\n\t\t}\n\t}\n}\n\nfunc BenchmarkWriterEmpty(b *testing.B) {\n\tb.ReportAllocs()\n\tstr := strings.Repeat(\"x\", 1<<10)\n\tbs := []byte(str)\n\tfor i := 0; i < b.N; i++ {\n\t\tbw := NewWriter(ioutil.Discard)\n\t\t_ = bw.Flush()\n\t\t_ = bw.Flush()\n\t\t_, _ = bw.Write(bs)\n\t\t_ = bw.Flush()\n\t\t_, _ = bw.WriteString(str)\n\t\t_ = bw.Flush()\n\t}\n}\n\nfunc BenchmarkWriterFlush(b *testing.B) {\n\tb.ReportAllocs()\n\tbw := NewWriter(ioutil.Discard)\n\tstr := strings.Repeat(\"x\", 50)\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = bw.WriteString(str)\n\t\t_ = bw.Flush()\n\t}\n}\n"
  },
  {
    "path": "pkg/bytes/buffer.go",
    "content": "package bytes\n\nimport (\n\t\"sync\"\n)\n\n// Buffer buffer.\ntype Buffer struct {\n\tbuf  []byte\n\tnext *Buffer // next free buffer\n}\n\n// Bytes bytes.\nfunc (b *Buffer) Bytes() []byte {\n\treturn b.buf\n}\n\n// Pool is a buffer pool.\ntype Pool struct {\n\tlock sync.Mutex\n\tfree *Buffer\n\tmax  int\n\tnum  int\n\tsize int\n}\n\n// NewPool new a memory buffer pool struct.\nfunc NewPool(num, size int) (p *Pool) {\n\tp = new(Pool)\n\tp.init(num, size)\n\treturn\n}\n\n// Init init the memory buffer.\nfunc (p *Pool) Init(num, size int) {\n\tp.init(num, size)\n}\n\n// init init the memory buffer.\nfunc (p *Pool) init(num, size int) {\n\tp.num = num\n\tp.size = size\n\tp.max = num * size\n\tp.grow()\n}\n\n// grow grow the memory buffer size, and update free pointer.\nfunc (p *Pool) grow() {\n\tvar (\n\t\ti   int\n\t\tb   *Buffer\n\t\tbs  []Buffer\n\t\tbuf []byte\n\t)\n\tbuf = make([]byte, p.max)\n\tbs = make([]Buffer, p.num)\n\tp.free = &bs[0]\n\tb = p.free\n\tfor i = 1; i < p.num; i++ {\n\t\tb.buf = buf[(i-1)*p.size : i*p.size]\n\t\tb.next = &bs[i]\n\t\tb = b.next\n\t}\n\tb.buf = buf[(i-1)*p.size : i*p.size]\n\tb.next = nil\n}\n\n// Get get a free memory buffer.\nfunc (p *Pool) Get() (b *Buffer) {\n\tp.lock.Lock()\n\tif b = p.free; b == nil {\n\t\tp.grow()\n\t\tb = p.free\n\t}\n\tp.free = b.next\n\tp.lock.Unlock()\n\treturn\n}\n\n// Put put back a memory buffer to free.\nfunc (p *Pool) Put(b *Buffer) {\n\tp.lock.Lock()\n\tb.next = p.free\n\tp.free = b\n\tp.lock.Unlock()\n}\n"
  },
  {
    "path": "pkg/bytes/buffer_test.go",
    "content": "package bytes\n\nimport (\n\t\"testing\"\n)\n\nfunc TestBuffer(t *testing.T) {\n\tp := NewPool(2, 10)\n\tb := p.Get()\n\tif b.Bytes() == nil || len(b.Bytes()) == 0 {\n\t\tt.FailNow()\n\t}\n\tb = p.Get()\n\tif b.Bytes() == nil || len(b.Bytes()) == 0 {\n\t\tt.FailNow()\n\t}\n\tb = p.Get()\n\tif b.Bytes() == nil || len(b.Bytes()) == 0 {\n\t\tt.FailNow()\n\t}\n}\n"
  },
  {
    "path": "pkg/bytes/writer.go",
    "content": "package bytes\n\n// Writer writer.\ntype Writer struct {\n\tn   int\n\tbuf []byte\n}\n\n// NewWriterSize new a writer with size.\nfunc NewWriterSize(n int) *Writer {\n\treturn &Writer{buf: make([]byte, n)}\n}\n\n// Len buff len.\nfunc (w *Writer) Len() int {\n\treturn w.n\n}\n\n// Size buff cap.\nfunc (w *Writer) Size() int {\n\treturn len(w.buf)\n}\n\n// Reset reset the buff.\nfunc (w *Writer) Reset() {\n\tw.n = 0\n}\n\n// Buffer return buff.\nfunc (w *Writer) Buffer() []byte {\n\treturn w.buf[:w.n]\n}\n\n// Peek peek a buf.\nfunc (w *Writer) Peek(n int) []byte {\n\tvar buf []byte\n\tw.grow(n)\n\tbuf = w.buf[w.n : w.n+n]\n\tw.n += n\n\treturn buf\n}\n\n// Write write a buff.\nfunc (w *Writer) Write(p []byte) {\n\tw.grow(len(p))\n\tw.n += copy(w.buf[w.n:], p)\n}\n\nfunc (w *Writer) grow(n int) {\n\tvar buf []byte\n\tif w.n+n < len(w.buf) {\n\t\treturn\n\t}\n\tbuf = make([]byte, 2*len(w.buf)+n)\n\tcopy(buf, w.buf[:w.n])\n\tw.buf = buf\n}\n"
  },
  {
    "path": "pkg/bytes/writer_test.go",
    "content": "package bytes\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestWriter(t *testing.T) {\n\tw := NewWriterSize(64)\n\tif w.Len() != 0 && w.Size() != 64 {\n\t\tt.FailNow()\n\t}\n\tb := []byte(\"hello\")\n\tw.Write(b)\n\tif !reflect.DeepEqual(b, w.Buffer()) {\n\t\tt.FailNow()\n\t}\n\tw.Peek(len(b))\n\tw.Reset()\n\tfor i := 0; i < 1024; i++ {\n\t\tw.Write(b)\n\t}\n\tw.Reset()\n\tif w.Len() != 0 {\n\t\tt.FailNow()\n\t}\n}\n"
  },
  {
    "path": "pkg/encoding/binary/endian.go",
    "content": "package binary\n\n// BigEndian big endian.\nvar BigEndian bigEndian\n\ntype bigEndian struct{}\n\nfunc (bigEndian) Int8(b []byte) int8 { return int8(b[0]) }\n\nfunc (bigEndian) PutInt8(b []byte, v int8) {\n\tb[0] = byte(v)\n}\n\nfunc (bigEndian) Int16(b []byte) int16 { return int16(b[1]) | int16(b[0])<<8 }\n\nfunc (bigEndian) PutInt16(b []byte, v int16) {\n\t_ = b[1]\n\tb[0] = byte(v >> 8)\n\tb[1] = byte(v)\n}\n\nfunc (bigEndian) Int32(b []byte) int32 {\n\treturn int32(b[3]) | int32(b[2])<<8 | int32(b[1])<<16 | int32(b[0])<<24\n}\n\nfunc (bigEndian) PutInt32(b []byte, v int32) {\n\t_ = b[3]\n\tb[0] = byte(v >> 24)\n\tb[1] = byte(v >> 16)\n\tb[2] = byte(v >> 8)\n\tb[3] = byte(v)\n}\n"
  },
  {
    "path": "pkg/encoding/binary/endian_test.go",
    "content": "package binary\n\nimport \"testing\"\n\nfunc TestInt8(t *testing.T) {\n\tb := make([]byte, 1)\n\tBigEndian.PutInt8(b, 100)\n\ti := BigEndian.Int8(b)\n\tif i != 100 {\n\t\tt.FailNow()\n\t}\n}\n\nfunc TestInt16(t *testing.T) {\n\tb := make([]byte, 2)\n\tBigEndian.PutInt16(b, 100)\n\ti := BigEndian.Int16(b)\n\tif i != 100 {\n\t\tt.FailNow()\n\t}\n}\n\nfunc TestInt32(t *testing.T) {\n\tb := make([]byte, 4)\n\tBigEndian.PutInt32(b, 100)\n\ti := BigEndian.Int32(b)\n\tif i != 100 {\n\t\tt.FailNow()\n\t}\n}\n"
  },
  {
    "path": "pkg/ip/ip.go",
    "content": "package ip\n\nimport (\n\t\"net\"\n\t\"strings\"\n)\n\n// InternalIP return internal ip.\nfunc InternalIP() string {\n\tinters, err := net.Interfaces()\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tfor _, inter := range inters {\n\t\tif inter.Flags&net.FlagUp != 0 && !strings.HasPrefix(inter.Name, \"lo\") {\n\t\t\taddrs, err := inter.Addrs()\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, addr := range addrs {\n\t\t\t\tif ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {\n\t\t\t\t\tif ipnet.IP.To4() != nil {\n\t\t\t\t\t\treturn ipnet.IP.String()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/ip/ip_test.go",
    "content": "package ip\n\nimport \"testing\"\n\nfunc TestIP(t *testing.T) {\n\tip := InternalIP()\n\tif ip == \"\" {\n\t\tt.FailNow()\n\t}\n}\n"
  },
  {
    "path": "pkg/strings/ints.go",
    "content": "package strings\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n)\n\nvar (\n\tbfPool = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\treturn bytes.NewBuffer([]byte{})\n\t\t},\n\t}\n)\n\n// JoinInt32s format int32 slice like:n1,n2,n3.\nfunc JoinInt32s(is []int32, p string) string {\n\tif len(is) == 0 {\n\t\treturn \"\"\n\t}\n\tif len(is) == 1 {\n\t\treturn strconv.FormatInt(int64(is[0]), 10)\n\t}\n\tbuf := bfPool.Get().(*bytes.Buffer)\n\tfor _, i := range is {\n\t\tbuf.WriteString(strconv.FormatInt(int64(i), 10))\n\t\tbuf.WriteString(p)\n\t}\n\tif buf.Len() > 0 {\n\t\tbuf.Truncate(buf.Len() - 1)\n\t}\n\ts := buf.String()\n\tbuf.Reset()\n\tbfPool.Put(buf)\n\treturn s\n}\n\n// SplitInt32s split string into int32 slice.\nfunc SplitInt32s(s, p string) ([]int32, error) {\n\tif s == \"\" {\n\t\treturn nil, nil\n\t}\n\tsArr := strings.Split(s, p)\n\tres := make([]int32, 0, len(sArr))\n\tfor _, sc := range sArr {\n\t\ti, err := strconv.ParseInt(sc, 10, 32)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tres = append(res, int32(i))\n\t}\n\treturn res, nil\n}\n\n// JoinInt64s format int64 slice like:n1,n2,n3.\nfunc JoinInt64s(is []int64, p string) string {\n\tif len(is) == 0 {\n\t\treturn \"\"\n\t}\n\tif len(is) == 1 {\n\t\treturn strconv.FormatInt(is[0], 10)\n\t}\n\tbuf := bfPool.Get().(*bytes.Buffer)\n\tfor _, i := range is {\n\t\tbuf.WriteString(strconv.FormatInt(i, 10))\n\t\tbuf.WriteString(p)\n\t}\n\tif buf.Len() > 0 {\n\t\tbuf.Truncate(buf.Len() - 1)\n\t}\n\ts := buf.String()\n\tbuf.Reset()\n\tbfPool.Put(buf)\n\treturn s\n}\n\n// SplitInt64s split string into int64 slice.\nfunc SplitInt64s(s, p string) ([]int64, error) {\n\tif s == \"\" {\n\t\treturn nil, nil\n\t}\n\tsArr := strings.Split(s, p)\n\tres := make([]int64, 0, len(sArr))\n\tfor _, sc := range sArr {\n\t\ti, err := strconv.ParseInt(sc, 10, 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tres = append(res, i)\n\t}\n\treturn res, nil\n}\n"
  },
  {
    "path": "pkg/strings/ints_test.go",
    "content": "package strings\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestInt32(t *testing.T) {\n\ti := []int32{1, 2, 3}\n\ts := JoinInt32s(i, \",\")\n\tii, _ := SplitInt32s(s, \",\")\n\tif !reflect.DeepEqual(i, ii) {\n\t\tt.FailNow()\n\t}\n}\n\nfunc TestInt64(t *testing.T) {\n\ti := []int64{1, 2, 3}\n\ts := JoinInt64s(i, \",\")\n\tii, _ := SplitInt64s(s, \",\")\n\tif !reflect.DeepEqual(i, ii) {\n\t\tt.FailNow()\n\t}\n}\n"
  },
  {
    "path": "pkg/time/debug.go",
    "content": "package time\n\nconst (\n\t// Debug debug switch\n\tDebug = false\n)\n"
  },
  {
    "path": "pkg/time/duration.go",
    "content": "package time\n\nimport (\n\txtime \"time\"\n)\n\n// Duration be used toml unmarshal string time, like 1s, 500ms.\ntype Duration xtime.Duration\n\n// UnmarshalText unmarshal text to duration.\nfunc (d *Duration) UnmarshalText(text []byte) error {\n\ttmp, err := xtime.ParseDuration(string(text))\n\tif err == nil {\n\t\t*d = Duration(tmp)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/time/duration_test.go",
    "content": "package time\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestDurationText(t *testing.T) {\n\tvar (\n\t\tinput  = []byte(\"10s\")\n\t\toutput = time.Second * 10\n\t\td      Duration\n\t)\n\tif err := d.UnmarshalText(input); err != nil {\n\t\tt.FailNow()\n\t}\n\tif int64(output) != int64(d) {\n\t\tt.FailNow()\n\t}\n}\n"
  },
  {
    "path": "pkg/time/timer.go",
    "content": "package time\n\nimport (\n\t\"sync\"\n\titime \"time\"\n\n\tlog \"github.com/golang/glog\"\n)\n\nconst (\n\ttimerFormat      = \"2006-01-02 15:04:05\"\n\tinfiniteDuration = itime.Duration(1<<63 - 1)\n)\n\n// TimerData timer data.\ntype TimerData struct {\n\tKey    string\n\texpire itime.Time\n\tfn     func()\n\tindex  int\n\tnext   *TimerData\n}\n\n// Delay delay duration.\nfunc (td *TimerData) Delay() itime.Duration {\n\treturn itime.Until(td.expire)\n}\n\n// ExpireString expire string.\nfunc (td *TimerData) ExpireString() string {\n\treturn td.expire.Format(timerFormat)\n}\n\n// Timer timer.\ntype Timer struct {\n\tlock   sync.Mutex\n\tfree   *TimerData\n\ttimers []*TimerData\n\tsignal *itime.Timer\n\tnum    int\n}\n\n// NewTimer new a timer.\n// A heap must be initialized before any of the heap operations\n// can be used. Init is idempotent with respect to the heap invariants\n// and may be called whenever the heap invariants may have been invalidated.\n// Its complexity is O(n) where n = h.Len().\n//\nfunc NewTimer(num int) (t *Timer) {\n\tt = new(Timer)\n\tt.init(num)\n\treturn t\n}\n\n// Init init the timer.\nfunc (t *Timer) Init(num int) {\n\tt.init(num)\n}\n\nfunc (t *Timer) init(num int) {\n\tt.signal = itime.NewTimer(infiniteDuration)\n\tt.timers = make([]*TimerData, 0, num)\n\tt.num = num\n\tt.grow()\n\tgo t.start()\n}\n\nfunc (t *Timer) grow() {\n\tvar (\n\t\ti   int\n\t\ttd  *TimerData\n\t\ttds = make([]TimerData, t.num)\n\t)\n\tt.free = &(tds[0])\n\ttd = t.free\n\tfor i = 1; i < t.num; i++ {\n\t\ttd.next = &(tds[i])\n\t\ttd = td.next\n\t}\n\ttd.next = nil\n}\n\n// get get a free timer data.\nfunc (t *Timer) get() (td *TimerData) {\n\tif td = t.free; td == nil {\n\t\tt.grow()\n\t\ttd = t.free\n\t}\n\tt.free = td.next\n\treturn\n}\n\n// put put back a timer data.\nfunc (t *Timer) put(td *TimerData) {\n\ttd.fn = nil\n\ttd.next = t.free\n\tt.free = td\n}\n\n// Add add the element x onto the heap. The complexity is\n// O(log(n)) where n = h.Len().\nfunc (t *Timer) Add(expire itime.Duration, fn func()) (td *TimerData) {\n\tt.lock.Lock()\n\ttd = t.get()\n\ttd.expire = itime.Now().Add(expire)\n\ttd.fn = fn\n\tt.add(td)\n\tt.lock.Unlock()\n\treturn\n}\n\n// Del removes the element at index i from the heap.\n// The complexity is O(log(n)) where n = h.Len().\nfunc (t *Timer) Del(td *TimerData) {\n\tt.lock.Lock()\n\tt.del(td)\n\tt.put(td)\n\tt.lock.Unlock()\n}\n\n// Push pushes the element x onto the heap. The complexity is\n// O(log(n)) where n = h.Len().\nfunc (t *Timer) add(td *TimerData) {\n\tvar d itime.Duration\n\ttd.index = len(t.timers)\n\t// add to the minheap last node\n\tt.timers = append(t.timers, td)\n\tt.up(td.index)\n\tif td.index == 0 {\n\t\t// if first node, signal start goroutine\n\t\td = td.Delay()\n\t\tt.signal.Reset(d)\n\t\tif Debug {\n\t\t\tlog.Infof(\"timer: add reset delay %d ms\", int64(d)/int64(itime.Millisecond))\n\t\t}\n\t}\n\tif Debug {\n\t\tlog.Infof(\"timer: push item key: %s, expire: %s, index: %d\", td.Key, td.ExpireString(), td.index)\n\t}\n}\n\nfunc (t *Timer) del(td *TimerData) {\n\tvar (\n\t\ti    = td.index\n\t\tlast = len(t.timers) - 1\n\t)\n\tif i < 0 || i > last || t.timers[i] != td {\n\t\t// already remove, usually by expire\n\t\tif Debug {\n\t\t\tlog.Infof(\"timer del i: %d, last: %d, %p\", i, last, td)\n\t\t}\n\t\treturn\n\t}\n\tif i != last {\n\t\tt.swap(i, last)\n\t\tt.down(i, last)\n\t\tt.up(i)\n\t}\n\t// remove item is the last node\n\tt.timers[last].index = -1 // for safety\n\tt.timers = t.timers[:last]\n\tif Debug {\n\t\tlog.Infof(\"timer: remove item key: %s, expire: %s, index: %d\", td.Key, td.ExpireString(), td.index)\n\t}\n}\n\n// Set update timer data.\nfunc (t *Timer) Set(td *TimerData, expire itime.Duration) {\n\tt.lock.Lock()\n\tt.del(td)\n\ttd.expire = itime.Now().Add(expire)\n\tt.add(td)\n\tt.lock.Unlock()\n}\n\n// start start the timer.\nfunc (t *Timer) start() {\n\tfor {\n\t\tt.expire()\n\t\t<-t.signal.C\n\t}\n}\n\n// expire removes the minimum element (according to Less) from the heap.\n// The complexity is O(log(n)) where n = max.\n// It is equivalent to Del(0).\nfunc (t *Timer) expire() {\n\tvar (\n\t\tfn func()\n\t\ttd *TimerData\n\t\td  itime.Duration\n\t)\n\tt.lock.Lock()\n\tfor {\n\t\tif len(t.timers) == 0 {\n\t\t\td = infiniteDuration\n\t\t\tif Debug {\n\t\t\t\tlog.Info(\"timer: no other instance\")\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\ttd = t.timers[0]\n\t\tif d = td.Delay(); d > 0 {\n\t\t\tbreak\n\t\t}\n\t\tfn = td.fn\n\t\t// let caller put back\n\t\tt.del(td)\n\t\tt.lock.Unlock()\n\t\tif fn == nil {\n\t\t\tlog.Warning(\"expire timer no fn\")\n\t\t} else {\n\t\t\tif Debug {\n\t\t\t\tlog.Infof(\"timer key: %s, expire: %s, index: %d expired, call fn\", td.Key, td.ExpireString(), td.index)\n\t\t\t}\n\t\t\tfn()\n\t\t}\n\t\tt.lock.Lock()\n\t}\n\tt.signal.Reset(d)\n\tif Debug {\n\t\tlog.Infof(\"timer: expier reset delay %d ms\", int64(d)/int64(itime.Millisecond))\n\t}\n\tt.lock.Unlock()\n}\n\nfunc (t *Timer) up(j int) {\n\tfor {\n\t\ti := (j - 1) / 2 // parent\n\t\tif i >= j || !t.less(j, i) {\n\t\t\tbreak\n\t\t}\n\t\tt.swap(i, j)\n\t\tj = i\n\t}\n}\n\nfunc (t *Timer) down(i, n int) {\n\tfor {\n\t\tj1 := 2*i + 1\n\t\tif j1 >= n || j1 < 0 { // j1 < 0 after int overflow\n\t\t\tbreak\n\t\t}\n\t\tj := j1 // left child\n\t\tif j2 := j1 + 1; j2 < n && !t.less(j1, j2) {\n\t\t\tj = j2 // = 2*i + 2  // right child\n\t\t}\n\t\tif !t.less(j, i) {\n\t\t\tbreak\n\t\t}\n\t\tt.swap(i, j)\n\t\ti = j\n\t}\n}\n\nfunc (t *Timer) less(i, j int) bool {\n\treturn t.timers[i].expire.Before(t.timers[j].expire)\n}\n\nfunc (t *Timer) swap(i, j int) {\n\tt.timers[i], t.timers[j] = t.timers[j], t.timers[i]\n\tt.timers[i].index = i\n\tt.timers[j].index = j\n}\n"
  },
  {
    "path": "pkg/time/timer_test.go",
    "content": "package time\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\tlog \"github.com/golang/glog\"\n)\n\nfunc TestTimer(t *testing.T) {\n\ttimer := NewTimer(100)\n\ttds := make([]*TimerData, 100)\n\tfor i := 0; i < 100; i++ {\n\t\ttds[i] = timer.Add(time.Duration(i)*time.Second+5*time.Minute, nil)\n\t}\n\tprintTimer(timer)\n\tfor i := 0; i < 100; i++ {\n\t\tlog.Infof(\"td: %s, %s, %d\", tds[i].Key, tds[i].ExpireString(), tds[i].index)\n\t\ttimer.Del(tds[i])\n\t}\n\tprintTimer(timer)\n\tfor i := 0; i < 100; i++ {\n\t\ttds[i] = timer.Add(time.Duration(i)*time.Second+5*time.Minute, nil)\n\t}\n\tprintTimer(timer)\n\tfor i := 0; i < 100; i++ {\n\t\ttimer.Del(tds[i])\n\t}\n\tprintTimer(timer)\n\ttimer.Add(time.Second, nil)\n\ttime.Sleep(time.Second * 2)\n\tif len(timer.timers) != 0 {\n\t\tt.FailNow()\n\t}\n}\n\nfunc printTimer(timer *Timer) {\n\tlog.Infof(\"----------timers: %d ----------\", len(timer.timers))\n\tfor i := 0; i < len(timer.timers); i++ {\n\t\tlog.Infof(\"timer: %s, %s, index: %d\", timer.timers[i].Key, timer.timers[i].ExpireString(), timer.timers[i].index)\n\t}\n\tlog.Infof(\"--------------------\")\n}\n"
  },
  {
    "path": "pkg/websocket/conn.go",
    "content": "package websocket\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/Terry-Mao/goim/pkg/bufio\"\n)\n\nconst (\n\t// Frame header byte 0 bits from Section 5.2 of RFC 6455\n\tfinBit  = 1 << 7\n\trsv1Bit = 1 << 6\n\trsv2Bit = 1 << 5\n\trsv3Bit = 1 << 4\n\topBit   = 0x0f\n\n\t// Frame header byte 1 bits from Section 5.2 of RFC 6455\n\tmaskBit = 1 << 7\n\tlenBit  = 0x7f\n\n\tcontinuationFrame        = 0\n\tcontinuationFrameMaxRead = 100\n)\n\n// The message types are defined in RFC 6455, section 11.8.\nconst (\n\t// TextMessage denotes a text data message. The text message payload is\n\t// interpreted as UTF-8 encoded text data.\n\tTextMessage = 1\n\n\t// BinaryMessage denotes a binary data message.\n\tBinaryMessage = 2\n\n\t// CloseMessage denotes a close control message. The optional message\n\t// payload contains a numeric code and text. Use the FormatCloseMessage\n\t// function to format a close message payload.\n\tCloseMessage = 8\n\n\t// PingMessage denotes a ping control message. The optional message payload\n\t// is UTF-8 encoded text.\n\tPingMessage = 9\n\n\t// PongMessage denotes a ping control message. The optional message payload\n\t// is UTF-8 encoded text.\n\tPongMessage = 10\n)\n\nvar (\n\t// ErrMessageClose close control message\n\tErrMessageClose = errors.New(\"close control message\")\n\t// ErrMessageMaxRead continuation frame max read\n\tErrMessageMaxRead = errors.New(\"continuation frame max read\")\n)\n\n// Conn represents a WebSocket connection.\ntype Conn struct {\n\trwc     io.ReadWriteCloser\n\tr       *bufio.Reader\n\tw       *bufio.Writer\n\tmaskKey []byte\n}\n\n// new connection\nfunc newConn(rwc io.ReadWriteCloser, r *bufio.Reader, w *bufio.Writer) *Conn {\n\treturn &Conn{rwc: rwc, r: r, w: w, maskKey: make([]byte, 4)}\n}\n\n// WriteMessage write a message by type.\nfunc (c *Conn) WriteMessage(msgType int, msg []byte) (err error) {\n\tif err = c.WriteHeader(msgType, len(msg)); err != nil {\n\t\treturn\n\t}\n\terr = c.WriteBody(msg)\n\treturn\n}\n\n// WriteHeader write header frame.\nfunc (c *Conn) WriteHeader(msgType int, length int) (err error) {\n\tvar h []byte\n\tif h, err = c.w.Peek(2); err != nil {\n\t\treturn\n\t}\n\t// 1.First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)\n\th[0] = 0\n\th[0] |= finBit | byte(msgType)\n\t// 2.Second byte. Mask/Payload len(7bits)\n\th[1] = 0\n\tswitch {\n\tcase length <= 125:\n\t\t// 7 bits\n\t\th[1] |= byte(length)\n\tcase length < 65536:\n\t\t// 16 bits\n\t\th[1] |= 126\n\t\tif h, err = c.w.Peek(2); err != nil {\n\t\t\treturn\n\t\t}\n\t\tbinary.BigEndian.PutUint16(h, uint16(length))\n\tdefault:\n\t\t// 64 bits\n\t\th[1] |= 127\n\t\tif h, err = c.w.Peek(8); err != nil {\n\t\t\treturn\n\t\t}\n\t\tbinary.BigEndian.PutUint64(h, uint64(length))\n\t}\n\treturn\n}\n\n// WriteBody write a message body.\nfunc (c *Conn) WriteBody(b []byte) (err error) {\n\tif len(b) > 0 {\n\t\t_, err = c.w.Write(b)\n\t}\n\treturn\n}\n\n// Peek write peek.\nfunc (c *Conn) Peek(n int) ([]byte, error) {\n\treturn c.w.Peek(n)\n}\n\n// Flush flush writer buffer\nfunc (c *Conn) Flush() error {\n\treturn c.w.Flush()\n}\n\n// ReadMessage read a message.\nfunc (c *Conn) ReadMessage() (op int, payload []byte, err error) {\n\tvar (\n\t\tfin         bool\n\t\tfinOp, n    int\n\t\tpartPayload []byte\n\t)\n\tfor {\n\t\t// read frame\n\t\tif fin, op, partPayload, err = c.readFrame(); err != nil {\n\t\t\treturn\n\t\t}\n\t\tswitch op {\n\t\tcase BinaryMessage, TextMessage, continuationFrame:\n\t\t\tif fin && len(payload) == 0 {\n\t\t\t\treturn op, partPayload, nil\n\t\t\t}\n\t\t\t// continuation frame\n\t\t\tpayload = append(payload, partPayload...)\n\t\t\tif op != continuationFrame {\n\t\t\t\tfinOp = op\n\t\t\t}\n\t\t\t// final frame\n\t\t\tif fin {\n\t\t\t\top = finOp\n\t\t\t\treturn\n\t\t\t}\n\t\tcase PingMessage:\n\t\t\t// handler ping\n\t\t\tif err = c.WriteMessage(PongMessage, partPayload); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\tcase PongMessage:\n\t\t\t// handler pong\n\t\tcase CloseMessage:\n\t\t\t// handler close\n\t\t\terr = ErrMessageClose\n\t\t\treturn\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"unknown control message, fin=%t, op=%d\", fin, op)\n\t\t\treturn\n\t\t}\n\t\tif n > continuationFrameMaxRead {\n\t\t\terr = ErrMessageMaxRead\n\t\t\treturn\n\t\t}\n\t\tn++\n\t}\n}\n\nfunc (c *Conn) readFrame() (fin bool, op int, payload []byte, err error) {\n\tvar (\n\t\tb          byte\n\t\tp          []byte\n\t\tmask       bool\n\t\tmaskKey    []byte\n\t\tpayloadLen int64\n\t)\n\t// 1.First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)\n\tb, err = c.r.ReadByte()\n\tif err != nil {\n\t\treturn\n\t}\n\t// final frame\n\tfin = (b & finBit) != 0\n\t// rsv MUST be 0\n\tif rsv := b & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 {\n\t\treturn false, 0, nil, fmt.Errorf(\"unexpected reserved bits rsv1=%d, rsv2=%d, rsv3=%d\", b&rsv1Bit, b&rsv2Bit, b&rsv3Bit)\n\t}\n\t// op code\n\top = int(b & opBit)\n\t// 2.Second byte. Mask/Payload len(7bits)\n\tb, err = c.r.ReadByte()\n\tif err != nil {\n\t\treturn\n\t}\n\t// is mask payload\n\tmask = (b & maskBit) != 0\n\t// payload length\n\tswitch b & lenBit {\n\tcase 126:\n\t\t// 16 bits\n\t\tif p, err = c.r.Pop(2); err != nil {\n\t\t\treturn\n\t\t}\n\t\tpayloadLen = int64(binary.BigEndian.Uint16(p))\n\tcase 127:\n\t\t// 64 bits\n\t\tif p, err = c.r.Pop(8); err != nil {\n\t\t\treturn\n\t\t}\n\t\tpayloadLen = int64(binary.BigEndian.Uint64(p))\n\tdefault:\n\t\t// 7 bits\n\t\tpayloadLen = int64(b & lenBit)\n\t}\n\t// read mask key\n\tif mask {\n\t\tmaskKey, err = c.r.Pop(4)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif c.maskKey == nil {\n\t\t\tc.maskKey = make([]byte, 4)\n\t\t}\n\t\tcopy(c.maskKey, maskKey)\n\t}\n\t// read payload\n\tif payloadLen > 0 {\n\t\tif payload, err = c.r.Pop(int(payloadLen)); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif mask {\n\t\t\tmaskBytes(c.maskKey, 0, payload)\n\t\t}\n\t}\n\treturn\n}\n\n// Close close the connection.\nfunc (c *Conn) Close() error {\n\treturn c.rwc.Close()\n}\n\nfunc maskBytes(key []byte, pos int, b []byte) int {\n\tfor i := range b {\n\t\tb[i] ^= key[pos&3]\n\t\tpos++\n\t}\n\treturn pos & 3\n}\n"
  },
  {
    "path": "pkg/websocket/request.go",
    "content": "package websocket\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/Terry-Mao/goim/pkg/bufio\"\n)\n\n// Request request.\ntype Request struct {\n\tMethod     string\n\tRequestURI string\n\tProto      string\n\tHost       string\n\tHeader     http.Header\n\n\treader *bufio.Reader\n}\n\n// ReadRequest reads and parses an incoming request from b.\nfunc ReadRequest(r *bufio.Reader) (req *Request, err error) {\n\tvar (\n\t\tb  []byte\n\t\tok bool\n\t)\n\treq = &Request{reader: r}\n\tif b, err = req.readLine(); err != nil {\n\t\treturn\n\t}\n\tif req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(string(b)); !ok {\n\t\treturn nil, fmt.Errorf(\"malformed HTTP request %s\", b)\n\t}\n\tif req.Header, err = req.readMIMEHeader(); err != nil {\n\t\treturn\n\t}\n\treq.Host = req.Header.Get(\"Host\")\n\treturn req, nil\n}\n\nfunc (r *Request) readLine() ([]byte, error) {\n\tvar line []byte\n\tfor {\n\t\tl, more, err := r.reader.ReadLine()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// Avoid the copy if the first call produced a full line.\n\t\tif line == nil && !more {\n\t\t\treturn l, nil\n\t\t}\n\t\tline = append(line, l...)\n\t\tif !more {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn line, nil\n}\n\nfunc (r *Request) readMIMEHeader() (header http.Header, err error) {\n\tvar (\n\t\tline []byte\n\t\ti    int\n\t\tk, v string\n\t)\n\theader = make(http.Header, 16)\n\tfor {\n\t\tif line, err = r.readLine(); err != nil {\n\t\t\treturn\n\t\t}\n\t\tline = trim(line)\n\t\tif len(line) == 0 {\n\t\t\treturn\n\t\t}\n\t\tif i = bytes.IndexByte(line, ':'); i <= 0 {\n\t\t\terr = fmt.Errorf(\"malformed MIME header line: \" + string(line))\n\t\t\treturn\n\t\t}\n\t\tk = string(line[:i])\n\t\t// Skip initial spaces in value.\n\t\ti++ // skip colon\n\t\tfor i < len(line) && (line[i] == ' ' || line[i] == '\\t') {\n\t\t\ti++\n\t\t}\n\t\tv = string(line[i:])\n\t\theader.Add(k, v)\n\t}\n}\n\n// parseRequestLine parses \"GET /foo HTTP/1.1\" into its three parts.\nfunc parseRequestLine(line string) (method, requestURI, proto string, ok bool) {\n\ts1 := strings.Index(line, \" \")\n\ts2 := strings.Index(line[s1+1:], \" \")\n\tif s1 < 0 || s2 < 0 {\n\t\treturn\n\t}\n\ts2 += s1 + 1\n\treturn line[:s1], line[s1+1 : s2], line[s2+1:], true\n}\n\n// trim returns s with leading and trailing spaces and tabs removed.\n// It does not assume Unicode or UTF-8.\nfunc trim(s []byte) []byte {\n\ti := 0\n\tfor i < len(s) && (s[i] == ' ' || s[i] == '\\t') {\n\t\ti++\n\t}\n\tn := len(s)\n\tfor n > i && (s[n-1] == ' ' || s[n-1] == '\\t') {\n\t\tn--\n\t}\n\treturn s[i:n]\n}\n"
  },
  {
    "path": "pkg/websocket/server.go",
    "content": "package websocket\n\nimport (\n\t\"crypto/sha1\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/Terry-Mao/goim/pkg/bufio\"\n)\n\nvar (\n\tkeyGUID = []byte(\"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\")\n\t// ErrBadRequestMethod bad request method\n\tErrBadRequestMethod = errors.New(\"bad method\")\n\t// ErrNotWebSocket not websocket protocal\n\tErrNotWebSocket = errors.New(\"not websocket protocol\")\n\t// ErrBadWebSocketVersion bad websocket version\n\tErrBadWebSocketVersion = errors.New(\"missing or bad WebSocket Version\")\n\t// ErrChallengeResponse mismatch challenge response\n\tErrChallengeResponse = errors.New(\"mismatch challenge/response\")\n)\n\n// Upgrade Switching Protocols\nfunc Upgrade(rwc io.ReadWriteCloser, rr *bufio.Reader, wr *bufio.Writer, req *Request) (conn *Conn, err error) {\n\tif req.Method != \"GET\" {\n\t\treturn nil, ErrBadRequestMethod\n\t}\n\tif req.Header.Get(\"Sec-Websocket-Version\") != \"13\" {\n\t\treturn nil, ErrBadWebSocketVersion\n\t}\n\tif strings.ToLower(req.Header.Get(\"Upgrade\")) != \"websocket\" {\n\t\treturn nil, ErrNotWebSocket\n\t}\n\tif !strings.Contains(strings.ToLower(req.Header.Get(\"Connection\")), \"upgrade\") {\n\t\treturn nil, ErrNotWebSocket\n\t}\n\tchallengeKey := req.Header.Get(\"Sec-Websocket-Key\")\n\tif challengeKey == \"\" {\n\t\treturn nil, ErrChallengeResponse\n\t}\n\t_, _ = wr.WriteString(\"HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\n\")\n\t_, _ = wr.WriteString(\"Sec-WebSocket-Accept: \" + computeAcceptKey(challengeKey) + \"\\r\\n\\r\\n\")\n\tif err = wr.Flush(); err != nil {\n\t\treturn\n\t}\n\treturn newConn(rwc, rr, wr), nil\n}\n\nfunc computeAcceptKey(challengeKey string) string {\n\th := sha1.New()\n\t_, _ = h.Write([]byte(challengeKey))\n\t_, _ = h.Write(keyGUID)\n\treturn base64.StdEncoding.EncodeToString(h.Sum(nil))\n}\n"
  },
  {
    "path": "pkg/websocket/server_test.go",
    "content": "package websocket\n\nimport (\n\t\"net\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/net/websocket\"\n\n\t\"github.com/Terry-Mao/goim/pkg/bufio\"\n)\n\nfunc TestServer(t *testing.T) {\n\tvar (\n\t\tdata = []byte{0, 1, 2}\n\t)\n\tln, err := net.Listen(\"tcp\", \":8080\")\n\tif err != nil {\n\t\tt.FailNow()\n\t}\n\tgo func() {\n\t\tconn, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\trd := bufio.NewReader(conn)\n\t\twr := bufio.NewWriter(conn)\n\t\treq, err := ReadRequest(rd)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tif req.RequestURI != \"/sub\" {\n\t\t\tt.Error(err)\n\t\t}\n\t\tws, err := Upgrade(conn, rd, wr, req)\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tif err = ws.WriteMessage(BinaryMessage, data); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tif err = ws.Flush(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\top, b, err := ws.ReadMessage()\n\t\tif err != nil || op != BinaryMessage || !reflect.DeepEqual(b, data) {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\ttime.Sleep(time.Millisecond * 100)\n\t// ws client\n\tws, err := websocket.Dial(\"ws://127.0.0.1:8080/sub\", \"\", \"*\")\n\tif err != nil {\n\t\tt.FailNow()\n\t}\n\t// receive binary frame\n\tvar b []byte\n\tif err = websocket.Message.Receive(ws, &b); err != nil {\n\t\tt.FailNow()\n\t}\n\tif !reflect.DeepEqual(b, data) {\n\t\tt.FailNow()\n\t}\n\t// send binary frame\n\tif err = websocket.Message.Send(ws, data); err != nil {\n\t\tt.FailNow()\n\t}\n}\n"
  },
  {
    "path": "scripts/README.md",
    "content": "#  Installing\n\n### Install JDK\n```\n    ./jdk.sh\n```\n\n### Install Zookeeper\n```\n    ./zk.sh\n```\n\n### Install Kafka\n```\n    ./kafka.sh\n```\n"
  },
  {
    "path": "scripts/jdk8.sh",
    "content": "#!/bin/bash\n# config parameters\nJDK_HOME=/usr/local/jdk8/\n# download jdk\ncurl -L https://download.oracle.com/otn-pub/java/jdk/8u191-b12/2787e4a523244c269598db4e85c51e0c/jdk-8u191-linux-x64.tar.gz -o jdk-8u191-linux-x64.tar.gz\ntar zxf jdk-8u191-linux-x64.tar.gz \n# install jdk\nmkdir -p $JDK_HOME\nmv jdk1.8.0/* $JDK_HOME\nupdate-alternatives --install \"/usr/bin/java\" \"java\" \"$JDK_HOME/bin/java\" 1500\nupdate-alternatives --install \"/usr/bin/javac\" \"javac\" \"$JDK_HOME/bin/javac\" 1500\nupdate-alternatives --install \"/usr/bin/javaws\" \"javaws\" \"$JDK_HOME/bin/javaws\" 1500\n"
  },
  {
    "path": "scripts/kafka.sh",
    "content": "#!/bin/bash\n# config parameters\nJDK_HOME=/usr/local/jdk8/\nKAFKA_HOME=/data/app/kafka/\nKAFKA_CONF=/data/app/kafka/config/server.properties \nKAFKA_DATA=/data/kafka_data/\nKAFKA_SUPERVISOR=/etc/supervisor/conf.d/kafka.conf\n\nUSAGE=\"./kafka.sh {broker.id} {current.addr} {zk.addr1,zk.addr2,zk.addr3}\\r\\neg: ./kafka.sh 1 127.0.0.1 127.0.0.1:2181,127.0.0.2:2181,127.0.0.3:2181\"\n# check parammeters\nBROKER_ID=$1\nCURRENT_ADDR=$2\nZK_ADDRS=$3\nif [ ! \"$BROKER_ID\" ]; then\necho \"kafka {broker.id} can not be null\"\necho -e  $USAGE\nexit 1\nfi\nif [ ! \"$CURRENT_ADDR\" ]; then\necho \"current addr can not be null\"\nexit 1\nfi\nif [ ! \"$ZK_ADDRS\" ]; then\necho \"zookeeper addrs can not be null\"\nexit 1\nfi\nif [ ! -d \"$JDK_HOME\" ]; then\necho \"jdk not exist down jdk8\"\n./jdk8.sh\nfi\n\n# download kafka\ncurl \"http://apache.stu.edu.tw/kafka/2.1.0/kafka_2.11-2.1.0.tgz\"  -o kafka_2.11-2.1.0.tgz\ntar zxf kafka_2.11-2.1.0.tgz\n# install kafka\nmkdir -p $KAFKA_HOME\nmkdir -p $KAFKA_DATA\nmv kafka_2.11-2.1.0/* $KAFKA_HOME\n# kafka config\necho \"broker.id=$BROKER_ID\">$KAFKA_CONF\necho \"host.name=$CURRENT_ADDR\">>$KAFKA_CONF\necho \"zookeeper.connect=$ZK_ADDRS/kafka\">>$KAFKA_CONF\necho \"num.network.threads=4\nnum.io.threads=4\nsocket.send.buffer.bytes=1024000\nsocket.receive.buffer.bytes=1024000\nsocket.request.max.bytes=52428800\nlog.dirs=$KAFKA_DATA\nnum.partitions=9\nnum.recovery.threads.per.data.dir=1\nlog.cleanup.policy=delete\nlog.retention.hours=24\nlog.segment.bytes=536870912\nlog.retention.check.interval.ms=300000\nlog.cleaner.enable=false\nzookeeper.connection.timeout.ms=6000\ndefault.replication.factor=2\ndelete.topic.enable=false\nauto.create.topics.enable=false\">>$KAFKA_CONF\n\n# install supervisor\nif ! type \"supervisorctl\" > /dev/null; then\n    apt-get install -y supervisor\nfi\n# kafka supervisor config\nmkdir -p /etc/supervisor/conf.d/\nmkdir -p /data/log/kafka/\necho \"[program:kafka]\ncommand=$KAFKA_HOME/bin/kafka-server-start.sh $KAFKA_CONF\nuser=root\nautostart=true\nautorestart=true\nexitcodes=0\nstartsecs=10\nstartretries=10\nstopwaitsecs=10\nstopsignal=KILL\nstdout_logfile=/data/log/kafka/stdout.log\nstderr_logfile=/data/log/kafka/stderr.log\nstdout_logfile_maxbytes=100MB\nstdout_logfile_backups=5\nstderr_logfile_maxbytes=100MB\nstderr_logfile_backups=5\nenvironment=JAVA_HOME=$JDK_HOME,JRE_HOME='$JDK_HOME/jre',KAFKA_HEAP_OPTS='-Xmx6g -Xms6g -XX:MetaspaceSize=96m -XX:G1HeapRegionSize=16M -XX:MinMetaspaceFreeRatio=50 -XX:MaxMetaspaceFreeRatio=80'\">$_KAFKA_SUPERVISOR\n\nsupervisorctl update\n"
  },
  {
    "path": "scripts/zk.sh",
    "content": "#!/bin/bash\n# config parameters\nJDK_HOME=/usr/local/jdk8\nZK_HOME=/data/server/zookeeper\nZK_SUPERVISOR=/etc/supervisor/conf.d/zookeeper.conf\n\nUSAGE=\"./zk.sh {zk.id} {zk.addr1,zk.addr2,zk.addr3}\\r\\neg:./zk.sh 1 127.0.0.1:2181,127.0.0.2:2181,127.0.0.3:2181\"\n# check parammeters\nZK_ID=$1\nstr=$2\nZK_ADDRS=(${str//,/ })\nZK_ADDRS_SIZE=${#ZK_ADDRS[@]}\nif [ ! -n \"$1\" ];then\necho -e $USAGE\nexit 1\nfi\nif [ ! -n \"$2\" ];then\necho -e $USAGE\nexit 1\nfi \necho $ZK_ID\necho ${ZK_ADDRS[@]}\n\n\nset -e\n\ncurl -L http://apache.website-solution.net/zookeeper/zookeeper-3.4.12/zookeeper-3.4.12.tar.gz -o zookeeper-3.4.12.tar.gz\ntar zxf zookeeper-3.4.12.tar.gz\nmkdir -p $ZK_HOME\necho $ZK_ID>$ZK_HOME/myid\nmv zookeeper-3.4.12/* $ZK_HOME\necho \"tickTime=2000\" > $ZK_HOME/conf/zoo.cfg\necho \"initLimit=10\" >> $ZK_HOME/conf/zoo.cfg \necho \"syncLimit=5\" >> $ZK_HOME/conf/zoo.cfg\necho \"dataDir=/data/server/zookeeper\" >> $ZK_HOME/conf/zoo.cfg\necho \"clientPort=2181\" >> $ZK_HOME/conf/zoo.cfg\necho \"autopurge.snapRetainCount=5\" >> $ZK_HOME/conf/zoo.cfg\necho \"autopurge.purgeInterval=24\" >> $ZK_HOME/conf/zoo.cfg\nfor ((index=0;index<$ZK_ADDRS_SIZE;index++))\ndo\n    id=$[$index+1]\n    echo \"server.$id=${ZK_ADDRS[index]}:2888:3888\" >> $ZK_HOME/conf/zoo.cfg \ndone\n\n# install supervisor\nif ! type \"supervisorctl\" > /dev/null; then\n    apt-get install -y supervisor\nfi\n# zookeeper supervisor config\nmkdir -p /etc/supervisor/conf.d/\nmkdir -p /data/log/zookeeper/\necho \"[program:zookeeper]\ncommand=${ZK_HOME}/bin/zkServer.sh start-foreground\ndirectory=${ZK_HOME}\nuser=root\nautostart=true\nautorestart=true\nexitcodes=0\nstartsecs=10\nstartretries=10\nstopwaitsecs=10\nstopsignal=KILL\nstdout_logfile=/data/log/zookeeper/stdout.log\nstderr_logfile=/data/log/zookeeper/stderr.log\nstdout_logfile_maxbytes=100MB\nstdout_logfile_backups=5\nstderr_logfile_maxbytes=100MB\nstderr_logfile_backups=5\nenvironment=JAVA_HOME=$JDK_HOME,JRE_HOME='$JDK_HOME/jre'\n\">$ZK_SUPERVISOR\n\nsupervisorctl update\n"
  }
]