├── .github └── workflows │ ├── build_bsd.yml │ ├── build_linux.yml │ ├── build_windows.yml │ └── close_inactive_issues.yml ├── LICENSE ├── README.md ├── allocator.go ├── client.go ├── client_test.go ├── codec ├── codec.go └── codec_test.go ├── context.go ├── context_test.go ├── error.go ├── examples ├── README.md ├── bench │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── bench_nbio │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── bench_pool │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── broadcast │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── broadcast_pool │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── graceful │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── httprpc │ ├── README.md │ ├── arpc.js │ ├── chat.html │ └── server.go ├── mempool_fuzzy │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── micro │ ├── etcd │ │ ├── client │ │ │ └── client.go │ │ ├── server │ │ │ └── server.go │ │ └── tools │ │ │ ├── etcd1.bat │ │ │ ├── etcd2.bat │ │ │ ├── etcd3.bat │ │ │ └── etcdcluster.bat │ └── redis │ │ ├── client │ │ └── client.go │ │ └── server │ │ └── server.go ├── middleware │ ├── coder │ │ ├── gzip │ │ │ ├── client │ │ │ │ └── client.go │ │ │ └── server │ │ │ │ └── server.go │ │ ├── msgpack │ │ │ ├── client │ │ │ │ └── client.go │ │ │ └── server │ │ │ │ └── server.go │ │ └── tracing │ │ │ ├── client │ │ │ └── client.go │ │ │ └── server │ │ │ └── server.go │ └── router │ │ ├── client │ │ └── client.go │ │ └── server │ │ └── server.go ├── nbio │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── notify │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── pool │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── protocols │ ├── kcp │ │ ├── client │ │ │ └── client.go │ │ └── server │ │ │ └── server.go │ ├── quic │ │ ├── client │ │ │ └── client.go │ │ └── server │ │ │ └── server.go │ ├── tls │ │ ├── client │ │ │ └── client.go │ │ └── server │ │ │ └── server.go │ ├── unixsocket │ │ ├── client │ │ │ └── client.go │ │ └── server │ │ │ └── server.go │ ├── utp │ │ ├── client │ │ │ └── client.go │ │ └── server │ │ │ └── server.go │ └── websocket │ │ ├── client │ │ └── client.go │ │ ├── jsclient │ │ ├── arpc.js │ │ └── hello.html │ │ └── server │ │ └── server.go ├── pubsub │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── pubsub_ws_with_nbio │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── rpc │ ├── client │ │ └── client.go │ └── server │ │ └── server.go ├── stream │ ├── client │ │ └── client.go │ └── server │ │ └── server.go └── webchat │ ├── README.md │ ├── arpc.js │ ├── chat.html │ └── server.go ├── extension ├── arpchttp │ └── handler.go ├── jsclient │ └── arpc.js ├── listener │ └── listener.go ├── micro │ ├── etcd │ │ ├── discovery.go │ │ └── register.go │ ├── redis │ │ ├── client.go │ │ ├── discovery.go │ │ └── register.go │ └── service.go ├── middleware │ ├── coder │ │ ├── appender.go │ │ ├── config.go │ │ ├── gzip │ │ │ └── gzip.go │ │ ├── msgpack │ │ │ └── msgpack.go │ │ └── tracing │ │ │ ├── reporter.go │ │ │ ├── tracer.go │ │ │ └── values.go │ └── router │ │ ├── graceful.go │ │ ├── logger.go │ │ └── recover.go ├── protocol │ ├── quic │ │ ├── quic.go │ │ └── quic_test.go │ └── websocket │ │ ├── websocket.go │ │ └── websocket_test.go └── pubsub │ ├── client.go │ ├── error.go │ ├── pubsub_test.go │ ├── router.go │ ├── server.go │ └── topic.go ├── go.mod ├── go.sum ├── handler.go ├── handler_test.go ├── log ├── log.go └── log_test.go ├── proto.go ├── proto_test.go ├── server.go ├── server_test.go ├── stream.go └── util ├── util.go └── util_test.go /.github/workflows/build_bsd.yml: -------------------------------------------------------------------------------- 1 | name: build-macos 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | GO111MODULE: off 13 | 14 | jobs: 15 | test: 16 | name: build-macos 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | go: [1.20.x] 21 | os: [macos-latest] 22 | runs-on: ${{ matrix.os}} 23 | steps: 24 | - name: install golang 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: ${{ matrix.go }} 28 | - name: checkout code 29 | uses: actions/checkout@v2 30 | - name: go env 31 | run: | 32 | printf "$(go version)\n" 33 | printf "\n\ngo environment:\n\n" 34 | go get -u github.com/lesismal/arpc 35 | ulimit -n 30000 36 | go env 37 | echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" 38 | - name: go test 39 | run: go test -timeout 60s -coverprofile="./coverage" 40 | -------------------------------------------------------------------------------- /.github/workflows/build_linux.yml: -------------------------------------------------------------------------------- 1 | name: build-linux 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | GO111MODULE: off 13 | 14 | jobs: 15 | test: 16 | name: build-linux 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | go: [1.13.x] 21 | os: [ubuntu-latest] 22 | runs-on: ${{ matrix.os}} 23 | steps: 24 | - name: install golang 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: ${{ matrix.go }} 28 | - name: checkout code 29 | uses: actions/checkout@v2 30 | - name: go env 31 | run: | 32 | printf "$(go version)\n" 33 | printf "\n\ngo environment:\n\n" 34 | go get -u github.com/lesismal/arpc 35 | ulimit -n 30000 36 | go env 37 | echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" 38 | - name: go test 39 | run: go test -timeout 60s -coverprofile="./coverage" 40 | - name: update code coverage report 41 | uses: codecov/codecov-action@v1.2.1 42 | with: 43 | file: ./coverage 44 | flags: unittests 45 | verbose: true 46 | name: codecov-arpc -------------------------------------------------------------------------------- /.github/workflows/build_windows.yml: -------------------------------------------------------------------------------- 1 | name: build-windows 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | GO111MODULE: off 13 | 14 | jobs: 15 | test: 16 | name: build-windows 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | go: [1.13.x] 21 | os: [windows-latest] 22 | runs-on: ${{ matrix.os}} 23 | steps: 24 | - name: install golang 25 | uses: actions/setup-go@v2 26 | with: 27 | go-version: ${{ matrix.go }} 28 | - name: checkout code 29 | uses: actions/checkout@v2 30 | - name: go env 31 | run: | 32 | printf "$(go version)\n" 33 | printf "\n\ngo environment:\n\n" 34 | go get -u github.com/lesismal/arpc 35 | go env 36 | echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" 37 | - name: go test 38 | run: go test -timeout 60s -coverprofile="./coverage" -------------------------------------------------------------------------------- /.github/workflows/close_inactive_issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v3 14 | with: 15 | days-before-issue-stale: 30 16 | days-before-issue-close: 14 17 | stale-issue-label: "stale" 18 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 19 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 20 | days-before-pr-stale: -1 21 | days-before-pr-close: -1 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 lesismal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /codec/codec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package codec 6 | 7 | import ( 8 | "encoding/json" 9 | ) 10 | 11 | // DefaultCodec is the default codec used by arpc 12 | var DefaultCodec Codec = &JSONCodec{} 13 | 14 | // Codec is the interface that wraps the arpc Message data encoding method. 15 | // 16 | // Marshal returns the JSON encoding of v 17 | // 18 | // Unmarshal parses the Message data and stores the result 19 | // in the value pointed to by v 20 | type Codec interface { 21 | Marshal(v interface{}) ([]byte, error) 22 | Unmarshal(data []byte, v interface{}) error 23 | } 24 | 25 | // JSONCodec wraps std json 26 | type JSONCodec struct{} 27 | 28 | // Marshal wraps std json.Marshal 29 | func (j *JSONCodec) Marshal(v interface{}) ([]byte, error) { 30 | return json.Marshal(v) 31 | } 32 | 33 | // Unmarshal wraps std json.Unmarshal 34 | func (j *JSONCodec) Unmarshal(data []byte, v interface{}) error { 35 | return json.Unmarshal(data, v) 36 | } 37 | 38 | // SetCodec sets default codec instance 39 | func SetCodec(c Codec) { 40 | DefaultCodec = c 41 | } 42 | -------------------------------------------------------------------------------- /codec/codec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package codec 6 | 7 | import ( 8 | "bytes" 9 | "encoding/gob" 10 | "reflect" 11 | "testing" 12 | ) 13 | 14 | // codecGob . 15 | type codecGob struct{} 16 | 17 | // Marshal . 18 | func (c *codecGob) Marshal(v interface{}) ([]byte, error) { 19 | buffer := &bytes.Buffer{} 20 | err := gob.NewEncoder(buffer).Encode(v) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return buffer.Bytes(), nil 25 | } 26 | 27 | // Unmarshal . 28 | func (c *codecGob) Unmarshal(data []byte, v interface{}) error { 29 | return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v) 30 | } 31 | func TestJSONCodec_Marshal(t *testing.T) { 32 | type Value struct { 33 | I int 34 | S string 35 | } 36 | 37 | v1 := &Value{I: 3, S: "hello"} 38 | v2 := &Value{} 39 | jc := &JSONCodec{} 40 | data, err := jc.Marshal(v1) 41 | if err != nil { 42 | t.Errorf("JSONCodec.Marshal() error = %v, wantErr %v", err, nil) 43 | return 44 | } 45 | err = jc.Unmarshal(data, v2) 46 | if err != nil { 47 | t.Errorf("JSONCodec.Unmarshal() error = %v, wantErr %v", err, nil) 48 | return 49 | } 50 | if !reflect.DeepEqual(v1, v2) { 51 | t.Errorf("v2 = %v, want %v", v2, v1) 52 | } 53 | } 54 | 55 | func TestJSONCodec_Unmarshal(t *testing.T) { 56 | TestJSONCodec_Marshal(t) 57 | } 58 | 59 | func TestSetCodec(t *testing.T) { 60 | type Value struct { 61 | I int 62 | S string 63 | } 64 | 65 | gc := &codecGob{} 66 | SetCodec(gc) 67 | 68 | v1 := &Value{I: 3, S: "hello"} 69 | v2 := &Value{} 70 | dc := DefaultCodec 71 | data, err := dc.Marshal(v1) 72 | if err != nil { 73 | t.Errorf("JSONCodec.Marshal() error = %v, wantErr %v", err, nil) 74 | return 75 | } 76 | err = gc.Unmarshal(data, v2) 77 | if err != nil { 78 | t.Errorf("JSONCodec.Unmarshal() error = %v, wantErr %v", err, nil) 79 | return 80 | } 81 | if !reflect.DeepEqual(v1, v2) { 82 | t.Errorf("v2 = %v, want %v", v2, v1) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package arpc 6 | 7 | import ( 8 | "math" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var ( 14 | contextPool = sync.Pool{ 15 | New: func() interface{} { 16 | return &Context{} 17 | }, 18 | } 19 | 20 | emptyContext = Context{} 21 | ) 22 | 23 | // Context represents an arpc Call's context. 24 | type Context struct { 25 | Client *Client 26 | Message *Message 27 | 28 | index int 29 | handlers []HandlerFunc 30 | responseErr interface{} 31 | } 32 | 33 | func (ctx *Context) Release() { 34 | ctx.Message.Release() 35 | *ctx = emptyContext 36 | contextPool.Put(ctx) 37 | } 38 | 39 | func (ctx *Context) ResponseError() interface{} { 40 | return ctx.responseErr 41 | } 42 | 43 | // Get returns value for key. 44 | func (ctx *Context) Get(key interface{}) (interface{}, bool) { 45 | if len(ctx.Message.values) == 0 { 46 | return nil, false 47 | } 48 | value, ok := ctx.Message.values[key] 49 | return value, ok 50 | } 51 | 52 | // Set sets key-value pair. 53 | func (ctx *Context) Set(key interface{}, value interface{}) { 54 | if key == nil || value == nil { 55 | return 56 | } 57 | if ctx.Message.values == nil { 58 | ctx.Message.values = map[interface{}]interface{}{} 59 | } 60 | ctx.Message.values[key] = value 61 | } 62 | 63 | // Values returns values. 64 | func (ctx *Context) Values() map[interface{}]interface{} { 65 | if ctx.Message == nil { 66 | return nil 67 | } 68 | return ctx.Message.values 69 | } 70 | 71 | // Body returns body. 72 | func (ctx *Context) Body() []byte { 73 | return ctx.Message.Data() 74 | } 75 | 76 | // Bind parses the body data and stores the result 77 | // in the value pointed to by v. 78 | func (ctx *Context) Bind(v interface{}) error { 79 | msg := ctx.Message 80 | if msg.IsError() { 81 | return msg.Error() 82 | } 83 | if v != nil { 84 | data := msg.Data() 85 | switch vt := v.(type) { 86 | case *[]byte: 87 | *vt = data 88 | case *string: 89 | *vt = string(data) 90 | // case *error: 91 | // *vt = errors.New(util.BytesToStr(data)) 92 | default: 93 | return ctx.Client.Codec.Unmarshal(data, v) 94 | } 95 | } 96 | return nil 97 | } 98 | 99 | // Write responses a Message to the Client. 100 | func (ctx *Context) Write(v interface{}) error { 101 | return ctx.write(v, false, TimeForever) 102 | } 103 | 104 | // WriteWithTimeout responses a Message to the Client with timeout. 105 | func (ctx *Context) WriteWithTimeout(v interface{}, timeout time.Duration) error { 106 | return ctx.write(v, false, timeout) 107 | } 108 | 109 | // Error responses an error Message to the Client. 110 | func (ctx *Context) Error(v interface{}) error { 111 | return ctx.write(v, v != nil, TimeForever) 112 | } 113 | 114 | // Next calls next middleware or method/router handler. 115 | func (ctx *Context) Next() { 116 | index := int(ctx.index) 117 | if index < len(ctx.handlers) { 118 | ctx.index++ 119 | ctx.handlers[index](ctx) 120 | } 121 | } 122 | 123 | // Abort stops the one-by-one-calling of middlewares and method/router handler. 124 | func (ctx *Context) Abort() { 125 | ctx.index = int(math.MaxInt8) 126 | } 127 | 128 | // Deadline implements stdlib's Context. 129 | func (ctx *Context) Deadline() (deadline time.Time, ok bool) { 130 | return 131 | } 132 | 133 | // Done implements stdlib's Context. 134 | func (ctx *Context) Done() <-chan struct{} { 135 | return nil 136 | } 137 | 138 | // Err implements stdlib's Context. 139 | func (ctx *Context) Err() error { 140 | return nil 141 | } 142 | 143 | // Value returns the value associated with this context for key, implements stdlib's Context. 144 | func (ctx *Context) Value(key interface{}) interface{} { 145 | value, _ := ctx.Get(key) 146 | return value 147 | } 148 | 149 | func (ctx *Context) write(v interface{}, isError bool, timeout time.Duration) error { 150 | cli := ctx.Client 151 | if !cli.Handler.AsyncWrite() { 152 | return ctx.writeDirectly(v, isError) 153 | } 154 | req := ctx.Message 155 | if req.Cmd() != CmdRequest { 156 | return ErrContextResponseToNotify 157 | } 158 | if _, ok := v.(error); ok { 159 | isError = true 160 | } 161 | if isError { 162 | ctx.responseErr = v 163 | } 164 | 165 | rsp := newMessage(CmdResponse, req.method(), v, isError, req.IsAsync(), req.Seq(), cli.Handler, cli.Codec, ctx.Message.values) 166 | return cli.PushMsg(rsp, timeout) 167 | } 168 | 169 | func (ctx *Context) writeDirectly(v interface{}, isError bool) error { 170 | cli := ctx.Client 171 | req := ctx.Message 172 | if req.Cmd() != CmdRequest { 173 | return ErrContextResponseToNotify 174 | } 175 | if _, ok := v.(error); ok { 176 | isError = true 177 | } 178 | rsp := newMessage(CmdResponse, req.method(), v, isError, req.IsAsync(), req.Seq(), cli.Handler, cli.Codec, ctx.Message.values) 179 | if !cli.reconnecting { 180 | coders := cli.Handler.Coders() 181 | for j := 0; j < len(coders); j++ { 182 | rsp = coders[j].Encode(cli, rsp) 183 | } 184 | _, err := cli.Handler.Send(cli.Conn, rsp.Buffer) 185 | if err != nil { 186 | cli.Conn.Close() 187 | } 188 | return err 189 | } 190 | cli.dropMessage(rsp) 191 | return ErrClientReconnecting 192 | } 193 | 194 | func newContext(cli *Client, msg *Message, handlers []HandlerFunc) *Context { 195 | ctx := contextPool.Get().(*Context) 196 | ctx.Client = cli 197 | ctx.Message = msg 198 | ctx.Message.values = msg.values 199 | ctx.handlers = handlers 200 | return ctx 201 | } 202 | -------------------------------------------------------------------------------- /context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package arpc 6 | 7 | import ( 8 | "errors" 9 | "testing" 10 | 11 | "github.com/lesismal/arpc/codec" 12 | ) 13 | 14 | func TestContext_Get(t *testing.T) { 15 | ctx := &Context{Message: &Message{}} 16 | if v, ok := ctx.Get("key"); ok { 17 | t.Fatalf("Context.Get() error, returns %v, want nil", v) 18 | } 19 | values := ctx.Values() 20 | if len(values) > 0 { 21 | t.Fatalf("invalid Context.Values() length, returns %v, want 0", len(values)) 22 | } 23 | } 24 | 25 | func TestContext_Set(t *testing.T) { 26 | key := "key" 27 | value := "value" 28 | 29 | ctx := &Context{Message: &Message{}} 30 | ctx.Set(key, nil) 31 | cv, ok := ctx.Get(key) 32 | if ok { 33 | t.Fatalf("Context.Get() failed: Get '%v', want nil", cv) 34 | } 35 | 36 | ctx.Set(key, value) 37 | cv, ok = ctx.Get(key) 38 | if !ok { 39 | t.Fatalf("Context.Get() failed: Get nil, want '%v'", value) 40 | } 41 | if cv != value { 42 | t.Fatalf("Context.Get() failed: Get '%v', want '%v'", cv, value) 43 | } 44 | } 45 | 46 | func TestContext_Body(t *testing.T) { 47 | bodyValue := "body" 48 | ctx := &Context{ 49 | Client: &Client{Codec: codec.DefaultCodec}, 50 | Message: newMessage(CmdRequest, "method", bodyValue, false, false, 0, DefaultHandler, codec.DefaultCodec, nil), 51 | } 52 | if string(ctx.Body()) != bodyValue { 53 | t.Fatalf("Context.Body() = %v, want %v", string(ctx.Body()), bodyValue) 54 | } 55 | } 56 | 57 | func TestContext_Bind(t *testing.T) { 58 | ctx := &Context{ 59 | Client: &Client{Codec: codec.DefaultCodec}, 60 | Message: newMessage(CmdRequest, "method", "data", true, false, 0, DefaultHandler, codec.DefaultCodec, nil), 61 | } 62 | if err := ctx.Bind(nil); err == nil { 63 | t.Fatalf("Context.Bind() error = nil, want %v", err) 64 | } 65 | } 66 | 67 | func TestContext_Abort(t *testing.T) { 68 | ok := false 69 | h1 := func(ctx *Context) { 70 | ctx.Abort() 71 | } 72 | h2 := func(ctx *Context) { 73 | ok = true 74 | } 75 | ctx := &Context{handlers: []HandlerFunc{h1, h2}} 76 | ctx.Next() 77 | if ok { 78 | t.Fatalf("Context.Abort() ok != false, have %v", ok) 79 | } 80 | } 81 | 82 | func TestContext_Deadline(t *testing.T) { 83 | ctx := &Context{} 84 | if deadline, ok := ctx.Deadline(); !deadline.IsZero() || ok { 85 | t.Fatalf("Context.Deadline() err, have %v, %v", ok, deadline.IsZero()) 86 | } 87 | } 88 | 89 | func TestContext_Done(t *testing.T) { 90 | ctx := &Context{} 91 | if done := ctx.Done(); done != nil { 92 | t.Fatalf("Context.Bind() done != nil, have %v", done) 93 | } 94 | } 95 | 96 | func TestContext_Err(t *testing.T) { 97 | ctx := &Context{} 98 | ctx.Err() 99 | if err := ctx.Err(); err != nil { 100 | t.Fatalf("Context.Err() error != nil, have %v", err) 101 | } 102 | } 103 | 104 | func TestContext_Value(t *testing.T) { 105 | ctx := &Context{Message: &Message{}} 106 | if value := ctx.Value(3); value != nil { 107 | t.Fatalf("Context.Value() value != nil, have %v", value) 108 | } 109 | ctx.Set("key", "value") 110 | if value := ctx.Value("key"); value != "value" { 111 | t.Fatalf("Context.Value() value != 'value', have %v", value) 112 | } 113 | } 114 | 115 | func TestContext_ResponseError(t *testing.T) { 116 | ctx := &Context{ 117 | Client: &Client{Handler: DefaultHandler}, 118 | Message: newMessage(CmdRequest, "test", nil, false, false, 0, DefaultHandler, codec.DefaultCodec, nil), 119 | } 120 | err := errors.New("test err") 121 | ctx.Error(err) 122 | if ctx.ResponseError() != err { 123 | t.Fatalf("Context.ResponseError() != 'test err'") 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package arpc 6 | 7 | import "errors" 8 | 9 | // client error 10 | var ( 11 | // ErrClientTimeout represents a timeout error because of timer or context. 12 | ErrClientTimeout = errors.New("timeout") 13 | 14 | // ErrClientInvalidTimeoutZero represents an error of 0 time parameter. 15 | ErrClientInvalidTimeoutZero = errors.New("invalid timeout, should not be 0") 16 | 17 | // ErrClientInvalidTimeoutLessThanZero represents an error of less than 0 time parameter. 18 | ErrClientInvalidTimeoutLessThanZero = errors.New("invalid timeout, should not be < 0") 19 | 20 | // ErrClientInvalidTimeoutZeroWithNonNilCallback represents an error with 0 time parameter but with non-nil callback. 21 | ErrClientInvalidTimeoutZeroWithNonNilCallback = errors.New("invalid timeout 0 with non-nil callback") 22 | 23 | // ErrClientOverstock represents an error of Client's send queue is full. 24 | ErrClientOverstock = errors.New("timeout: rpc Client's send queue is full") 25 | 26 | // ErrClientReconnecting represents an error that Client is reconnecting. 27 | ErrClientReconnecting = errors.New("client reconnecting") 28 | 29 | // ErrClientStopped represents an error that Client is stopped. 30 | ErrClientStopped = errors.New("client stopped") 31 | 32 | // ErrClientInvalidPoolDialers represents an error of empty dialer array. 33 | ErrClientInvalidPoolDialers = errors.New("invalid dialers: empty array") 34 | 35 | // ErrClientInvalidAsyncHandler represents an error of invalid(nil) async handler. 36 | ErrClientInvalidAsyncHandler = errors.New("invalid async handler: should not be nil") 37 | ) 38 | 39 | // message error 40 | var ( 41 | // ErrInvalidRspMessage represents an error of invalid message CMD. 42 | ErrInvalidRspMessage = errors.New("invalid response message cmd") 43 | 44 | // ErrMethodNotFound represents an error of method not found. 45 | ErrMethodNotFound = errors.New("method not found") 46 | 47 | // ErrInvalidFlagBitIndex represents an error of invlaid flag bit index. 48 | ErrInvalidFlagBitIndex = errors.New("invalid index, should be 0-7") 49 | ) 50 | 51 | // context error 52 | var ( 53 | // ErrContextResponseToNotify represents an error that response to a notify message. 54 | ErrContextResponseToNotify = errors.New("should not response to a context with notify message") 55 | ) 56 | 57 | // stream errors 58 | var ( 59 | // ErrStreamClosedSend represents an error of stream closed send. 60 | ErrStreamClosedSend = errors.New("stream has closed send") 61 | ) 62 | 63 | // general errors 64 | var ( 65 | // ErrTimeout represents an error of timeout. 66 | ErrTimeout = errors.New("timeout") 67 | ) 68 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | go run github.com/lesismal/arpc/examples/bench/server 2 | go run github.com/lesismal/arpc/examples/bench/client 3 | 4 | go run github.com/lesismal/arpc/examples/broadcast/server 5 | go run github.com/lesismal/arpc/examples/broadcast/client 6 | 7 | go run github.com/lesismal/arpc/examples/graceful/server 8 | go run github.com/lesismal/arpc/examples/graceful/client 9 | 10 | go run github.com/lesismal/arpc/examples/micro/server 11 | go run github.com/lesismal/arpc/examples/micro/client 12 | 13 | go run github.com/lesismal/arpc/examples/middleware/coder/gzip/server 14 | go run github.com/lesismal/arpc/examples/middleware/coder/gzip/client 15 | 16 | go run github.com/lesismal/arpc/examples/middleware/coder/tracing/server 17 | go run github.com/lesismal/arpc/examples/middleware/coder/tracing/client 18 | 19 | go run github.com/lesismal/arpc/examples/middleware/router/server 20 | go run github.com/lesismal/arpc/examples/middleware/router/client 21 | 22 | go run github.com/lesismal/arpc/examples/notify/server 23 | go run github.com/lesismal/arpc/examples/notify/client 24 | 25 | go run github.com/lesismal/arpc/examples/pool/server 26 | go run github.com/lesismal/arpc/examples/pool/client 27 | 28 | go run github.com/lesismal/arpc/examples/protocols/kcp/server 29 | go run github.com/lesismal/arpc/examples/protocols/kcp/client 30 | 31 | go run github.com/lesismal/arpc/examples/protocols/quic/server 32 | go run github.com/lesismal/arpc/examples/protocols/quic/client 33 | 34 | go run github.com/lesismal/arpc/examples/protocols/tls/server 35 | go run github.com/lesismal/arpc/examples/protocols/tls/client 36 | 37 | go run github.com/lesismal/arpc/examples/protocols/unixsocket/server 38 | go run github.com/lesismal/arpc/examples/protocols/unixsocket/client 39 | 40 | go run github.com/lesismal/arpc/examples/protocols/utp/server 41 | go run github.com/lesismal/arpc/examples/protocols/utp/client 42 | 43 | go run github.com/lesismal/arpc/examples/protocols/websocket/server 44 | go run github.com/lesismal/arpc/examples/protocols/websocket/client 45 | 46 | go run github.com/lesismal/arpc/examples/pubsub/server 47 | go run github.com/lesismal/arpc/examples/pubsub/client 48 | 49 | go run github.com/lesismal/arpc/examples/rpc/server 50 | go run github.com/lesismal/arpc/examples/rpc/client 51 | 52 | go run github.com/lesismal/arpc/examples/webchat 53 | visit http://localhost:8888 -------------------------------------------------------------------------------- /examples/bench/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "log" 7 | "net" 8 | "runtime" 9 | "sync/atomic" 10 | "time" 11 | 12 | "github.com/lesismal/arpc" 13 | ) 14 | 15 | var ( 16 | addr = "localhost:8888" 17 | 18 | method = "Hello" 19 | ) 20 | 21 | // HelloReq . 22 | type HelloReq struct { 23 | Msg string 24 | } 25 | 26 | // HelloRsp . 27 | type HelloRsp struct { 28 | Msg string 29 | } 30 | 31 | func dialer() (net.Conn, error) { 32 | return net.DialTimeout("tcp", addr, time.Second*3) 33 | } 34 | 35 | func main() { 36 | var ( 37 | qpsSec uint64 38 | qpsTotal uint64 39 | clientNum = runtime.NumCPU() * 2 40 | eachClientCoroutineNum = 10 41 | ) 42 | 43 | arpc.EnablePool(true) 44 | 45 | clients := make([]*arpc.Client, clientNum) 46 | 47 | for i := 0; i < clientNum; i++ { 48 | client, err := arpc.NewClient(dialer) 49 | if err != nil { 50 | log.Println("NewClient failed:", err) 51 | return 52 | } 53 | clients[i] = client 54 | defer client.Stop() 55 | } 56 | 57 | for i := 0; i < clientNum; i++ { 58 | client := clients[i] 59 | for j := 0; j < eachClientCoroutineNum; j++ { 60 | go func() { 61 | var err error 62 | var data = make([]byte, 512) 63 | for k := 0; true; k++ { 64 | rand.Read(data) 65 | req := &HelloReq{Msg: base64.RawStdEncoding.EncodeToString(data)} 66 | rsp := &HelloRsp{} 67 | err = client.Call(method, req, rsp, time.Second*5) 68 | if err != nil { 69 | log.Printf("Call failed: %v", err) 70 | } else if rsp.Msg != req.Msg { 71 | log.Fatal("Call failed: not equal") 72 | } else { 73 | atomic.AddUint64(&qpsSec, 1) 74 | } 75 | } 76 | }() 77 | } 78 | } 79 | 80 | ticker := time.NewTicker(time.Second) 81 | for i := 0; true; i++ { 82 | if _, ok := <-ticker.C; !ok { 83 | return 84 | } 85 | if i < 3 { 86 | log.Printf("[qps preheating %v: %v]", i+1, atomic.SwapUint64(&qpsSec, 0)) 87 | continue 88 | } 89 | qps := atomic.SwapUint64(&qpsSec, 0) 90 | qpsTotal += qps 91 | log.Printf("[qps: %v], [avg: %v / s], [total: %v, %v s]", 92 | qps, int64(float64(qpsTotal)/float64(i-2)), qpsTotal, int64(float64(i-2))) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/bench/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/lesismal/arpc" 8 | ) 9 | 10 | const ( 11 | addr = "localhost:8888" 12 | ) 13 | 14 | // HelloReq . 15 | type HelloReq struct { 16 | Msg string 17 | } 18 | 19 | // HelloRsp . 20 | type HelloRsp struct { 21 | Msg string 22 | } 23 | 24 | // OnHello . 25 | func OnHello(ctx *arpc.Context) { 26 | req := &HelloReq{} 27 | ctx.Bind(req) 28 | ctx.Write(&HelloRsp{Msg: req.Msg}) 29 | } 30 | 31 | func main() { 32 | ln, err := net.Listen("tcp", addr) 33 | if err != nil { 34 | log.Fatalf("failed to listen: %v", err) 35 | } 36 | 37 | svr := arpc.NewServer() 38 | svr.Handler.EnablePool(true) 39 | svr.Handler.SetAsyncResponse(true) 40 | svr.Handler.Handle("Hello", OnHello) 41 | svr.Serve(ln) 42 | } 43 | -------------------------------------------------------------------------------- /examples/bench_nbio/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "log" 7 | "net" 8 | "runtime" 9 | "sync/atomic" 10 | "time" 11 | 12 | "github.com/lesismal/arpc" 13 | ) 14 | 15 | var ( 16 | addr = "localhost:8888" 17 | 18 | method = "Hello" 19 | ) 20 | 21 | // HelloReq . 22 | type HelloReq struct { 23 | Msg string 24 | } 25 | 26 | // HelloRsp . 27 | type HelloRsp struct { 28 | Msg string 29 | } 30 | 31 | func dialer() (net.Conn, error) { 32 | return net.DialTimeout("tcp", addr, time.Second*3) 33 | } 34 | 35 | func main() { 36 | var ( 37 | qpsSec uint64 38 | qpsTotal uint64 39 | clientNum = runtime.NumCPU() * 2 40 | eachClientCoroutineNum = 10 41 | ) 42 | 43 | arpc.EnablePool(true) 44 | 45 | clients := make([]*arpc.Client, clientNum) 46 | 47 | for i := 0; i < clientNum; i++ { 48 | client, err := arpc.NewClient(dialer) 49 | if err != nil { 50 | log.Println("NewClient failed:", err) 51 | return 52 | } 53 | clients[i] = client 54 | defer client.Stop() 55 | } 56 | 57 | for i := 0; i < clientNum; i++ { 58 | client := clients[i] 59 | for j := 0; j < eachClientCoroutineNum; j++ { 60 | go func() { 61 | var err error 62 | var data = make([]byte, 512) 63 | for k := 0; true; k++ { 64 | rand.Read(data) 65 | req := &HelloReq{Msg: base64.RawStdEncoding.EncodeToString(data)} 66 | rsp := &HelloRsp{} 67 | err = client.Call(method, req, rsp, time.Second*5) 68 | if err != nil { 69 | log.Printf("Call failed: %v", err) 70 | } else if rsp.Msg != req.Msg { 71 | log.Fatal("Call failed: not equal") 72 | } else { 73 | atomic.AddUint64(&qpsSec, 1) 74 | } 75 | } 76 | }() 77 | } 78 | } 79 | 80 | ticker := time.NewTicker(time.Second) 81 | for i := 0; true; i++ { 82 | if _, ok := <-ticker.C; !ok { 83 | return 84 | } 85 | if i < 3 { 86 | log.Printf("[qps preheating %v: %v]", i+1, atomic.SwapUint64(&qpsSec, 0)) 87 | continue 88 | } 89 | qps := atomic.SwapUint64(&qpsSec, 0) 90 | qpsTotal += qps 91 | log.Printf("[qps: %v], [avg: %v / s], [total: %v, %v s]", 92 | qps, int64(float64(qpsTotal)/float64(i-2)), qpsTotal, int64(float64(i-2))) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/bench_nbio/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "github.com/lesismal/arpc" 10 | "github.com/lesismal/arpc/codec" 11 | "github.com/lesismal/arpc/log" 12 | "github.com/lesismal/nbio" 13 | nlog "github.com/lesismal/nbio/logging" 14 | "github.com/lesismal/nbio/mempool" 15 | ) 16 | 17 | var ( 18 | addr = "localhost:8888" 19 | handler = arpc.NewHandler() 20 | ) 21 | 22 | // HelloReq . 23 | type HelloReq struct { 24 | Msg string 25 | } 26 | 27 | // HelloRsp . 28 | type HelloRsp struct { 29 | Msg string 30 | } 31 | 32 | // Session . 33 | type Session struct { 34 | *arpc.Client 35 | bytes.Buffer 36 | } 37 | 38 | func onOpen(c *nbio.Conn) { 39 | client := &arpc.Client{Conn: c, Codec: codec.DefaultCodec, Handler: handler} 40 | session := &Session{ 41 | Client: client, 42 | } 43 | c.SetSession(session) 44 | } 45 | 46 | func onData(c *nbio.Conn, data []byte) { 47 | iSession := c.Session() 48 | if iSession == nil { 49 | c.Close() 50 | return 51 | } 52 | session := iSession.(*Session) 53 | session.Write(data) 54 | for session.Len() >= arpc.HeadLen { 55 | headBuf := session.Bytes()[:4] 56 | header := arpc.Header(headBuf) 57 | total := arpc.HeadLen + header.BodyLen() 58 | if session.Len() < total { 59 | return 60 | } 61 | 62 | buffer := mempool.Malloc(total) 63 | session.Read(buffer) 64 | 65 | msg := handler.NewMessageWithBuffer(buffer) 66 | handler.OnMessage(session.Client, msg) 67 | } 68 | } 69 | 70 | func main() { 71 | nlog.SetLogger(log.DefaultLogger) 72 | 73 | arpc.DefaultAllocator = mempool.DefaultMemPool 74 | 75 | handler.EnablePool(true) 76 | handler.SetAsyncWrite(false) 77 | handler.SetAsyncResponse(true) 78 | 79 | // register router 80 | handler.Handle("Hello", func(ctx *arpc.Context) { 81 | req := &HelloReq{} 82 | ctx.Bind(req) 83 | ctx.Write(&HelloRsp{Msg: req.Msg}) 84 | }) 85 | 86 | g := nbio.NewGopher(nbio.Config{ 87 | Network: "tcp", 88 | Addrs: []string{addr}, 89 | }) 90 | 91 | g.OnOpen(onOpen) 92 | g.OnData(onData) 93 | 94 | err := g.Start() 95 | if err != nil { 96 | log.Error("Start failed: %v", err) 97 | } 98 | defer g.Stop() 99 | 100 | quit := make(chan os.Signal, 1) 101 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 102 | <-quit 103 | } 104 | -------------------------------------------------------------------------------- /examples/bench_pool/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "runtime" 8 | "sync/atomic" 9 | "time" 10 | 11 | "github.com/lesismal/arpc" 12 | ) 13 | 14 | var ( 15 | addr = "localhost:8888" 16 | 17 | method = "Hello" 18 | ) 19 | 20 | // HelloReq . 21 | type HelloReq struct { 22 | Msg string 23 | } 24 | 25 | // HelloRsp . 26 | type HelloRsp struct { 27 | Msg string 28 | } 29 | 30 | func dialer() (net.Conn, error) { 31 | return net.DialTimeout("tcp", addr, time.Second*3) 32 | } 33 | 34 | func main() { 35 | var ( 36 | qpsSec uint64 37 | asyncTimes uint64 38 | qpsTotal uint64 39 | failedTotal uint64 40 | clientNum = runtime.NumCPU() * 2 41 | eachClientCoroutineNum = 10 42 | ) 43 | 44 | arpc.EnablePool(true) 45 | 46 | clients := make([]*arpc.Client, clientNum) 47 | 48 | for i := 0; i < clientNum; i++ { 49 | client, err := arpc.NewClient(dialer) 50 | if err != nil { 51 | log.Println("NewClient failed:", err) 52 | return 53 | } 54 | clients[i] = client 55 | defer client.Stop() 56 | } 57 | 58 | for i := 0; i < clientNum; i++ { 59 | client := clients[i] 60 | for j := 0; j < eachClientCoroutineNum-1; j++ { 61 | go func() { 62 | var err error 63 | for k := 0; true; k++ { 64 | req := &HelloReq{Msg: fmt.Sprintf("[%v] %v", client.Conn.LocalAddr(), k)} 65 | rsp := &HelloRsp{} 66 | err = client.Call(method, req, rsp, time.Second*5) 67 | if err != nil || rsp.Msg != req.Msg { 68 | atomic.AddUint64(&failedTotal, 1) 69 | log.Printf("Call failed: %v", err) 70 | } else { 71 | //log.Printf("Call Response: \"%v\"", rsp.Msg) 72 | atomic.AddUint64(&qpsSec, 1) 73 | } 74 | } 75 | }() 76 | } 77 | go func() { 78 | ticker := time.NewTicker(time.Second) 79 | for i := 0; true; i++ { 80 | <-ticker.C 81 | req := &HelloReq{Msg: fmt.Sprintf("[%v] %v", client.Conn.LocalAddr(), i)} 82 | rsp := &HelloRsp{} 83 | err := client.CallAsync(method, req, func(ctx *arpc.Context, er error) { 84 | if er != nil { 85 | log.Printf("CallAsync failed: %v", er) 86 | atomic.AddUint64(&failedTotal, 1) 87 | return 88 | } 89 | er = ctx.Bind(rsp) 90 | if er != nil || rsp.Msg != req.Msg { 91 | log.Printf("CallAsync failed: %v", er) 92 | atomic.AddUint64(&failedTotal, 1) 93 | } else { 94 | //log.Printf("Call Response: \"%v\"", rsp.Msg) 95 | atomic.AddUint64(&qpsSec, 1) 96 | atomic.AddUint64(&asyncTimes, 1) 97 | } 98 | }, time.Second*5) 99 | if err != nil { 100 | log.Printf("CallAsync failed: %v", err) 101 | atomic.AddUint64(&failedTotal, 1) 102 | } else { 103 | //log.Printf("Call Response: \"%v\"", rsp.Msg) 104 | atomic.AddUint64(&qpsSec, 1) 105 | atomic.AddUint64(&asyncTimes, 1) 106 | } 107 | } 108 | }() 109 | } 110 | 111 | ticker := time.NewTicker(time.Second) 112 | for i := 0; true; i++ { 113 | if _, ok := <-ticker.C; !ok { 114 | return 115 | } 116 | if i < 3 { 117 | log.Printf("[qps preheating %v: %v]", i+1, atomic.SwapUint64(&qpsSec, 0)) 118 | continue 119 | } 120 | qps := atomic.SwapUint64(&qpsSec, 0) 121 | qpsTotal += qps 122 | log.Printf("[qps: %v], [asyncTimes: %v], [avg: %v / s], [total failed: %v, success: %v, %v s]", 123 | qps, atomic.LoadUint64(&asyncTimes), int64(float64(qpsTotal)/float64(i-2)), atomic.LoadUint64(&failedTotal), qpsTotal, int64(float64(i-2))) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /examples/bench_pool/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/lesismal/arpc" 8 | ) 9 | 10 | const ( 11 | addr = "localhost:8888" 12 | ) 13 | 14 | // HelloReq . 15 | type HelloReq struct { 16 | Msg string 17 | } 18 | 19 | // HelloRsp . 20 | type HelloRsp struct { 21 | Msg string 22 | } 23 | 24 | // OnHello . 25 | func OnHello(ctx *arpc.Context) { 26 | req := &HelloReq{} 27 | ctx.Bind(req) 28 | ctx.Write(&HelloRsp{Msg: req.Msg}) 29 | } 30 | 31 | func main() { 32 | arpc.EnablePool(true) 33 | 34 | ln, err := net.Listen("tcp", addr) 35 | if err != nil { 36 | log.Fatalf("failed to listen: %v", err) 37 | } 38 | 39 | svr := arpc.NewServer() 40 | 41 | svr.Handler.Handle("Hello", OnHello) 42 | svr.Serve(ln) 43 | } 44 | -------------------------------------------------------------------------------- /examples/broadcast/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | ) 10 | 11 | // OnBroadcast . 12 | func OnBroadcast(ctx *arpc.Context) { 13 | ret := "" 14 | ctx.Bind(&ret) 15 | log.Printf("OnBroadcast: \"%v\"", ret) 16 | } 17 | 18 | func dialer() (net.Conn, error) { 19 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 20 | } 21 | 22 | func main() { 23 | var clients []*arpc.Client 24 | 25 | arpc.DefaultHandler.Handle("/broadcast", OnBroadcast) 26 | 27 | for i := 0; i < 10; i++ { 28 | client, err := arpc.NewClient(dialer) 29 | if err != nil { 30 | log.Println("NewClient failed:", err) 31 | return 32 | } 33 | defer client.Stop() 34 | 35 | clients = append(clients, client) 36 | } 37 | 38 | for i := 0; i < 10; i++ { 39 | client := clients[i] 40 | go func() { 41 | passwd := "123qwe" 42 | response := "" 43 | err := client.Call("/enter", passwd, &response, time.Second*5) 44 | if err != nil { 45 | log.Printf("Call failed: %v", err) 46 | } else { 47 | log.Printf("Call Response: \"%v\"", response) 48 | } 49 | }() 50 | } 51 | 52 | <-make(chan int) 53 | } 54 | -------------------------------------------------------------------------------- /examples/broadcast/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sync" 7 | "time" 8 | 9 | "github.com/lesismal/arpc" 10 | ) 11 | 12 | var mux = sync.RWMutex{} 13 | var server = arpc.NewServer() 14 | var clientMap = make(map[*arpc.Client]struct{}) 15 | 16 | func main() { 17 | 18 | server.Handler.Handle("/enter", func(ctx *arpc.Context) { 19 | passwd := "" 20 | ctx.Bind(&passwd) 21 | if passwd == "123qwe" { 22 | // keep client 23 | mux.Lock() 24 | clientMap[ctx.Client] = struct{}{} 25 | mux.Unlock() 26 | 27 | ctx.Write(nil) 28 | 29 | log.Printf("enter success") 30 | } else { 31 | log.Printf("enter failed invalid passwd: %v", passwd) 32 | ctx.Client.Stop() 33 | } 34 | }) 35 | // release client 36 | server.Handler.HandleDisconnected(func(c *arpc.Client) { 37 | mux.Lock() 38 | delete(clientMap, c) 39 | mux.Unlock() 40 | }) 41 | 42 | go func() { 43 | ticker := time.NewTicker(time.Second) 44 | for i := 0; true; i++ { 45 | <-ticker.C 46 | switch i % 4 { 47 | case 0: 48 | server.Broadcast("/broadcast", fmt.Sprintf("Broadcast msg %d", i)) 49 | case 1: 50 | server.BroadcastWithFilter("/broadcast", fmt.Sprintf("BroadcastWithFilter msg %d", i), func(c *arpc.Client) bool { 51 | return true 52 | }) 53 | case 2: 54 | server.ForEach(func(c *arpc.Client) { 55 | c.Notify("/broadcast", fmt.Sprintf("ForEach msg %d", i), arpc.TimeZero) 56 | }) 57 | case 3: 58 | server.ForEachWithFilter(func(c *arpc.Client) { 59 | c.Notify("/broadcast", fmt.Sprintf("ForEachWithFilter msg %d", i), arpc.TimeZero) 60 | }, func(c *arpc.Client) bool { 61 | return true 62 | }) 63 | 64 | } 65 | } 66 | }() 67 | 68 | server.Run("localhost:8888") 69 | } 70 | -------------------------------------------------------------------------------- /examples/broadcast_pool/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | ) 10 | 11 | var notifyCount int32 12 | 13 | // OnBroadcast . 14 | func OnBroadcast(ctx *arpc.Context) { 15 | ret := "" 16 | ctx.Bind(&ret) 17 | log.Printf("OnServerNotify: \"%v\"", ret) 18 | // if atomic.AddInt32(¬ifyCount, 1) >= 20 { 19 | // os.Exit(0) 20 | // } 21 | } 22 | 23 | func dialer() (net.Conn, error) { 24 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 25 | } 26 | 27 | func main() { 28 | arpc.EnablePool(true) 29 | 30 | var clients []*arpc.Client 31 | 32 | arpc.DefaultHandler.Handle("/broadcast", OnBroadcast) 33 | 34 | for i := 0; i < 10; i++ { 35 | client, err := arpc.NewClient(dialer) 36 | if err != nil { 37 | log.Println("NewClient failed:", err) 38 | return 39 | } 40 | defer client.Stop() 41 | 42 | clients = append(clients, client) 43 | } 44 | 45 | for i := 0; i < 10; i++ { 46 | client := clients[i] 47 | go func() { 48 | 49 | passwd := "123qwe" 50 | response := "" 51 | err := client.Call("/enter", passwd, &response, time.Second*5) 52 | if err != nil { 53 | log.Printf("Call failed: %v", err) 54 | } else { 55 | log.Printf("Call Response: \"%v\"", response) 56 | } 57 | }() 58 | } 59 | 60 | <-make(chan int) 61 | } 62 | -------------------------------------------------------------------------------- /examples/broadcast_pool/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sync" 7 | "time" 8 | 9 | "github.com/lesismal/arpc" 10 | ) 11 | 12 | var mux = sync.RWMutex{} 13 | var server = arpc.NewServer() 14 | var clientMap = make(map[*arpc.Client]struct{}) 15 | 16 | func main() { 17 | server.Handler.EnablePool(true) 18 | 19 | server.Handler.Handle("/enter", func(ctx *arpc.Context) { 20 | passwd := "" 21 | ctx.Bind(&passwd) 22 | if passwd == "123qwe" { 23 | // keep client 24 | mux.Lock() 25 | clientMap[ctx.Client] = struct{}{} 26 | mux.Unlock() 27 | 28 | ctx.Write(nil) 29 | 30 | log.Printf("enter success") 31 | } else { 32 | log.Printf("enter failed invalid passwd: %v", passwd) 33 | ctx.Client.Stop() 34 | } 35 | }) 36 | // release client 37 | server.Handler.HandleDisconnected(func(c *arpc.Client) { 38 | mux.Lock() 39 | delete(clientMap, c) 40 | mux.Unlock() 41 | }) 42 | 43 | go func() { 44 | ticker := time.NewTicker(time.Second) 45 | for i := 0; true; i++ { 46 | <-ticker.C 47 | switch i % 4 { 48 | case 0: 49 | server.Broadcast("/broadcast", fmt.Sprintf("Broadcast msg %d", i)) 50 | case 1: 51 | server.BroadcastWithFilter("/broadcast", fmt.Sprintf("BroadcastWithFilter msg %d", i), func(c *arpc.Client) bool { 52 | return true 53 | }) 54 | case 2: 55 | server.ForEach(func(c *arpc.Client) { 56 | c.Notify("/broadcast", fmt.Sprintf("ForEach msg %d", i), arpc.TimeZero) 57 | }) 58 | case 3: 59 | server.ForEachWithFilter(func(c *arpc.Client) { 60 | c.Notify("/broadcast", fmt.Sprintf("ForEachWithFilter msg %d", i), arpc.TimeZero) 61 | }, func(c *arpc.Client) bool { 62 | return true 63 | }) 64 | 65 | } 66 | } 67 | }() 68 | 69 | server.Run("localhost:8888") 70 | } 71 | -------------------------------------------------------------------------------- /examples/graceful/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "sync" 7 | "time" 8 | 9 | "github.com/lesismal/arpc" 10 | ) 11 | 12 | func main() { 13 | client, err := arpc.NewClient(func() (net.Conn, error) { 14 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 15 | }) 16 | if err != nil { 17 | panic(err) 18 | } 19 | defer client.Stop() 20 | 21 | wg := sync.WaitGroup{} 22 | for i := 0; i < 9; i++ { 23 | wg.Add(1) 24 | go func() { 25 | defer wg.Done() 26 | req := "hello" 27 | rsp := "" 28 | err = client.Call("/echo", &req, &rsp, time.Second*10) 29 | if err != nil { 30 | log.Fatalf("Call /echo failed: %v", err) 31 | } else { 32 | log.Printf("Call /echo Response: \"%v\"", rsp) 33 | } 34 | }() 35 | } 36 | wg.Wait() 37 | 38 | req := "hello" 39 | rsp := "" 40 | err = client.Call("/echo", &req, &rsp, time.Second*10) 41 | if err != nil { 42 | log.Fatalf("Call /echo failed: %v", err) 43 | } else { 44 | log.Printf("Call /echo Response: \"%v\"", rsp) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/graceful/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "os/signal" 8 | 9 | // "sync" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/lesismal/arpc" 14 | "github.com/lesismal/arpc/extension/middleware/router" 15 | ) 16 | 17 | func main() { 18 | server := arpc.NewServer() 19 | 20 | graceful := &router.Graceful{} 21 | 22 | // step 1: register graceful middleware 23 | server.Handler.Use(graceful.Handler()) 24 | 25 | server.Handler.Handle("/echo", func(ctx *arpc.Context) { 26 | // delay 5s for you to shutdown server by `ctrl + c` 27 | time.Sleep(time.Second * 5) 28 | str := "" 29 | err := ctx.Bind(&str) 30 | ctx.Write(str) 31 | log.Printf("/echo: \"%v\", error: %v", str, err) 32 | }, true) 33 | 34 | go server.Run("localhost:8888") 35 | 36 | quit := make(chan os.Signal, 1) 37 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 38 | <-quit 39 | 40 | // step 2: shutdown by graceful middleware 41 | graceful.Shutdown() 42 | 43 | // step 3: shutdown arpc server 44 | server.Shutdown(context.Background()) 45 | } 46 | -------------------------------------------------------------------------------- /examples/httprpc/README.md: -------------------------------------------------------------------------------- 1 | ## Web Chat Example 2 | 3 | ```sh 4 | cd $GOPATH/src/github.com/lesismal/arpc/examples/httprpc 5 | go run server.go 6 | ``` 7 | - visit [http://localhost:8888](http://localhost:8888) 8 | -------------------------------------------------------------------------------- /examples/httprpc/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Arpc Chat Example 6 | 7 | 55 | 91 | 92 | 93 | 94 |
95 |
96 | 97 | 98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /examples/httprpc/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/lesismal/arpc" 8 | "github.com/lesismal/arpc/codec" 9 | "github.com/lesismal/arpc/extension/arpchttp" 10 | "github.com/lesismal/arpc/extension/protocol/websocket" 11 | "github.com/lesismal/arpc/log" 12 | ) 13 | 14 | func main() { 15 | svr := arpc.NewServer() 16 | wsHandler := svr.Handler 17 | wsHandler.Handle("/ws/echo", func(ctx *arpc.Context) { 18 | log.Info("/ws/echo: %v", string(ctx.Body())) 19 | ctx.Write(ctx.Body()) 20 | }) 21 | wsHandler.Handle("/ws/notify", func(ctx *arpc.Context) { 22 | log.Info("/ws/notify: %v", string(ctx.Body())) 23 | }) 24 | 25 | httpHandler := arpc.DefaultHandler 26 | httpHandler.SetAsyncWrite(false) 27 | httpHandler.Handle("/http/echo", func(ctx *arpc.Context) { 28 | log.Info("/http/echo: %v", string(ctx.Body())) 29 | ctx.Write(ctx.Body()) 30 | }) 31 | httpHandler.Handle("/http/notify", func(ctx *arpc.Context) { 32 | log.Info("/http/notify: %v", string(ctx.Body())) 33 | }) 34 | 35 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 36 | log.Info("url: %v", r.URL.String()) 37 | if r.URL.Path == "/" { 38 | http.ServeFile(w, r, "chat.html") 39 | } else if r.URL.Path == "/arpc.js" { 40 | http.ServeFile(w, r, "arpc.js") 41 | } else { 42 | http.NotFound(w, r) 43 | } 44 | }) 45 | 46 | ln, _ := websocket.Listen("localhost:8888", nil) 47 | go func() { 48 | err := http.ListenAndServe("localhost:8888", nil) 49 | if err != nil { 50 | log.Error("ListenAndServe: %v", err) 51 | } 52 | }() 53 | time.Sleep(time.Second / 100) 54 | 55 | http.HandleFunc("/ws/rpc", ln.(*websocket.Listener).Handler) 56 | http.HandleFunc("/http/rpc", arpchttp.Handler(httpHandler, codec.DefaultCodec)) 57 | 58 | svr.Serve(ln) 59 | } 60 | -------------------------------------------------------------------------------- /examples/mempool_fuzzy/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "flag" 7 | "log" 8 | "net" 9 | "sync/atomic" 10 | "time" 11 | 12 | "github.com/lesismal/arpc" 13 | ) 14 | 15 | var qps int64 16 | var qpsBroadcast int64 17 | var clientNum = flag.Int("pool", 10, "client number") 18 | var concurrency = flag.Int("c", 100, "concurrency number for each cleint") 19 | 20 | // OnBroadcast . 21 | func OnBroadcast(ctx *arpc.Context) { 22 | data := "" 23 | ctx.Bind(&data) 24 | if data != "broadcast msg" { 25 | log.Fatalf("dirty data: %v", data) 26 | } 27 | atomic.AddInt64(&qpsBroadcast, 1) 28 | } 29 | 30 | func dialer() (net.Conn, error) { 31 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 32 | } 33 | 34 | func main() { 35 | flag.Parse() 36 | 37 | arpc.EnablePool(true) 38 | 39 | var clients []*arpc.Client 40 | 41 | arpc.DefaultHandler.Handle("/broadcast", OnBroadcast) 42 | 43 | for i := 0; i < *clientNum; i++ { 44 | client, err := arpc.NewClient(dialer) 45 | if err != nil { 46 | log.Println("NewClient failed:", err) 47 | return 48 | } 49 | defer client.Stop() 50 | 51 | clients = append(clients, client) 52 | } 53 | 54 | for i := 0; i < *clientNum; i++ { 55 | client := clients[i] 56 | for j := 0; j < *concurrency; j++ { 57 | go func() { 58 | var ( 59 | req = make([]byte, 1024) 60 | res []byte 61 | ) 62 | for { 63 | rand.Read(req) 64 | err := client.Call("/echo", req, &res, time.Second*5) 65 | if err != nil { 66 | log.Fatalf("Call failed: %v", err) 67 | } 68 | if !bytes.Equal(req, res) { 69 | log.Fatalf("invalid response") 70 | } 71 | atomic.AddInt64(&qps, 1) 72 | } 73 | }() 74 | } 75 | } 76 | 77 | var total int64 = 0 78 | var totalBroadcast int64 = 0 79 | for { 80 | time.Sleep(time.Second) 81 | tmpQps := atomic.SwapInt64(&qps, 0) 82 | total += tmpQps 83 | tmpBroadcast := atomic.SwapInt64(&qpsBroadcast, 0) 84 | totalBroadcast += tmpBroadcast 85 | 86 | log.Printf("qps: %v, total: %v, broadcast: %v, broadcastTotal: %v", tmpQps, total, tmpBroadcast, totalBroadcast) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/mempool_fuzzy/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | ) 10 | 11 | var mux = sync.RWMutex{} 12 | var server = arpc.NewServer() 13 | var clientMap = make(map[*arpc.Client]struct{}) 14 | 15 | func main() { 16 | server.Handler.EnablePool(true) 17 | 18 | server.Handler.Handle("/echo", func(ctx *arpc.Context) { 19 | var data []byte 20 | err := ctx.Bind(&data) 21 | if err != nil { 22 | log.Fatalf("invalid request: %v", err) 23 | } 24 | ctx.Write(data) 25 | }) 26 | 27 | // add client 28 | server.Handler.HandleConnected(func(c *arpc.Client) { 29 | mux.Lock() 30 | clientMap[c] = struct{}{} 31 | mux.Unlock() 32 | }) 33 | // remove client 34 | server.Handler.HandleDisconnected(func(c *arpc.Client) { 35 | mux.Lock() 36 | delete(clientMap, c) 37 | mux.Unlock() 38 | }) 39 | 40 | go func() { 41 | ticker := time.NewTicker(time.Second / 100) 42 | for i := 0; true; i++ { 43 | <-ticker.C 44 | broadcast() 45 | } 46 | }() 47 | 48 | server.Run("localhost:8888") 49 | } 50 | 51 | func broadcast() { 52 | msg := server.NewMessage(arpc.CmdNotify, "/broadcast", "broadcast msg") 53 | mux.RLock() 54 | defer func() { 55 | mux.RUnlock() 56 | msg.Release() 57 | }() 58 | 59 | for client := range clientMap { 60 | msg.Retain() 61 | client.PushMsg(msg, arpc.TimeZero) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/micro/etcd/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/lesismal/arpc/extension/micro" 8 | "github.com/lesismal/arpc/extension/micro/etcd" 9 | "github.com/lesismal/arpc/log" 10 | ) 11 | 12 | func dialer(addr string) (net.Conn, error) { 13 | return net.DialTimeout("tcp", addr, time.Second*3) 14 | } 15 | 16 | func main() { 17 | var ( 18 | appPrefix = "app" 19 | service = "echo" 20 | 21 | endpoints = []string{"localhost:2379", "localhost:22379", "localhost:32379"} 22 | 23 | serviceManager = micro.NewServiceManager(dialer) 24 | ) 25 | discovery, err := etcd.NewDiscovery(endpoints, appPrefix, serviceManager) 26 | if err != nil { 27 | log.Error("NewDiscovery failed: %v", err) 28 | panic(err) 29 | } 30 | defer discovery.Stop() 31 | 32 | for { 33 | client, err := serviceManager.ClientBy(service) 34 | if err != nil { 35 | log.Error("get Client failed: %v", err) 36 | } else { 37 | req := "hello" 38 | rsp := "" 39 | err = client.Call("/echo", &req, &rsp, time.Second*5) 40 | if err != nil { 41 | log.Info("Call /echo failed: %v", err) 42 | } else { 43 | log.Info("Call /echo Response: \"%v\"", rsp) 44 | } 45 | } 46 | time.Sleep(time.Second) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/micro/etcd/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | "time" 9 | 10 | "github.com/lesismal/arpc" 11 | "github.com/lesismal/arpc/extension/micro/etcd" 12 | "github.com/lesismal/arpc/log" 13 | ) 14 | 15 | func main() { 16 | var ( 17 | appPrefix = "app" 18 | service = "echo" 19 | addr = "localhost:8888" 20 | weight = 2 21 | ttl int64 = 10 22 | 23 | endpoints = []string{"localhost:2379", "localhost:22379", "localhost:32379"} 24 | ) 25 | 26 | svr := arpc.NewServer() 27 | // register router 28 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 29 | str := "" 30 | err := ctx.Bind(&str) 31 | ret := fmt.Sprintf("%v_from_%v", str, addr) 32 | ctx.Write(ret) 33 | log.Info("/echo: \"%v\", error: %v", ret, err) 34 | }) 35 | go func() { 36 | err := svr.Run(addr) 37 | log.Error("server exit: %v", err) 38 | os.Exit(0) 39 | }() 40 | 41 | time.Sleep(time.Second / 2) 42 | key := fmt.Sprintf("%v/%v/%v/%v", appPrefix, service, addr, time.Now().UnixNano()) 43 | value := fmt.Sprintf("%v", weight) 44 | register, err := etcd.NewRegister(endpoints, key, value, ttl) 45 | if err != nil { 46 | log.Error("NewRegister failed: %v", err) 47 | panic(err) 48 | } 49 | defer register.Stop() 50 | 51 | quit := make(chan os.Signal, 1) 52 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 53 | <-quit 54 | svr.Stop() 55 | } 56 | -------------------------------------------------------------------------------- /examples/micro/etcd/tools/etcd1.bat: -------------------------------------------------------------------------------- 1 | etcd --name="infra1" --listen-client-urls="http://127.0.0.1:2379" --advertise-client-urls="http://127.0.0.1:2379" --listen-peer-urls="http://127.0.0.1:12380" --initial-advertise-peer-urls="http://127.0.0.1:12380" --initial-cluster-token="etcd-cluster-1" --initial-cluster="infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380" --initial-cluster-state="new" 2 | 3 | rem etcd --name="infra2" --listen-client-urls="http://127.0.0.1:22379" --advertise-client-urls="http://127.0.0.1:22379" --listen-peer-urls="http://127.0.0.1:22380" --initial-advertise-peer-urls="http://127.0.0.1:22380" --initial-cluster-token="etcd-cluster-1" --initial-cluster="infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380" --initial-cluster-state="new" 4 | 5 | rem etcd --name="infra3" --listen-client-urls="http://127.0.0.1:32379" --advertise-client-urls="http://127.0.0.1:32379" --listen-peer-urls="http://127.0.0.1:32380" --initial-advertise-peer-urls="http://127.0.0.1:32380" --initial-cluster-token="etcd-cluster-1" --initial-cluster="infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380" --initial-cluster-state="new" -------------------------------------------------------------------------------- /examples/micro/etcd/tools/etcd2.bat: -------------------------------------------------------------------------------- 1 | rem etcd --name="infra1" --listen-client-urls="http://127.0.0.1:2379" --advertise-client-urls="http://127.0.0.1:2379" --listen-peer-urls="http://127.0.0.1:12380" --initial-advertise-peer-urls="http://127.0.0.1:12380" --initial-cluster-token="etcd-cluster-1" --initial-cluster="infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380" --initial-cluster-state="new" 2 | 3 | etcd --name="infra2" --listen-client-urls="http://127.0.0.1:22379" --advertise-client-urls="http://127.0.0.1:22379" --listen-peer-urls="http://127.0.0.1:22380" --initial-advertise-peer-urls="http://127.0.0.1:22380" --initial-cluster-token="etcd-cluster-1" --initial-cluster="infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380" --initial-cluster-state="new" 4 | 5 | rem etcd --name="infra3" --listen-client-urls="http://127.0.0.1:32379" --advertise-client-urls="http://127.0.0.1:32379" --listen-peer-urls="http://127.0.0.1:32380" --initial-advertise-peer-urls="http://127.0.0.1:32380" --initial-cluster-token="etcd-cluster-1" --initial-cluster="infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380" --initial-cluster-state="new" -------------------------------------------------------------------------------- /examples/micro/etcd/tools/etcd3.bat: -------------------------------------------------------------------------------- 1 | rem etcd --name="infra1" --listen-client-urls="http://127.0.0.1:2379" --advertise-client-urls="http://127.0.0.1:2379" --listen-peer-urls="http://127.0.0.1:12380" --initial-advertise-peer-urls="http://127.0.0.1:12380" --initial-cluster-token="etcd-cluster-1" --initial-cluster="infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380" --initial-cluster-state="new" 2 | 3 | rem etcd --name="infra2" --listen-client-urls="http://127.0.0.1:22379" --advertise-client-urls="http://127.0.0.1:22379" --listen-peer-urls="http://127.0.0.1:22380" --initial-advertise-peer-urls="http://127.0.0.1:22380" --initial-cluster-token="etcd-cluster-1" --initial-cluster="infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380" --initial-cluster-state="new" 4 | 5 | etcd --name="infra3" --listen-client-urls="http://127.0.0.1:32379" --advertise-client-urls="http://127.0.0.1:32379" --listen-peer-urls="http://127.0.0.1:32380" --initial-advertise-peer-urls="http://127.0.0.1:32380" --initial-cluster-token="etcd-cluster-1" --initial-cluster="infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380" --initial-cluster-state="new" -------------------------------------------------------------------------------- /examples/micro/etcd/tools/etcdcluster.bat: -------------------------------------------------------------------------------- 1 | start cmd /c etcd1 2 | start cmd /c etcd2 3 | start cmd /c etcd3 -------------------------------------------------------------------------------- /examples/micro/redis/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/lesismal/arpc/extension/micro/redis" 8 | 9 | "github.com/lesismal/arpc/extension/micro" 10 | "github.com/lesismal/arpc/log" 11 | ) 12 | 13 | func dialer(addr string) (net.Conn, error) { 14 | return net.DialTimeout("tcp", addr, time.Second*3) 15 | } 16 | 17 | func main() { 18 | var ( 19 | appPrefix = "app" 20 | service = "echo" 21 | 22 | serviceManager = micro.NewServiceManager(dialer) 23 | ) 24 | discovery, err := redis.NewDiscovery("localhost:6379", appPrefix, time.Second*5, serviceManager) 25 | if err != nil { 26 | log.Error("NewDiscovery failed: %v", err) 27 | panic(err) 28 | } 29 | defer discovery.Stop() 30 | 31 | for { 32 | client, err := serviceManager.ClientBy(service) 33 | if err != nil { 34 | log.Error("get Client failed: %v", err) 35 | } else { 36 | req := "hello" 37 | rsp := "" 38 | err = client.Call("/echo", &req, &rsp, time.Second*5) 39 | if err != nil { 40 | log.Info("Call /echo failed: %v", err) 41 | } else { 42 | log.Info("Call /echo Response: \"%v\"", rsp) 43 | } 44 | } 45 | time.Sleep(time.Second) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/micro/redis/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | "time" 9 | 10 | "github.com/lesismal/arpc/extension/micro/redis" 11 | 12 | "github.com/lesismal/arpc" 13 | "github.com/lesismal/arpc/log" 14 | ) 15 | 16 | func main() { 17 | var ( 18 | namespace = "app" 19 | service = "echo" 20 | addr = "localhost:8888" 21 | weight = 3 22 | ) 23 | 24 | svr := arpc.NewServer() 25 | // register router 26 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 27 | str := "" 28 | err := ctx.Bind(&str) 29 | ret := fmt.Sprintf("%v_from_%v", str, addr) 30 | ctx.Write(ret) 31 | log.Info("/echo: \"%v\", error: %v", ret, err) 32 | }) 33 | go func() { 34 | err := svr.Run(addr) 35 | log.Error("server exit: %v", err) 36 | os.Exit(0) 37 | }() 38 | 39 | time.Sleep(time.Second / 2) 40 | register, err := redis.NewRegister("localhost:6379", namespace, service, addr, weight, time.Second*5, time.Second*8) 41 | if err != nil { 42 | log.Error("NewRegister failed: %v", err) 43 | panic(err) 44 | } 45 | defer register.Stop() 46 | 47 | quit := make(chan os.Signal, 1) 48 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 49 | <-quit 50 | svr.Stop() 51 | } 52 | -------------------------------------------------------------------------------- /examples/middleware/coder/gzip/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | "github.com/lesismal/arpc/extension/middleware/coder/gzip" 10 | ) 11 | 12 | func main() { 13 | client, err := arpc.NewClient(func() (net.Conn, error) { 14 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 15 | }) 16 | if err != nil { 17 | panic(err) 18 | } 19 | defer client.Stop() 20 | 21 | client.Handler.UseCoder(gzip.New(1024)) 22 | 23 | req := "" 24 | for i := 0; i < 2048; i++ { 25 | req += "a" 26 | } 27 | rsp := "" 28 | err = client.Call("/echo", &req, &rsp, time.Second*5) 29 | if err != nil { 30 | log.Fatalf("Call /echo failed: %v", err) 31 | } else { 32 | log.Printf("Call /echo Response: \"%v\"", rsp) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/middleware/coder/gzip/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/lesismal/arpc" 5 | "github.com/lesismal/arpc/extension/middleware/coder/gzip" 6 | "github.com/lesismal/arpc/log" 7 | ) 8 | 9 | func main() { 10 | svr := arpc.NewServer() 11 | 12 | svr.Handler.UseCoder(gzip.New(1024)) 13 | 14 | // register router 15 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 16 | ctx.Write(ctx.Body()) 17 | log.Info("/echo") 18 | }) 19 | 20 | svr.Run("localhost:8888") 21 | } 22 | -------------------------------------------------------------------------------- /examples/middleware/coder/msgpack/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/lesismal/arpc" 10 | "github.com/lesismal/arpc/extension/middleware/coder/msgpack" 11 | ) 12 | 13 | func main() { 14 | client, err := arpc.NewClient(func() (net.Conn, error) { 15 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 16 | }) 17 | if err != nil { 18 | panic(err) 19 | } 20 | defer client.Stop() 21 | 22 | client.Handler.UseCoder(msgpack.New()) 23 | 24 | for i := 0; i < 5; i++ { 25 | req := fmt.Sprintf("hello %v", i) 26 | rsp := "" 27 | midlewareValues := map[interface{}]interface{}{} 28 | k, v := fmt.Sprintf("key-%v", i), fmt.Sprintf("value-%v", i) 29 | midlewareValues[k] = v 30 | err = client.Call("/echo", &req, &rsp, time.Second*5, midlewareValues) 31 | if err != nil { 32 | log.Fatalf("Call /echo failed: %v", err) 33 | } else { 34 | log.Printf("Call /echo Response: \"%v\"", rsp) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/middleware/coder/msgpack/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/lesismal/arpc" 5 | "github.com/lesismal/arpc/extension/middleware/coder/msgpack" 6 | "github.com/lesismal/arpc/log" 7 | ) 8 | 9 | func main() { 10 | svr := arpc.NewServer() 11 | 12 | svr.Handler.UseCoder(msgpack.New()) 13 | 14 | // register router 15 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 16 | ctx.Write(ctx.Body()) 17 | log.Info("/echo, %v", ctx.Values()) 18 | }) 19 | 20 | svr.Run("localhost:8888") 21 | } 22 | -------------------------------------------------------------------------------- /examples/middleware/coder/tracing/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "net" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | "github.com/lesismal/arpc" 13 | "github.com/lesismal/arpc/extension/middleware/coder/tracing" 14 | opentracing "github.com/opentracing/opentracing-go" 15 | "github.com/opentracing/opentracing-go/log" 16 | ) 17 | 18 | func main() { 19 | client, err := arpc.NewClient(func() (net.Conn, error) { 20 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 21 | }) 22 | if err != nil { 23 | panic(err) 24 | } 25 | defer client.Stop() 26 | 27 | tracer := tracing.NewTracer(nil) 28 | opentracing.InitGlobalTracer(tracer) 29 | 30 | client.Handler.UseCoder(tracer) 31 | 32 | reader := bufio.NewReader(os.Stdin) 33 | for { 34 | span := opentracing.StartSpan("getInput") 35 | span.SetTag("component", "client") 36 | ctx := opentracing.ContextWithSpan(context.Background(), span) 37 | // Make sure that global baggage propagation works. 38 | span.SetBaggageItem("user", "echo") 39 | span.LogFields(log.Object("ctx", ctx)) 40 | fmt.Print("\n\nEnter text (empty string to exit): ") 41 | text, _ := reader.ReadString('\n') 42 | text = strings.TrimSpace(text) 43 | if len(text) == 0 { 44 | fmt.Println("Exiting.") 45 | os.Exit(0) 46 | } 47 | 48 | span.LogFields(log.String("user text", text)) 49 | 50 | req := text 51 | rsp := "" 52 | err = client.Call("/echo", &req, &rsp, time.Second*5, tracing.Values(span.Context())) 53 | if err != nil { 54 | span.LogFields(log.Error(err)) 55 | } else { 56 | span.LogFields(log.String("rsp", rsp)) 57 | } 58 | 59 | span.Finish() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/middleware/coder/tracing/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | golog "log" 7 | "net/http" 8 | 9 | "github.com/lesismal/arpc" 10 | "github.com/lesismal/arpc/extension/middleware/coder/tracing" 11 | "github.com/opentracing/opentracing-go" 12 | "github.com/opentracing/opentracing-go/ext" 13 | "github.com/opentracing/opentracing-go/log" 14 | ) 15 | 16 | func httpServer() { 17 | http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 18 | textCarrier := opentracing.HTTPHeadersCarrier(req.Header) 19 | wireSpanContext, err := opentracing.GlobalTracer().Extract( 20 | opentracing.TextMap, textCarrier) 21 | if err != nil { 22 | panic(err) 23 | } 24 | serverSpan := opentracing.GlobalTracer().StartSpan( 25 | "http-server-span", 26 | ext.RPCServerOption(wireSpanContext)) 27 | serverSpan.SetTag("component", "http-server") 28 | defer serverSpan.Finish() 29 | 30 | fullBody, err := ioutil.ReadAll(req.Body) 31 | if err != nil { 32 | serverSpan.LogFields(log.Error(err)) 33 | } 34 | serverSpan.LogFields(log.String("request body", string(fullBody))) 35 | }) 36 | 37 | golog.Fatal(http.ListenAndServe("localhost:8889", nil)) 38 | } 39 | 40 | func main() { 41 | tracer := tracing.NewTracer(nil) 42 | opentracing.InitGlobalTracer(tracer) 43 | 44 | go httpServer() 45 | 46 | svr := arpc.NewServer() 47 | svr.Handler.UseCoder(tracer) 48 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 49 | wireSpanContext, err := opentracing.GlobalTracer().Extract(nil, ctx) 50 | if err != nil { 51 | panic(err) 52 | } 53 | serverSpan := opentracing.GlobalTracer().StartSpan( 54 | "arpc-server-span", 55 | ext.RPCServerOption(wireSpanContext)) 56 | serverSpan.SetTag("component", "arpc-server") 57 | defer serverSpan.Finish() 58 | 59 | payload := "" 60 | err = ctx.Bind(&payload) 61 | if err != nil { 62 | serverSpan.LogFields(log.Error(err)) 63 | } 64 | serverSpan.LogFields(log.String("payload", payload)) 65 | ctx.Write(payload) 66 | 67 | httpClient := &http.Client{} 68 | httpReq, _ := http.NewRequest("POST", "http://localhost:8889/", bytes.NewReader([]byte(payload))) 69 | textCarrier := opentracing.HTTPHeadersCarrier(httpReq.Header) 70 | err = serverSpan.Tracer().Inject(serverSpan.Context(), opentracing.TextMap, textCarrier) 71 | if err != nil { 72 | panic(err) 73 | } 74 | resp, err := httpClient.Do(httpReq) 75 | if err != nil { 76 | serverSpan.LogFields(log.Error(err)) 77 | } else { 78 | serverSpan.LogFields(log.Object("response", resp)) 79 | } 80 | }) 81 | 82 | svr.Run("localhost:8888") 83 | } 84 | -------------------------------------------------------------------------------- /examples/middleware/router/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | ) 10 | 11 | func main() { 12 | client, err := arpc.NewClient(func() (net.Conn, error) { 13 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 14 | }) 15 | if err != nil { 16 | panic(err) 17 | } 18 | defer client.Stop() 19 | 20 | req := "hello" 21 | rsp := "" 22 | err = client.Call("/panic", &req, &rsp, time.Second*5) 23 | if err != nil { 24 | log.Fatalf("Call /panic failed: %v", err) 25 | } else { 26 | log.Printf("Call /panic Response: \"%v\"", rsp) 27 | } 28 | 29 | err = client.Call("/logger", &req, &rsp, time.Second*5) 30 | if err != nil { 31 | log.Fatalf("/logger: %v", err) 32 | } else { 33 | log.Printf("Call /logger Response: \"%v\"", rsp) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/middleware/router/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lesismal/arpc" 7 | "github.com/lesismal/arpc/extension/middleware/router" 8 | "github.com/lesismal/arpc/log" 9 | ) 10 | 11 | func main() { 12 | svr := arpc.NewServer() 13 | 14 | svr.Handler.Use(router.Recover()) 15 | svr.Handler.Use(router.Logger()) 16 | 17 | // register router 18 | svr.Handler.Handle("/panic", func(ctx *arpc.Context) { 19 | ctx.Write(ctx.Body()) 20 | log.Info("/panic handler") 21 | panic(string(ctx.Body())) 22 | }) 23 | 24 | // register router 25 | svr.Handler.Handle("/logger", func(ctx *arpc.Context) { 26 | ctx.Write(ctx.Body()) 27 | log.Info("/logger handler") 28 | time.Sleep(time.Millisecond) 29 | }) 30 | 31 | svr.Run("localhost:8888") 32 | } 33 | -------------------------------------------------------------------------------- /examples/nbio/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/lesismal/arpc" 8 | "github.com/lesismal/arpc/log" 9 | ) 10 | 11 | func main() { 12 | clientNum := 10 13 | pool, err := arpc.NewClientPool(func() (net.Conn, error) { 14 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 15 | }, clientNum) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | for i := 0; i < clientNum; i++ { 21 | req := "hello" 22 | rsp := "" 23 | err = pool.Next().Call("/echo", &req, &rsp, time.Second*5) 24 | if err != nil { 25 | log.Error("Call /echo failed: %v", err) 26 | return 27 | } 28 | log.Info("Call /echo Response: \"%v\"", rsp) 29 | } 30 | 31 | pool.Stop() 32 | 33 | time.Sleep(time.Second / 10) 34 | } 35 | -------------------------------------------------------------------------------- /examples/nbio/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | 11 | "github.com/lesismal/arpc" 12 | "github.com/lesismal/arpc/codec" 13 | "github.com/lesismal/arpc/extension/listener" 14 | "github.com/lesismal/arpc/log" 15 | "github.com/lesismal/nbio" 16 | nlog "github.com/lesismal/nbio/logging" 17 | "github.com/lesismal/nbio/mempool" 18 | "github.com/lesismal/nbio/taskpool" 19 | ) 20 | 21 | var ( 22 | addr = "localhost:8888" 23 | 24 | multiListener *listener.Listener 25 | 26 | nbioHandler = arpc.NewHandler() 27 | 28 | stdSvr = arpc.NewServer() 29 | nbioSvr = nbio.NewGopher(nbio.Config{}) 30 | 31 | pool = taskpool.New(1024*8, 1024) 32 | 33 | method = "/echo" 34 | ) 35 | 36 | func onEcho(ctx *arpc.Context) { 37 | str := "" 38 | err := ctx.Bind(&str) 39 | if err != nil { 40 | ctx.Error("invalid message") 41 | log.Error("Bind failed: %v", err) 42 | return 43 | } 44 | ctx.Write(str) 45 | } 46 | 47 | func main() { 48 | nlog.SetLogger(log.DefaultLogger) 49 | 50 | arpc.DefaultAllocator = mempool.DefaultMemPool 51 | 52 | var err error 53 | var maxStdOnline = 5 54 | multiListener, err = listener.Listen("tcp", addr, maxStdOnline, "") 55 | if err != nil { 56 | panic(err) 57 | } 58 | lnA, lnB := multiListener.Listeners() 59 | initStdServer(lnA) 60 | initNBIOServer(lnB) 61 | go multiListener.Run() 62 | 63 | quit := make(chan os.Signal, 1) 64 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 65 | <-quit 66 | 67 | multiListener.Close() 68 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) 69 | defer cancel() 70 | nbioSvr.Stop() 71 | stdSvr.Shutdown(ctx) 72 | } 73 | 74 | func initStdServer(ln net.Listener) { 75 | stdSvr.Handler.EnablePool(true) 76 | stdSvr.Handler.SetAsyncResponse(true) 77 | stdSvr.Handler.HandleDisconnected(func(*arpc.Client) { 78 | multiListener.OfflineA() 79 | }) 80 | 81 | // register router 82 | stdSvr.Handler.Handle(method, onEcho) 83 | 84 | go func() { 85 | err := stdSvr.Serve(ln) 86 | log.Error("stdSvr serve: %v", err) 87 | }() 88 | } 89 | 90 | func initNBIOServer(ln net.Listener) { 91 | nbioHandler.EnablePool(true) 92 | nbioHandler.SetAsyncWrite(false) 93 | 94 | // register router 95 | nbioHandler.Handle(method, onEcho) 96 | 97 | nbioSvr.OnOpen(nbioOnOpen) 98 | nbioSvr.OnData(nbioOnData) 99 | nbioSvr.OnClose(nbioOnClose) 100 | nbioSvr.Execute = pool.Go 101 | 102 | err := nbioSvr.Start() 103 | if err != nil { 104 | log.Error("Start failed: %v", err) 105 | panic(err) 106 | } 107 | 108 | go func() { 109 | n := 0 110 | for { 111 | c, err := ln.Accept() 112 | if err != nil { 113 | return 114 | } 115 | n++ 116 | println("nbio.Gopher total Accepted:", n) 117 | nbioSvr.AddConn(c) 118 | } 119 | }() 120 | } 121 | 122 | // Session . 123 | type Session struct { 124 | *arpc.Client 125 | cache []byte 126 | } 127 | 128 | func nbioOnOpen(c *nbio.Conn) { 129 | client := &arpc.Client{Conn: c, Codec: codec.DefaultCodec, Handler: nbioHandler} 130 | session := &Session{ 131 | Client: client, 132 | } 133 | c.SetSession(session) 134 | } 135 | 136 | func nbioOnClose(c *nbio.Conn, err error) { 137 | iSession := c.Session() 138 | if iSession == nil { 139 | c.Close() 140 | return 141 | } 142 | c.MustExecute(func() { 143 | session := iSession.(*Session) 144 | if session.cache != nil { 145 | mempool.Free(session.cache) 146 | session.cache = nil 147 | } 148 | }) 149 | } 150 | 151 | func nbioOnData(c *nbio.Conn, data []byte) { 152 | iSession := c.Session() 153 | if iSession == nil { 154 | c.Close() 155 | return 156 | } 157 | 158 | c.Execute(func() { 159 | session := iSession.(*Session) 160 | start := 0 161 | if session.cache != nil { 162 | session.cache = append(session.cache, data...) 163 | data = session.cache 164 | } 165 | for { 166 | if len(data) < arpc.HeadLen { 167 | goto Exit 168 | } 169 | header := arpc.Header(data[start : start+4]) 170 | total := arpc.HeadLen + header.BodyLen() 171 | if len(data)-start < total { 172 | goto Exit 173 | } 174 | 175 | buffer := mempool.Malloc(total) 176 | copy(buffer, data[start:start+total]) 177 | start += total 178 | msg := nbioHandler.NewMessageWithBuffer(buffer) 179 | pool.Go(func() { 180 | nbioHandler.OnMessage(session.Client, msg) 181 | }) 182 | } 183 | 184 | Exit: 185 | if session.cache != nil { 186 | if start == len(data) { 187 | mempool.Free(data) 188 | session.cache = nil 189 | } else { 190 | left := mempool.Malloc(len(data) - start) 191 | copy(left, data[start:]) 192 | mempool.Free(data) 193 | session.cache = left 194 | } 195 | } else if start < len(data) { 196 | left := mempool.Malloc(len(data) - start) 197 | copy(left, data[start:]) 198 | mempool.Free(data) 199 | session.cache = left 200 | } 201 | }) 202 | } 203 | -------------------------------------------------------------------------------- /examples/notify/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "os" 7 | "time" 8 | 9 | "github.com/lesismal/arpc" 10 | ) 11 | 12 | const ( 13 | addr = "localhost:8888" 14 | 15 | methodHello = "Hello" 16 | methodNotify = "Notify" 17 | ) 18 | 19 | var notifyCount = 3 20 | 21 | // OnServerNotify . 22 | func OnServerNotify(ctx *arpc.Context) { 23 | ret := "" 24 | ctx.Bind(&ret) 25 | log.Printf("OnServerNotify: \"%v\"", ret) 26 | notifyCount-- 27 | if notifyCount == 0 { 28 | os.Exit(0) 29 | } 30 | } 31 | 32 | func dialer() (net.Conn, error) { 33 | return net.DialTimeout("tcp", addr, time.Second*3) 34 | } 35 | 36 | func main() { 37 | client, err := arpc.NewClient(dialer) 38 | if err != nil { 39 | log.Println("NewClient failed:", err) 40 | return 41 | } 42 | 43 | // register handler for method 44 | client.Handler.Handle(methodNotify, OnServerNotify) 45 | defer client.Stop() 46 | 47 | payload := "hello from client.Call" 48 | response := "" 49 | err = client.Call(methodHello, payload, &response, time.Second*5) 50 | if err != nil { 51 | log.Printf("Call failed: %v", err) 52 | } else { 53 | log.Printf("Call Response: \"%v\"", response) 54 | } 55 | 56 | <-make(chan int) 57 | } 58 | -------------------------------------------------------------------------------- /examples/notify/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | ) 10 | 11 | const ( 12 | addr = "localhost:8888" 13 | 14 | methodHello = "Hello" 15 | methodNotify = "Notify" 16 | ) 17 | 18 | // OnClientHello . 19 | func OnClientHello(ctx *arpc.Context) { 20 | str := "" 21 | ctx.Bind(&str) 22 | ctx.Write(str) 23 | 24 | log.Printf("OnClientHello: \"%v\"", str) 25 | 26 | client := ctx.Client 27 | // send 3 notify messages 28 | go func() { 29 | notifyPayload := "notify from server, nonblock" 30 | client.Notify(methodNotify, notifyPayload, arpc.TimeZero) 31 | 32 | notifyPayload = "notify from server, block" 33 | client.Notify(methodNotify, notifyPayload, arpc.TimeForever) 34 | 35 | notifyPayload = "notify from server, with 1 second timeout" 36 | client.Notify(methodNotify, notifyPayload, time.Second) 37 | }() 38 | } 39 | 40 | func main() { 41 | ln, err := net.Listen("tcp", addr) 42 | if err != nil { 43 | log.Fatalf("failed to listen: %v", err) 44 | } 45 | 46 | svr := arpc.NewServer() 47 | svr.Handler.Handle(methodHello, OnClientHello) 48 | 49 | svr.Serve(ln) 50 | } 51 | -------------------------------------------------------------------------------- /examples/pool/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | ) 10 | 11 | func main() { 12 | pool, err := arpc.NewClientPool(func() (net.Conn, error) { 13 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 14 | }, 5) 15 | if err != nil { 16 | panic(err) 17 | } 18 | for i := 0; i < pool.Size(); i++ { 19 | pool.Get(i).Set("user", i) 20 | } 21 | defer pool.Stop() 22 | 23 | for i := 0; i < 10; i++ { 24 | req := "hello" 25 | rsp := "" 26 | client := pool.Next() 27 | err = client.Call("/echo", &req, &rsp, time.Second*5) 28 | user, _ := pool.Get(i).Get("user") 29 | if err != nil { 30 | log.Fatalf("client[%v] Call failed: %v", user, err) 31 | } else { 32 | log.Printf("client[%v] Call Response: \"%v\"", user, rsp) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/pool/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/lesismal/arpc" 7 | ) 8 | 9 | func main() { 10 | svr := arpc.NewServer() 11 | 12 | // register router 13 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 14 | str := "" 15 | err := ctx.Bind(&str) 16 | ctx.Write(str) 17 | log.Printf("/echo: \"%v\", error: %v", str, err) 18 | }) 19 | 20 | svr.Run("localhost:8888") 21 | } 22 | -------------------------------------------------------------------------------- /examples/protocols/kcp/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha1" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/lesismal/arpc" 10 | "github.com/xtaci/kcp-go" 11 | "golang.org/x/crypto/pbkdf2" 12 | ) 13 | 14 | func main() { 15 | client, err := arpc.NewClient(func() (net.Conn, error) { 16 | key := pbkdf2.Key([]byte("demo pass"), []byte("demo salt"), 1024, 32, sha1.New) 17 | block, _ := kcp.NewAESBlockCrypt(key) 18 | return kcp.DialWithOptions("localhost:8888", block, 10, 3) 19 | }) 20 | if err != nil { 21 | panic(err) 22 | } 23 | defer client.Stop() 24 | 25 | req := "hello" 26 | rsp := "" 27 | err = client.Call("/echo", &req, &rsp, time.Second*5) 28 | if err != nil { 29 | log.Fatalf("Call failed: %v", err) 30 | } else { 31 | log.Printf("Call Response: \"%v\"", rsp) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/protocols/kcp/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha1" 5 | "log" 6 | 7 | "github.com/lesismal/arpc" 8 | "github.com/xtaci/kcp-go" 9 | "golang.org/x/crypto/pbkdf2" 10 | ) 11 | 12 | func main() { 13 | key := pbkdf2.Key([]byte("demo pass"), []byte("demo salt"), 1024, 32, sha1.New) 14 | block, _ := kcp.NewAESBlockCrypt(key) 15 | ln, err := kcp.ListenWithOptions("localhost:8888", block, 10, 3) 16 | if err != nil { 17 | log.Fatalf("failed to listen: %v", err) 18 | } 19 | 20 | svr := arpc.NewServer() 21 | 22 | // register router 23 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 24 | str := "" 25 | ctx.Bind(&str) 26 | ctx.Write(str) 27 | log.Printf("/echo: \"%v\", error: %v", str, err) 28 | }) 29 | 30 | svr.Serve(ln) 31 | } 32 | -------------------------------------------------------------------------------- /examples/protocols/quic/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/lesismal/arpc" 10 | "github.com/lesismal/arpc/extension/protocol/quic" 11 | ) 12 | 13 | func main() { 14 | client, err := arpc.NewClient(func() (net.Conn, error) { 15 | tlsConf := &tls.Config{ 16 | InsecureSkipVerify: true, 17 | NextProtos: []string{"quic-echo-example"}, 18 | } 19 | return quic.Dial("localhost:8888", tlsConf, nil, 0) 20 | }) 21 | if err != nil { 22 | panic(err) 23 | } 24 | defer client.Stop() 25 | 26 | req := "hello" 27 | rsp := "" 28 | err = client.Call("/echo", &req, &rsp, time.Second*5) 29 | if err != nil { 30 | log.Fatalf("Call failed: %v", err) 31 | } else { 32 | log.Printf("Call Response: \"%v\"", rsp) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/protocols/quic/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "encoding/pem" 9 | "log" 10 | "math/big" 11 | 12 | "github.com/lesismal/arpc" 13 | "github.com/lesismal/arpc/extension/protocol/quic" 14 | ) 15 | 16 | func main() { 17 | ln, err := quic.Listen("localhost:8888", generateTLSConfig()) 18 | if err != nil { 19 | log.Fatalf("failed to listen: %v", err) 20 | } 21 | svr := arpc.NewServer() 22 | 23 | // register router 24 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 25 | str := "" 26 | err := ctx.Bind(&str) 27 | ctx.Write(str) 28 | log.Printf("/echo: \"%v\", error: %v", str, err) 29 | }) 30 | 31 | svr.Serve(ln) 32 | } 33 | 34 | // Setup a bare-bones TLS config for the server 35 | func generateTLSConfig() *tls.Config { 36 | key, err := rsa.GenerateKey(rand.Reader, 1024) 37 | if err != nil { 38 | panic(err) 39 | } 40 | template := x509.Certificate{SerialNumber: big.NewInt(1)} 41 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) 42 | if err != nil { 43 | panic(err) 44 | } 45 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) 46 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 47 | 48 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 49 | if err != nil { 50 | panic(err) 51 | } 52 | return &tls.Config{ 53 | Certificates: []tls.Certificate{tlsCert}, 54 | NextProtos: []string{"quic-echo-example"}, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/protocols/tls/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/lesismal/arpc" 10 | ) 11 | 12 | func main() { 13 | tlsConfig := &tls.Config{ 14 | InsecureSkipVerify: true, 15 | } 16 | client, err := arpc.NewClient(func() (net.Conn, error) { 17 | return tls.Dial("tcp", "localhost:8888", tlsConfig) 18 | }) 19 | if err != nil { 20 | panic(err) 21 | } 22 | defer client.Stop() 23 | 24 | req := "hello" 25 | rsp := "" 26 | err = client.Call("/echo", &req, &rsp, time.Second*5) 27 | if err != nil { 28 | log.Fatalf("Call failed: %v", err) 29 | } else { 30 | log.Printf("Call Response: \"%v\"", rsp) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/protocols/tls/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "log" 6 | 7 | "github.com/lesismal/arpc" 8 | ) 9 | 10 | var rsaCertPEM = []byte(`-----BEGIN CERTIFICATE----- 11 | MIIDazCCAlOgAwIBAgIUJeohtgk8nnt8ofratXJg7kUJsI4wDQYJKoZIhvcNAQEL 12 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 13 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDEyMDcwODIyNThaFw0zMDEy 14 | MDUwODIyNThaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 15 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB 16 | AQUAA4IBDwAwggEKAoIBAQCy+ZrIvwwiZv4bPmvKx/637ltZLwfgh3ouiEaTchGu 17 | IQltthkqINHxFBqqJg44TUGHWthlrq6moQuKnWNjIsEc6wSD1df43NWBLgdxbPP0 18 | x4tAH9pIJU7TQqbznjDBhzRbUjVXBIcn7bNknY2+5t784pPF9H1v7h8GqTWpNH9l 19 | cz/v+snoqm9HC+qlsFLa4A3X9l5v05F1uoBfUALlP6bWyjHAfctpiJkoB9Yw1TJa 20 | gpq7E50kfttwfKNkkAZIbib10HugkMoQJAs2EsGkje98druIl8IXmuvBIF6nZHuM 21 | lt3UIZjS9RwPPLXhRHt1P0mR7BoBcOjiHgtSEs7Wk+j7AgMBAAGjUzBRMB0GA1Ud 22 | DgQWBBQdheJv73XSOhgMQtkwdYPnfO02+TAfBgNVHSMEGDAWgBQdheJv73XSOhgM 23 | QtkwdYPnfO02+TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBf 24 | SKVNMdmBpD9m53kCrguo9iKQqmhnI0WLkpdWszc/vBgtpOE5ENOfHGAufHZve871 25 | 2fzTXrgR0TF6UZWsQOqCm5Oh3URsCdXWewVMKgJ3DCii6QJ0MnhSFt6+xZE9C6Hi 26 | WhcywgdR8t/JXKDam6miohW8Rum/IZo5HK9Jz/R9icKDGumcqoaPj/ONvY4EUwgB 27 | irKKB7YgFogBmCtgi30beLVkXgk0GEcAf19lHHtX2Pv/lh3m34li1C9eBm1ca3kk 28 | M2tcQtm1G89NROEjcG92cg+GX3GiWIjbI0jD1wnVy2LCOXMgOVbKfGfVKISFt0b1 29 | DNn00G8C6ttLoGU2snyk 30 | -----END CERTIFICATE----- 31 | `) 32 | 33 | var rsaKeyPEM = []byte(`-----BEGIN RSA PRIVATE KEY----- 34 | MIIEogIBAAKCAQEAsvmayL8MImb+Gz5rysf+t+5bWS8H4Id6LohGk3IRriEJbbYZ 35 | KiDR8RQaqiYOOE1Bh1rYZa6upqELip1jYyLBHOsEg9XX+NzVgS4HcWzz9MeLQB/a 36 | SCVO00Km854wwYc0W1I1VwSHJ+2zZJ2Nvube/OKTxfR9b+4fBqk1qTR/ZXM/7/rJ 37 | 6KpvRwvqpbBS2uAN1/Zeb9ORdbqAX1AC5T+m1soxwH3LaYiZKAfWMNUyWoKauxOd 38 | JH7bcHyjZJAGSG4m9dB7oJDKECQLNhLBpI3vfHa7iJfCF5rrwSBep2R7jJbd1CGY 39 | 0vUcDzy14UR7dT9JkewaAXDo4h4LUhLO1pPo+wIDAQABAoIBAF6yWwekrlL1k7Xu 40 | jTI6J7hCUesaS1yt0iQUzuLtFBXCPS7jjuUPgIXCUWl9wUBhAC8SDjWe+6IGzAiH 41 | xjKKDQuz/iuTVjbDAeTb6exF7b6yZieDswdBVjfJqHR2Wu3LEBTRpo9oQesKhkTS 42 | aFF97rZ3XCD9f/FdWOU5Wr8wm8edFK0zGsZ2N6r57yf1N6ocKlGBLBZ0v1Sc5ShV 43 | 1PVAxeephQvwL5DrOgkArnuAzwRXwJQG78L0aldWY2q6xABQZQb5+ml7H/kyytef 44 | i+uGo3jHKepVALHmdpCGr9Yv+yCElup+ekv6cPy8qcmMBqGMISL1i1FEONxLcKWp 45 | GEJi6QECgYEA3ZPGMdUm3f2spdHn3C+/+xskQpz6efiPYpnqFys2TZD7j5OOnpcP 46 | ftNokA5oEgETg9ExJQ8aOCykseDc/abHerYyGw6SQxmDbyBLmkZmp9O3iMv2N8Pb 47 | Nrn9kQKSr6LXZ3gXzlrDvvRoYUlfWuLSxF4b4PYifkA5AfsdiKkj+5sCgYEAzseF 48 | XDTRKHHJnzxZDDdHQcwA0G9agsNj64BGUEjsAGmDiDyqOZnIjDLRt0O2X3oiIE5S 49 | TXySSEiIkxjfErVJMumLaIwqVvlS4pYKdQo1dkM7Jbt8wKRQdleRXOPPN7msoEUk 50 | Ta9ZsftHVUknPqblz9Uthb5h+sRaxIaE1llqDiECgYATS4oHzuL6k9uT+Qpyzymt 51 | qThoIJljQ7TgxjxvVhD9gjGV2CikQM1Vov1JBigj4Toc0XuxGXaUC7cv0kAMSpi2 52 | Y+VLG+K6ux8J70sGHTlVRgeGfxRq2MBfLKUbGplBeDG/zeJs0tSW7VullSkblgL6 53 | nKNa3LQ2QEt2k7KHswryHwKBgENDxk8bY1q7wTHKiNEffk+aFD25q4DUHMH0JWti 54 | fVsY98+upFU+gG2S7oOmREJE0aser0lDl7Zp2fu34IEOdfRY4p+s0O0gB+Vrl5VB 55 | L+j7r9bzaX6lNQN6MvA7ryHahZxRQaD/xLbQHgFRXbHUyvdTyo4yQ1821qwNclLk 56 | HUrhAoGAUtjR3nPFR4TEHlpTSQQovS8QtGTnOi7s7EzzdPWmjHPATrdLhMA0ezPj 57 | Mr+u5TRncZBIzAZtButlh1AHnpN/qO3P0c0Rbdep3XBc/82JWO8qdb5QvAkxga3X 58 | BpA7MNLxiqss+rCbwf3NbWxEMiDQ2zRwVoafVFys7tjmv6t2Xck= 59 | -----END RSA PRIVATE KEY----- 60 | `) 61 | 62 | func main() { 63 | cert, err := tls.X509KeyPair(rsaCertPEM, rsaKeyPEM) 64 | if err != nil { 65 | log.Fatalf("tls.X509KeyPair failed: %v", err) 66 | } 67 | tlsConfig := &tls.Config{ 68 | Certificates: []tls.Certificate{cert}, 69 | InsecureSkipVerify: true, 70 | } 71 | tlsConfig.BuildNameToCertificate() 72 | ln, err := tls.Listen("tcp", "localhost:8888", tlsConfig) 73 | if err != nil { 74 | log.Fatalf("failed to listen: %v", err) 75 | } 76 | svr := arpc.NewServer() 77 | 78 | // register router 79 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 80 | str := "" 81 | ctx.Bind(&str) 82 | ctx.Write(str) 83 | log.Printf("/echo: \"%v\", error: %v", str, err) 84 | }) 85 | 86 | svr.Serve(ln) 87 | } 88 | -------------------------------------------------------------------------------- /examples/protocols/unixsocket/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | ) 10 | 11 | func main() { 12 | client, err := arpc.NewClient(func() (net.Conn, error) { 13 | addr, err := net.ResolveUnixAddr("unix", "bench.unixsock") 14 | if err != nil { 15 | return nil, err 16 | } 17 | return net.DialUnix("unix", nil, addr) 18 | }) 19 | if err != nil { 20 | panic(err) 21 | } 22 | defer client.Stop() 23 | 24 | req := "hello" 25 | rsp := "" 26 | err = client.Call("/echo", &req, &rsp, time.Second*5) 27 | if err != nil { 28 | log.Fatalf("Call failed: %v", err) 29 | } else { 30 | log.Printf("Call Response: \"%v\"", rsp) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/protocols/unixsocket/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | "github.com/lesismal/arpc" 8 | ) 9 | 10 | func main() { 11 | addr, err := net.ResolveUnixAddr("unix", "bench.unixsock") 12 | if err != nil { 13 | log.Fatalf("failed to ResolveUnixAddr: %v", err) 14 | } 15 | ln, err := net.ListenUnix("unix", addr) 16 | if err != nil { 17 | log.Fatalf("failed to ListenUnix: %v", err) 18 | } 19 | 20 | svr := arpc.NewServer() 21 | 22 | // register router 23 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 24 | str := "" 25 | err := ctx.Bind(&str) 26 | ctx.Write(str) 27 | log.Printf("/echo: \"%v\", error: %v", str, err) 28 | }) 29 | 30 | svr.Serve(ln) 31 | } 32 | -------------------------------------------------------------------------------- /examples/protocols/utp/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | "github.com/anacrolix/utp" 10 | "github.com/lesismal/arpc" 11 | ) 12 | 13 | func main() { 14 | client, err := arpc.NewClient(func() (net.Conn, error) { 15 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 16 | defer cancel() 17 | return utp.DialContext(ctx, "localhost:8888") 18 | }) 19 | if err != nil { 20 | panic(err) 21 | } 22 | defer client.Stop() 23 | 24 | req := "hello" 25 | rsp := "" 26 | err = client.Call("/echo", &req, &rsp, time.Second*5) 27 | if err != nil { 28 | log.Fatalf("Call failed: %v", err) 29 | } else { 30 | log.Printf("Call Response: \"%v\"", rsp) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/protocols/utp/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/anacrolix/utp" 7 | "github.com/lesismal/arpc" 8 | ) 9 | 10 | func main() { 11 | ln, err := utp.NewSocket("udp", "localhost:8888") 12 | if err != nil { 13 | log.Fatalf("failed to ListenUnix: %v", err) 14 | } 15 | 16 | svr := arpc.NewServer() 17 | 18 | // register router 19 | svr.Handler.Handle("/echo", func(ctx *arpc.Context) { 20 | str := "" 21 | err := ctx.Bind(&str) 22 | ctx.Write(str) 23 | log.Printf("/echo: \"%v\", error: %v", str, err) 24 | }) 25 | 26 | svr.Serve(ln) 27 | } 28 | -------------------------------------------------------------------------------- /examples/protocols/websocket/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | "github.com/lesismal/arpc/extension/protocol/websocket" 10 | ) 11 | 12 | func main() { 13 | arpc.DefaultHandler.Handle("/server/notify", func(ctx *arpc.Context) { 14 | str := "" 15 | err := ctx.Bind(&str) 16 | log.Printf("/server/notify: \"%v\", error: %v", str, err) 17 | }) 18 | 19 | client, err := arpc.NewClient(func() (net.Conn, error) { 20 | return websocket.Dial("ws://localhost:8888/ws") 21 | }) 22 | if err != nil { 23 | panic(err) 24 | } 25 | defer client.Stop() 26 | 27 | req := "hello" 28 | rsp := "" 29 | err = client.Call("/call/echo", &req, &rsp, time.Second*5) 30 | if err != nil { 31 | log.Fatalf("Call failed: %v", err) 32 | } else { 33 | log.Printf("Call Response: \"%v\"", rsp) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/protocols/websocket/jsclient/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Test Arpc Client

6 |

7 |

8 | 9 | 10 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/protocols/websocket/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/gofiber/fiber/v3" 12 | "github.com/lesismal/arpc" 13 | "github.com/lesismal/arpc/extension/protocol/websocket" 14 | "github.com/valyala/fasthttp/fasthttpadaptor" 15 | ) 16 | 17 | var handlerType = flag.String("h", "fiber", "use std/gin/fiber listener") 18 | 19 | func main() { 20 | flag.Parse() 21 | 22 | var ln net.Listener 23 | 24 | switch *handlerType { 25 | case "gin": 26 | ln = ginListener() 27 | case "fiber": 28 | ln = fberListener() 29 | default: 30 | ln = stdListener() 31 | } 32 | 33 | svr := arpc.NewServer() 34 | // register router 35 | svr.Handler.Handle("/call/echo", func(ctx *arpc.Context) { 36 | str := "" 37 | err := ctx.Bind(&str) 38 | ctx.Write(str) 39 | log.Printf("/call/echo: \"%v\", error: %v", str, err) 40 | }) 41 | 42 | svr.Handler.Handle("/notify", func(ctx *arpc.Context) { 43 | str := "" 44 | err := ctx.Bind(&str) 45 | log.Printf("/notify: \"%v\", error: %v", str, err) 46 | }) 47 | 48 | svr.Handler.HandleConnected(func(c *arpc.Client) { 49 | // go c.Call("/server/call", "server call", 0) 50 | go c.Notify("/server/notify", time.Now().Format("Welcome! Now Is: 2006-01-02 15:04:05.000"), 0) 51 | }) 52 | 53 | svr.Serve(ln) 54 | } 55 | 56 | func ginListener() net.Listener { 57 | router := gin.New() 58 | ln, _ := websocket.Listen("localhost:8888", nil) 59 | router.GET("/ws", func(c *gin.Context) { 60 | w := c.Writer 61 | r := c.Request 62 | ln.(*websocket.Listener).Handler(w, r) 63 | }) 64 | go func() { 65 | err := router.Run("localhost:8888") 66 | if err != nil { 67 | log.Fatalf("router.Run failed: %v", err) 68 | } 69 | }() 70 | return ln 71 | } 72 | 73 | type arpcHTTPHandler struct { 74 | ln *websocket.Listener 75 | } 76 | 77 | func (ah *arpcHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 78 | ah.ln.Handler(w, r) 79 | } 80 | 81 | func fberListener() net.Listener { 82 | router := fiber.New() 83 | ln, _ := websocket.Listen("localhost:8888", nil) 84 | router.Get("/ws", func(c fiber.Ctx) error { 85 | handler := fasthttpadaptor.NewFastHTTPHandler(&arpcHTTPHandler{ln: ln.(*websocket.Listener)}) 86 | handler(c.Context()) 87 | return nil 88 | 89 | }) 90 | go func() { 91 | err := router.Listen("localhost:8888") 92 | if err != nil { 93 | log.Fatalf("router.Run failed: %v", err) 94 | } 95 | }() 96 | return ln 97 | } 98 | 99 | func stdListener() net.Listener { 100 | ln, _ := websocket.Listen("localhost:8888", nil) 101 | http.HandleFunc("/ws", ln.(*websocket.Listener).Handler) 102 | go func() { 103 | err := http.ListenAndServe("localhost:8888", nil) 104 | if err != nil { 105 | log.Fatal("ListenAndServe: ", err) 106 | } 107 | }() 108 | return ln 109 | } 110 | -------------------------------------------------------------------------------- /examples/pubsub/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc/extension/pubsub" 9 | "github.com/lesismal/arpc/log" 10 | ) 11 | 12 | var ( 13 | address = "localhost:8888" 14 | 15 | password = "123qwe" 16 | 17 | topicName = "Broadcast" 18 | ) 19 | 20 | func onTopic(topic *pubsub.Topic) { 21 | log.Info("[OnTopic] [%v] \"%v\", [%v]", 22 | topic.Name, 23 | string(topic.Data), 24 | time.Unix(topic.Timestamp/1000000000, topic.Timestamp%1000000000).Format("2006-01-02 15:04:05.000")) 25 | } 26 | 27 | func consumer(c *pubsub.Client) { 28 | if err := c.Subscribe(topicName, onTopic, time.Second); err != nil { 29 | panic(err) 30 | } 31 | } 32 | 33 | func producer(c *pubsub.Client) { 34 | ticker := time.NewTicker(time.Second) 35 | for i := 0; true; i++ { 36 | _, ok := <-ticker.C 37 | if !ok { 38 | break 39 | } 40 | if i%5 == 0 { 41 | c.Publish(topicName, fmt.Sprintf("message from client %d", i), time.Second) 42 | } else { 43 | c.PublishToOne(topicName, fmt.Sprintf("message from client %d", i), time.Second) 44 | } 45 | } 46 | } 47 | 48 | func dialer() (net.Conn, error) { 49 | return net.DialTimeout("tcp", address, time.Second*3) 50 | } 51 | 52 | func newClient() *pubsub.Client { 53 | client, err := pubsub.NewClient(dialer) 54 | if err != nil { 55 | panic(err) 56 | } 57 | // client.Password = password 58 | 59 | // authentication 60 | err = client.Authenticate() 61 | if err != nil { 62 | panic(err) 63 | } 64 | 65 | return client 66 | } 67 | 68 | func main() { 69 | { 70 | for i := 0; i < 5; i++ { 71 | client := newClient() 72 | defer client.Stop() 73 | consumer(client) 74 | } 75 | } 76 | 77 | { 78 | client := newClient() 79 | defer client.Stop() 80 | go producer(client) 81 | } 82 | 83 | <-make(chan int) 84 | } 85 | -------------------------------------------------------------------------------- /examples/pubsub/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/lesismal/arpc/extension/pubsub" 8 | ) 9 | 10 | var ( 11 | address = "localhost:8888" 12 | 13 | password = "123qwe" 14 | 15 | topicName = "Broadcast" 16 | ) 17 | 18 | func main() { 19 | s := pubsub.NewServer() 20 | // s.Password = password 21 | 22 | // server publish to all clients 23 | go func() { 24 | for i := 0; true; i++ { 25 | time.Sleep(time.Second) 26 | s.Publish(topicName, fmt.Sprintf("message from server %v", i)) 27 | } 28 | }() 29 | 30 | s.Run(address) 31 | } 32 | -------------------------------------------------------------------------------- /examples/pubsub_ws_with_nbio/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc/extension/protocol/websocket" 9 | "github.com/lesismal/arpc/extension/pubsub" 10 | "github.com/lesismal/arpc/log" 11 | ) 12 | 13 | var ( 14 | password = "123qwe" 15 | 16 | topicName = "Broadcast" 17 | ) 18 | 19 | func onTopic(topic *pubsub.Topic) { 20 | log.Info("[OnTopic] [%v] \"%v\", [%v]", 21 | topic.Name, 22 | string(topic.Data), 23 | time.Unix(topic.Timestamp/1000000000, topic.Timestamp%1000000000).Format("2006-01-02 15:04:05.000")) 24 | } 25 | 26 | func consumer(c *pubsub.Client) { 27 | if err := c.Subscribe(topicName, onTopic, time.Second); err != nil { 28 | panic(err) 29 | } 30 | } 31 | 32 | func producer(c *pubsub.Client) { 33 | ticker := time.NewTicker(time.Second) 34 | for i := 0; true; i++ { 35 | _, ok := <-ticker.C 36 | if !ok { 37 | break 38 | } 39 | if i%5 == 0 { 40 | c.Publish(topicName, fmt.Sprintf("message from client %d", i), time.Second) 41 | } else { 42 | c.PublishToOne(topicName, fmt.Sprintf("message from client %d", i), time.Second) 43 | } 44 | } 45 | } 46 | 47 | func dialer() (net.Conn, error) { 48 | return websocket.Dial("ws://localhost:8888/ws") 49 | } 50 | 51 | func newClient() *pubsub.Client { 52 | client, err := pubsub.NewClient(dialer) 53 | if err != nil { 54 | panic(err) 55 | } 56 | // client.Password = password 57 | 58 | // authentication 59 | err = client.Authenticate() 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | return client 65 | } 66 | 67 | func main() { 68 | { 69 | for i := 0; i < 5; i++ { 70 | client := newClient() 71 | defer client.Stop() 72 | consumer(client) 73 | } 74 | } 75 | 76 | { 77 | client := newClient() 78 | defer client.Stop() 79 | go producer(client) 80 | } 81 | 82 | <-make(chan int) 83 | } 84 | -------------------------------------------------------------------------------- /examples/pubsub_ws_with_nbio/server/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "fmt" 23 | "net/http" 24 | "os" 25 | "os/signal" 26 | "time" 27 | 28 | "github.com/lesismal/arpc" 29 | "github.com/lesismal/arpc/codec" 30 | "github.com/lesismal/arpc/extension/pubsub" 31 | "github.com/lesismal/nbio/mempool" 32 | "github.com/lesismal/nbio/nbhttp" 33 | "github.com/lesismal/nbio/nbhttp/websocket" 34 | "github.com/lesismal/nbio/taskpool" 35 | ) 36 | 37 | var ( 38 | psServer *pubsub.Server 39 | 40 | executePool = taskpool.New(1024*4, 1024) 41 | 42 | keepaliveTime = 60 * time.Second 43 | 44 | upgrader = newUpgrader() 45 | ) 46 | 47 | func onWebsocket(w http.ResponseWriter, r *http.Request) { 48 | // if err := authenticate(...); err != nil { 49 | // some log 50 | // return 51 | // } 52 | 53 | wsConn, err := upgrader.Upgrade(w, r, nil) 54 | if err != nil { 55 | wsConn.Close() 56 | return 57 | } 58 | wsConn.SetReadDeadline(time.Now().Add(keepaliveTime)) 59 | } 60 | 61 | func main() { 62 | // disable frameworks' log 63 | { 64 | // alog.SetLevel(alog.LevelNone) 65 | // nlog.SetLevel(nlog.LevelNone) 66 | } 67 | 68 | // init pubsub server handler for nbhttp 69 | { 70 | // arpc.DefaultAllocator = mempool.DefaultMemPool 71 | // psServer.Handler.EnablePool(true) 72 | arpc.SetAsyncResponse(true) 73 | psServer = pubsub.NewServer() 74 | // must set async write for nbio 75 | psServer.Handler.SetAsyncWrite(false) 76 | } 77 | 78 | mux := &http.ServeMux{} 79 | mux.HandleFunc("/ws", onWebsocket) 80 | 81 | svr := nbhttp.NewEngine(nbhttp.Config{ 82 | Network: "tcp", 83 | Addrs: []string{"localhost:8888"}, 84 | Handler: mux, 85 | ReleaseWebsocketPayload: true, 86 | }) 87 | 88 | err := svr.Start() 89 | if err != nil { 90 | fmt.Printf("nbio.Start failed: %v\n", err) 91 | return 92 | } 93 | 94 | go func() { 95 | topicName := "Broadcast" 96 | for i := 0; true; i++ { 97 | time.Sleep(time.Second) 98 | psServer.Publish(topicName, fmt.Sprintf("message from server %v", i)) 99 | } 100 | }() 101 | 102 | interrupt := make(chan os.Signal, 1) 103 | signal.Notify(interrupt, os.Interrupt) 104 | <-interrupt 105 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 106 | defer cancel() 107 | svr.Shutdown(ctx) 108 | } 109 | 110 | type WebsocketConn struct { 111 | *websocket.Conn 112 | } 113 | 114 | func (c *WebsocketConn) Read(buf []byte) (int, error) { 115 | return -1, errors.New("unsupported") 116 | } 117 | 118 | func (c *WebsocketConn) Write(data []byte) (int, error) { 119 | err := c.Conn.WriteMessage(websocket.BinaryMessage, data) 120 | if err != nil { 121 | return 0, err 122 | } 123 | return len(data), nil 124 | } 125 | 126 | // Session . 127 | type Session struct { 128 | *arpc.Client 129 | cache []byte 130 | } 131 | 132 | func newUpgrader() *websocket.Upgrader { 133 | u := websocket.NewUpgrader() 134 | u.OnOpen(onOpen) 135 | u.OnMessage(onMessage) 136 | u.OnClose(onClose) 137 | return u 138 | } 139 | 140 | func onOpen(c *websocket.Conn) { 141 | client := &arpc.Client{Conn: &WebsocketConn{c}, Codec: codec.DefaultCodec, Handler: psServer.Handler} 142 | client.SetState(true) 143 | session := &Session{ 144 | Client: client, 145 | } 146 | c.SetSession(session) 147 | } 148 | 149 | func onClose(c *websocket.Conn, err error) { 150 | iSession := c.Session() 151 | if iSession == nil { 152 | c.Close() 153 | return 154 | } 155 | session, ok := iSession.(*Session) 156 | if ok { 157 | if session.cache != nil { 158 | mempool.Free(session.cache) 159 | session.cache = nil 160 | } 161 | psServer.Handler.OnDisconnected(session.Client) 162 | } 163 | } 164 | 165 | func onMessage(c *websocket.Conn, mt websocket.MessageType, data []byte) { 166 | iSession := c.Session() 167 | if iSession == nil { 168 | c.Close() 169 | return 170 | } 171 | 172 | session := iSession.(*Session) 173 | start := 0 174 | if session.cache != nil { 175 | session.cache = append(session.cache, data...) 176 | data = session.cache 177 | } 178 | 179 | received := false 180 | for { 181 | if len(data)-start < arpc.HeadLen { 182 | goto Exit 183 | } 184 | header := arpc.Header(data[start : start+4]) 185 | total := arpc.HeadLen + header.BodyLen() 186 | if len(data)-start < total { 187 | goto Exit 188 | } 189 | 190 | buffer := mempool.Malloc(total) 191 | copy(buffer, data[start:start+total]) 192 | start += total 193 | msg := psServer.Handler.NewMessageWithBuffer(buffer) 194 | psServer.Handler.OnMessage(session.Client, msg) 195 | received = true 196 | } 197 | 198 | Exit: 199 | if received { 200 | c.SetReadDeadline(time.Now().Add(keepaliveTime)) 201 | } 202 | if session.cache != nil { 203 | if start == len(data) { 204 | mempool.Free(data) 205 | session.cache = nil 206 | } else { 207 | left := mempool.Malloc(len(data) - start) 208 | copy(left, data[start:]) 209 | mempool.Free(data) 210 | session.cache = left 211 | } 212 | } else if start < len(data) { 213 | left := mempool.Malloc(len(data) - start) 214 | copy(left, data[start:]) 215 | mempool.Free(data) 216 | session.cache = left 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /examples/rpc/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | ) 10 | 11 | func main() { 12 | client, err := arpc.NewClient(func() (net.Conn, error) { 13 | return net.DialTimeout("tcp", "localhost:8888", time.Second*3) 14 | }) 15 | if err != nil { 16 | panic(err) 17 | } 18 | defer client.Stop() 19 | 20 | req := "hello" 21 | rsp := "" 22 | err = client.Call("/echo/sync", &req, &rsp, time.Second*5) 23 | if err != nil { 24 | log.Fatalf("Call /echo/sync failed: %v", err) 25 | } else { 26 | log.Printf("Call /echo/sync Response: \"%v\"", rsp) 27 | } 28 | err = client.Call("/echo/async", &req, &rsp, time.Second*5) 29 | if err != nil { 30 | log.Fatalf("Call /echo/async failed: %v", err) 31 | } else { 32 | log.Printf("Call /echo/async Response: \"%v\"", rsp) 33 | } 34 | done := make(chan string) 35 | err = client.CallAsync("/echo/async", &req, func(ctx *arpc.Context, er error) { 36 | if er != nil { 37 | log.Fatalf("Call /echo/async failed: %v", err) 38 | } 39 | rsp := "" 40 | er = ctx.Bind(&rsp) 41 | if er != nil { 42 | log.Fatalf("Call /echo/async Bind failed: %v", er) 43 | } 44 | if rsp != req { 45 | log.Fatalf("Call /echo/async failed: %v", er) 46 | } 47 | done <- rsp 48 | }, time.Second*5) 49 | if err != nil { 50 | log.Fatalf("Call /echo/async failed: %v", err) 51 | } else { 52 | rsp := <-done 53 | log.Printf("CallAsync /echo/async Response: \"%v\"", rsp) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/rpc/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/lesismal/arpc" 7 | ) 8 | 9 | func main() { 10 | svr := arpc.NewServer() 11 | 12 | // register router 13 | svr.Handler.Handle("/echo/sync", func(ctx *arpc.Context) { 14 | str := "" 15 | err := ctx.Bind(&str) 16 | ctx.Write(str) 17 | log.Printf("/echo/sync: \"%v\", error: %v", str, err) 18 | }) 19 | 20 | // register router 21 | svr.Handler.Handle("/echo/async", func(ctx *arpc.Context) { 22 | str := "" 23 | err := ctx.Bind(&str) 24 | go ctx.Write(str) 25 | log.Printf("/echo/async: \"%v\", error: %v", str, err) 26 | }) 27 | 28 | svr.Run("localhost:8888") 29 | } 30 | -------------------------------------------------------------------------------- /examples/stream/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | "sync" 11 | "time" 12 | 13 | "github.com/lesismal/arpc" 14 | ) 15 | 16 | var ( 17 | addr = "localhost:8888" 18 | 19 | method = "Hello" 20 | ) 21 | 22 | // HelloReq . 23 | type HelloReq struct { 24 | Msg string 25 | } 26 | 27 | // HelloRsp . 28 | type HelloRsp struct { 29 | Msg string 30 | } 31 | 32 | func dialer() (net.Conn, error) { 33 | return net.DialTimeout("tcp", addr, time.Second*3) 34 | } 35 | 36 | func main() { 37 | arpc.EnablePool(true) 38 | 39 | client, err := arpc.NewClient(dialer) 40 | if err != nil { 41 | log.Println("NewClient failed:", err) 42 | return 43 | } 44 | defer client.Stop() 45 | 46 | wg := &sync.WaitGroup{} 47 | wg.Add(1) 48 | client.Handler.HandleStream("/stream_server_to_client", func(stream *arpc.Stream) { 49 | defer wg.Done() 50 | defer stream.CloseRecv() 51 | for { 52 | str := "" 53 | err := stream.Recv(&str) 54 | if err == io.EOF { 55 | stream.CloseSend() 56 | log.Printf("[client] [stream id: %v] stream_server_to_client closed", stream.Id()) 57 | break 58 | } 59 | if err != nil { 60 | panic(err) 61 | } 62 | log.Printf("[client] [stream id: %v] stream_server_to_client: %v", stream.Id(), str) 63 | err = stream.Send(&str) 64 | if err != nil { 65 | panic(err) 66 | } 67 | } 68 | }) 69 | 70 | data := make([]byte, 10) 71 | rand.Read(data) 72 | req := &HelloReq{Msg: base64.RawStdEncoding.EncodeToString(data)} 73 | rsp := &HelloRsp{} 74 | err = client.Call(method, req, rsp, time.Second*5) 75 | if err != nil { 76 | log.Printf("Call failed: %v", err) 77 | } else if rsp.Msg != req.Msg { 78 | log.Fatal("Call failed: not equal") 79 | } 80 | 81 | wg.Wait() 82 | time.Sleep(time.Second) 83 | 84 | stream := client.NewStream("/stream_client_to_server") 85 | defer stream.CloseRecv() 86 | go func() { 87 | for i := 0; i < 3; i++ { 88 | err := stream.Send(fmt.Sprintf("stream data %v", i)) 89 | if err != nil { 90 | panic(err) 91 | } 92 | } 93 | err = stream.SendAndClose(fmt.Sprintf("stream data %v", 3)) 94 | if err != nil { 95 | panic(err) 96 | } 97 | }() 98 | 99 | for { 100 | str := "" 101 | err = stream.Recv(&str) 102 | if err == io.EOF { 103 | log.Printf("[client] [stream id: %v] stream_client_to_server closed", stream.Id()) 104 | break 105 | } 106 | if err != nil { 107 | panic(err) 108 | } 109 | log.Printf("[client] [stream id: %v] stream_client_to_server: %v", stream.Id(), str) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /examples/stream/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net" 8 | 9 | "github.com/lesismal/arpc" 10 | ) 11 | 12 | const ( 13 | addr = "localhost:8888" 14 | ) 15 | 16 | // HelloReq . 17 | type HelloReq struct { 18 | Msg string 19 | } 20 | 21 | // HelloRsp . 22 | type HelloRsp struct { 23 | Msg string 24 | } 25 | 26 | // OnHello . 27 | func OnHello(ctx *arpc.Context) { 28 | req := &HelloReq{} 29 | ctx.Bind(req) 30 | ctx.Write(&HelloRsp{Msg: req.Msg}) 31 | 32 | stream := ctx.Client.NewStream("/stream_server_to_client") 33 | defer stream.CloseRecv() 34 | go func() { 35 | for i := 0; i < 3; i++ { 36 | err := stream.Send(fmt.Sprintf("stream data %v", i)) 37 | if err != nil { 38 | panic(err) 39 | } 40 | } 41 | err := stream.SendAndClose(fmt.Sprintf("stream data %v", 3)) 42 | if err != nil { 43 | panic(err) 44 | } 45 | }() 46 | 47 | for { 48 | str := "" 49 | err := stream.Recv(&str) 50 | if err == io.EOF { 51 | log.Printf("[server] [stream id: %v] stream_server_to_client closed", stream.Id()) 52 | break 53 | } 54 | if err != nil { 55 | panic(err) 56 | } 57 | log.Printf("[server] [stream id: %v] stream_server_to_client: %v", stream.Id(), str) 58 | } 59 | } 60 | 61 | func OnStream(stream *arpc.Stream) { 62 | defer stream.CloseRecv() 63 | for { 64 | str := "" 65 | err := stream.Recv(&str) 66 | if err == io.EOF { 67 | stream.CloseSend() 68 | log.Printf("[server] [stream id: %v] stream_client_to_server closed", stream.Id()) 69 | break 70 | } 71 | if err != nil { 72 | panic(err) 73 | } 74 | log.Printf("[server] [stream id: %v] stream_client_to_server: [%v]", stream.Id(), str) 75 | err = stream.Send(&str) 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | } 81 | 82 | func main() { 83 | ln, err := net.Listen("tcp", addr) 84 | if err != nil { 85 | log.Fatalf("failed to listen: %v", err) 86 | } 87 | 88 | svr := arpc.NewServer() 89 | svr.Handler.EnablePool(true) 90 | svr.Handler.SetAsyncResponse(true) 91 | svr.Handler.Handle("Hello", OnHello) 92 | svr.Handler.HandleStream("/stream_client_to_server", OnStream) 93 | svr.Serve(ln) 94 | } 95 | -------------------------------------------------------------------------------- /examples/webchat/README.md: -------------------------------------------------------------------------------- 1 | ## Web Chat Example 2 | 3 | ```sh 4 | cd $GOPATH/src/github.com/lesismal/arpc/examples/webchat 5 | go run server.go 6 | ``` 7 | - visit [http://localhost:8888](http://localhost:8888) 8 | -------------------------------------------------------------------------------- /examples/webchat/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Arpc Chat Example 6 | 7 | 93 | 129 | 130 | 131 | 132 |
133 |
134 | 135 | 136 |
137 | 138 | 139 | -------------------------------------------------------------------------------- /examples/webchat/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | 11 | "github.com/lesismal/arpc" 12 | "github.com/lesismal/arpc/extension/protocol/websocket" 13 | "github.com/lesismal/arpc/log" 14 | ) 15 | 16 | // Message . 17 | type Message struct { 18 | User uint64 `json:"user"` 19 | Message string `json:"message"` 20 | Timestamp int64 `json:"timestamp"` 21 | } 22 | 23 | // NewMessage . 24 | func NewMessage(user uint64, msg string) *Message { 25 | return &Message{ 26 | User: user, 27 | Message: msg, 28 | Timestamp: time.Now().UnixNano(), 29 | } 30 | } 31 | 32 | // Room . 33 | type Room struct { 34 | users map[*arpc.Client]uint64 35 | chEnterRoom chan *arpc.Client 36 | chLeaveRoom chan *arpc.Client 37 | chBroadcast chan *Message 38 | chStop chan struct{} 39 | } 40 | 41 | // Enter . 42 | func (room *Room) Enter(cli *arpc.Client) { 43 | room.chEnterRoom <- cli 44 | } 45 | 46 | // Leave . 47 | func (room *Room) Leave(cli *arpc.Client) { 48 | room.chLeaveRoom <- cli 49 | } 50 | 51 | // Broadcast . 52 | func (room *Room) Broadcast(msg *Message) { 53 | room.chBroadcast <- msg 54 | } 55 | 56 | // Run . 57 | func (room *Room) Run() *Room { 58 | go func() { 59 | for userCnt := uint64(10000); true; userCnt++ { 60 | select { 61 | case cli := <-room.chEnterRoom: 62 | room.users[cli] = userCnt 63 | cli.Set("user", userCnt) 64 | userid := fmt.Sprintf("%v", userCnt) 65 | cli.Notify("/chat/server/userid", userid, 0) 66 | room.broadcastMsg("/chat/server/userenter", NewMessage(userCnt, "")) 67 | userCnt++ 68 | log.Info("[user_%v] enter room", userid) 69 | case cli := <-room.chLeaveRoom: 70 | delete(room.users, cli) 71 | user, ok := cli.Get("user") 72 | if ok { 73 | userid, ok := user.(uint64) 74 | if ok { 75 | room.broadcastMsg("/chat/server/userleave", NewMessage(userid, "")) 76 | log.Info("[user_%v] leave room", userid) 77 | } 78 | } 79 | case msg := <-room.chBroadcast: 80 | room.broadcastMsg("/chat/server/broadcast", msg) 81 | case <-room.chStop: 82 | room.broadcastMsg("/chat/server/shutdown", nil) 83 | return 84 | } 85 | } 86 | }() 87 | return room 88 | } 89 | 90 | // Stop . 91 | func (room *Room) Stop() *Room { 92 | close(room.chStop) 93 | return room 94 | } 95 | 96 | func (room *Room) broadcastMsg(method string, msg *Message) { 97 | for cli := range room.users { 98 | cli.Notify(method, msg, 0) 99 | } 100 | } 101 | 102 | // NewRoom . 103 | func NewRoom() *Room { 104 | return &Room{ 105 | users: map[*arpc.Client]uint64{}, 106 | chEnterRoom: make(chan *arpc.Client, 1024), 107 | chLeaveRoom: make(chan *arpc.Client, 1024), 108 | chBroadcast: make(chan *Message, 1024), 109 | chStop: make(chan struct{}), 110 | } 111 | } 112 | 113 | // NewServer . 114 | func NewServer(room *Room) *arpc.Server { 115 | ln, _ := websocket.Listen("localhost:8888", nil) 116 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 117 | log.Info("url: %v", r.URL.String()) 118 | if r.URL.Path == "/" { 119 | http.ServeFile(w, r, "chat.html") 120 | } else if r.URL.Path == "/arpc.js" { 121 | http.ServeFile(w, r, "arpc.js") 122 | } else { 123 | http.NotFound(w, r) 124 | } 125 | }) 126 | http.HandleFunc("/ws", ln.(*websocket.Listener).Handler) 127 | go func() { 128 | err := http.ListenAndServe("localhost:8888", nil) 129 | if err != nil { 130 | log.Error("ListenAndServe: ", err) 131 | panic(err) 132 | } 133 | }() 134 | 135 | svr := arpc.NewServer() 136 | 137 | svr.Handler.Handle("/chat/user/say", func(ctx *arpc.Context) { 138 | if user, ok := ctx.Client.Get("user"); ok { 139 | if userid, ok := user.(uint64); ok { 140 | msg := &Message{User: userid} 141 | err := ctx.Bind(&msg.Message) 142 | if err == nil { 143 | room.Broadcast(msg) 144 | } 145 | ctx.Write(msg.Message) 146 | } 147 | } 148 | }) 149 | 150 | svr.Handler.HandleConnected(func(c *arpc.Client) { 151 | room.Enter(c) 152 | }) 153 | 154 | svr.Handler.HandleDisconnected(func(c *arpc.Client) { 155 | room.Leave(c) 156 | }) 157 | 158 | go svr.Serve(ln) 159 | 160 | return svr 161 | } 162 | 163 | func main() { 164 | room := NewRoom().Run() 165 | server := NewServer(room) 166 | 167 | quit := make(chan os.Signal, 1) 168 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 169 | <-quit 170 | 171 | room.Stop() 172 | time.Sleep(time.Second / 10) 173 | server.Stop() 174 | 175 | log.Info("server exit") 176 | } 177 | -------------------------------------------------------------------------------- /extension/arpchttp/handler.go: -------------------------------------------------------------------------------- 1 | package arpchttp 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/lesismal/arpc" 10 | "github.com/lesismal/arpc/codec" 11 | ) 12 | 13 | type Conn struct { 14 | w http.ResponseWriter 15 | } 16 | 17 | func (c *Conn) Read(b []byte) (n int, err error) { 18 | return 0, nil 19 | } 20 | 21 | func (c *Conn) Write(b []byte) (n int, err error) { 22 | n, err = c.w.Write(b) 23 | return n, err 24 | } 25 | 26 | func (c *Conn) Close() error { 27 | return nil 28 | } 29 | 30 | func (c *Conn) LocalAddr() net.Addr { 31 | return nil 32 | } 33 | 34 | func (c *Conn) RemoteAddr() net.Addr { 35 | return nil 36 | } 37 | 38 | func (c *Conn) SetDeadline(t time.Time) error { 39 | return nil 40 | } 41 | 42 | func (c *Conn) SetReadDeadline(t time.Time) error { 43 | return nil 44 | } 45 | 46 | func (c *Conn) SetWriteDeadline(t time.Time) error { 47 | return nil 48 | } 49 | 50 | func Handler(nh arpc.Handler, codec codec.Codec) http.HandlerFunc { 51 | return func(w http.ResponseWriter, r *http.Request) { 52 | if r.Body == nil { 53 | w.WriteHeader(http.StatusBadRequest) 54 | return 55 | } 56 | 57 | defer r.Body.Close() 58 | body, err := io.ReadAll(r.Body) 59 | if err != nil { 60 | w.WriteHeader(http.StatusBadRequest) 61 | return 62 | } 63 | 64 | client := &arpc.Client{Conn: &Conn{w}, Codec: codec, Handler: nh} 65 | msg := nh.NewMessageWithBuffer(body) 66 | nh.OnMessage(client, msg) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /extension/listener/listener.go: -------------------------------------------------------------------------------- 1 | package listener 2 | 3 | import ( 4 | "net" 5 | "sync/atomic" 6 | "time" 7 | 8 | "github.com/lesismal/arpc/log" 9 | ) 10 | 11 | type event struct { 12 | err error 13 | conn net.Conn 14 | } 15 | 16 | type Listener struct { 17 | logtag string 18 | listener net.Listener 19 | onlineA int32 20 | onlineB int32 21 | maxOnlineA int32 22 | chEventA chan event 23 | chEventB chan event 24 | } 25 | 26 | func (l *Listener) Accept() (net.Conn, error) { 27 | c, err := l.listener.Accept() 28 | return c, err 29 | } 30 | 31 | func (l *Listener) Close() error { 32 | if l.listener == nil { 33 | return nil 34 | } 35 | close(l.chEventA) 36 | close(l.chEventB) 37 | return l.listener.Close() 38 | } 39 | 40 | // Addr returns the listener's network address. 41 | func (l *Listener) Addr() net.Addr { 42 | if l.listener == nil { 43 | return nil 44 | } 45 | return l.listener.Addr() 46 | } 47 | 48 | func (l *Listener) Listeners() (net.Listener, net.Listener) { 49 | return &ChanListener{ 50 | addr: l.listener.Addr(), 51 | chEvent: l.chEventA, 52 | }, &ChanListener{ 53 | addr: l.listener.Addr(), 54 | chEvent: l.chEventB, 55 | } 56 | } 57 | 58 | func (l *Listener) OfflineA() { 59 | atomic.AddInt32(&l.onlineA, -1) 60 | } 61 | 62 | func (l *Listener) Run() { 63 | tempSleep := time.Duration(0) 64 | maxSleep := time.Second 65 | for { 66 | c, err := l.Accept() 67 | if err != nil { 68 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 69 | log.Error("%v Accept error: %v; retrying...", l.logtag, err) 70 | if tempSleep == 0 { 71 | tempSleep = time.Millisecond * 20 72 | } else { 73 | tempSleep *= 2 74 | } 75 | if tempSleep > maxSleep { 76 | tempSleep = maxSleep 77 | } 78 | time.Sleep(tempSleep) 79 | } else { 80 | log.Error("%v Accept error: %v", l.logtag, err) 81 | return 82 | } 83 | } else { 84 | if atomic.AddInt32(&l.onlineA, 1) <= l.maxOnlineA { 85 | func() { 86 | // avoid conn leak when close 87 | defer func() { 88 | if err := recover(); err != nil { 89 | c.Close() 90 | } 91 | }() 92 | l.chEventA <- event{err: nil, conn: c} 93 | }() 94 | } else { 95 | atomic.AddInt32(&l.onlineA, -1) 96 | // avoid conn leak when close 97 | func() { 98 | defer func() { 99 | if err := recover(); err != nil { 100 | c.Close() 101 | } 102 | }() 103 | l.chEventB <- event{err: nil, conn: c} 104 | }() 105 | } 106 | tempSleep = 0 107 | } 108 | } 109 | } 110 | 111 | func Listen(network, addr string, maxOnlineA int, logtag string) (*Listener, error) { 112 | l, err := net.Listen(network, addr) 113 | if err != nil { 114 | return nil, err 115 | } 116 | if maxOnlineA < 0 { 117 | maxOnlineA = 0 118 | } 119 | if logtag == "" { 120 | logtag = "[ARPC SVR]" 121 | } 122 | return &Listener{ 123 | logtag: logtag, 124 | listener: l, 125 | onlineA: 0, 126 | onlineB: 0, 127 | maxOnlineA: int32(maxOnlineA), 128 | chEventA: make(chan event, 4096), 129 | chEventB: make(chan event, 4096), 130 | }, nil 131 | } 132 | 133 | type ChanListener struct { 134 | addr net.Addr 135 | chEvent chan event 136 | } 137 | 138 | func (l *ChanListener) Accept() (net.Conn, error) { 139 | e, ok := <-l.chEvent 140 | if !ok { 141 | return nil, net.ErrClosed 142 | } 143 | return e.conn, e.err 144 | } 145 | 146 | func (l *ChanListener) Close() error { 147 | return nil 148 | } 149 | 150 | // Addr returns the listener's network address. 151 | func (l *ChanListener) Addr() net.Addr { 152 | return l.addr 153 | } 154 | -------------------------------------------------------------------------------- /extension/micro/etcd/discovery.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/lesismal/arpc/extension/micro" 8 | "github.com/lesismal/arpc/log" 9 | "github.com/lesismal/arpc/util" 10 | clientv3 "go.etcd.io/etcd/client/v3" 11 | ) 12 | 13 | // Discovery . 14 | type Discovery struct { 15 | client *clientv3.Client 16 | prefix string 17 | serviceManager micro.ServiceManager 18 | } 19 | 20 | func (ds *Discovery) init(done chan struct{}) { 21 | doneWatch := make(chan struct{}) 22 | go util.Safe(func() { 23 | ds.lazyInit(done, doneWatch) 24 | }) 25 | ds.watch(doneWatch) 26 | } 27 | 28 | func (ds *Discovery) lazyInit(done, doneWatch chan struct{}) { 29 | defer close(done) 30 | 31 | <-doneWatch 32 | time.Sleep(time.Second / 100) 33 | resp, err := ds.client.Get(context.Background(), ds.prefix, clientv3.WithPrefix()) 34 | if err != nil { 35 | return 36 | } 37 | 38 | for _, ev := range resp.Kvs { 39 | if ds.serviceManager != nil { 40 | ds.serviceManager.AddServiceNodes(string(ev.Key), string(ev.Value)) 41 | } 42 | } 43 | } 44 | 45 | func (ds *Discovery) watch(doneWatch chan struct{}) { 46 | rch := ds.client.Watch(context.Background(), ds.prefix, clientv3.WithPrefix()) 47 | close(doneWatch) 48 | log.Info("Discovery watching: %s", ds.prefix) 49 | for wresp := range rch { 50 | for _, ev := range wresp.Events { 51 | switch ev.Type { 52 | case clientv3.EventTypePut: 53 | if ds.serviceManager != nil { 54 | ds.serviceManager.AddServiceNodes(string(ev.Kv.Key), string(ev.Kv.Value)) 55 | } 56 | case clientv3.EventTypeDelete: 57 | if ds.serviceManager != nil { 58 | ds.serviceManager.DeleteServiceNodes(string(ev.Kv.Key)) 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | // Stop . 66 | func (ds *Discovery) Stop() error { 67 | return ds.client.Close() 68 | } 69 | 70 | // NewDiscovery . 71 | func NewDiscovery(endpoints []string, prefix string, serviceManager micro.ServiceManager) (*Discovery, error) { 72 | client, err := clientv3.New(clientv3.Config{ 73 | Endpoints: endpoints, 74 | DialTimeout: 5 * time.Second, 75 | }) 76 | if err != nil { 77 | log.Error("NewDiscovery [%v] clientv3.New failed: %v", prefix, err) 78 | return nil, err 79 | } 80 | 81 | ds := &Discovery{ 82 | client: client, 83 | prefix: prefix, 84 | serviceManager: serviceManager, 85 | } 86 | 87 | done := make(chan struct{}) 88 | go util.Safe(func() { 89 | ds.init(done) 90 | }) 91 | <-done 92 | 93 | log.Info("NewDiscovery [%v] success", prefix) 94 | 95 | return ds, nil 96 | } 97 | -------------------------------------------------------------------------------- /extension/micro/etcd/register.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/lesismal/arpc/log" 8 | "github.com/lesismal/arpc/util" 9 | "go.etcd.io/etcd/client/v3" 10 | "go.etcd.io/etcd/client/v3/concurrency" 11 | ) 12 | 13 | // RegisterMutexPrefix . 14 | const RegisterMutexPrefix = "_arpc_micro_reg_mux_pfx" 15 | 16 | //Register . 17 | type Register struct { 18 | key string 19 | value string 20 | client *clientv3.Client 21 | leaseID clientv3.LeaseID 22 | chKeepalive <-chan *clientv3.LeaseKeepAliveResponse 23 | } 24 | 25 | // listenTTL . 26 | func (s *Register) listenTTL() { 27 | log.Info("Register listenTTL start") 28 | for resp := range s.chKeepalive { 29 | log.Debug("Register listenTTL: %v", resp) 30 | } 31 | log.Info("Register listenTTL stop") 32 | } 33 | 34 | // Stop . 35 | func (s *Register) Stop() error { 36 | // cancel ttl 37 | if _, err := s.client.Revoke(context.Background(), s.leaseID); err != nil { 38 | log.Error("Register Stop failed: %v", err) 39 | return err 40 | } 41 | return s.client.Close() 42 | } 43 | 44 | // NewRegister . 45 | func NewRegister(endpoints []string, key string, value string, ttl int64) (*Register, error) { 46 | // step 1: new client 47 | client, err := clientv3.New(clientv3.Config{ 48 | Endpoints: endpoints, 49 | DialTimeout: 5 * time.Second, 50 | }) 51 | if err != nil { 52 | log.Error("NewRegister [%v, %v] clientv3.New failed: %v", key, value, err) 53 | return nil, err 54 | } 55 | 56 | session, err := concurrency.NewSession(client) 57 | if err != nil { 58 | log.Error("NewRegister [%v, %v] concurrency.NewSession failed: %v", key, value, err) 59 | return nil, err 60 | } 61 | 62 | mux := concurrency.NewMutex(session, RegisterMutexPrefix) 63 | err = mux.Lock(context.TODO()) 64 | if err != nil { 65 | log.Error("NewRegister [%v, %v] Lock failed: %v", key, value, err) 66 | return nil, err 67 | } 68 | defer mux.Unlock(context.TODO()) 69 | 70 | // step 2: generate ttl 71 | resGrant, err := client.Grant(context.Background(), ttl) 72 | if err != nil { 73 | log.Error("NewRegister [%v, %v] client.Grant failed: %v", key, value, err) 74 | return nil, err 75 | } 76 | 77 | resGet, err := client.Get(context.Background(), key) 78 | if err != nil { 79 | log.Error("NewRegister [%v, %v] client.Get failed: %v", key, value, err) 80 | return nil, err 81 | } 82 | if len(resGet.Kvs) > 0 { 83 | log.Error("NewRegister [%v, %v] failed: already exists", key, value) 84 | return nil, err 85 | } 86 | 87 | // step 3: set kv 88 | _, err = client.Put(context.Background(), key, value, clientv3.WithLease(resGrant.ID)) 89 | if err != nil { 90 | log.Error("NewRegister [%v, %v] client.Put failed: %v", key, value, err) 91 | return nil, err 92 | } 93 | 94 | // step 4: set ttl and keepalive 95 | chKeepalive, err := client.KeepAlive(context.Background(), resGrant.ID) 96 | if err != nil { 97 | log.Error("NewRegister [%v, %v] client.KeepAlive failed: %v", key, value, err) 98 | return nil, err 99 | } 100 | 101 | register := &Register{ 102 | key: key, 103 | value: value, 104 | client: client, 105 | leaseID: resGrant.ID, 106 | chKeepalive: chKeepalive, 107 | } 108 | 109 | log.Info("NewRegister [%v, %v] success", key, value) 110 | 111 | go util.Safe(register.listenTTL) 112 | 113 | return register, nil 114 | } 115 | -------------------------------------------------------------------------------- /extension/micro/redis/client.go: -------------------------------------------------------------------------------- 1 | package redis 2 | -------------------------------------------------------------------------------- /extension/micro/redis/discovery.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/go-redis/redis/v8" 11 | "github.com/lesismal/arpc/extension/micro" 12 | "github.com/lesismal/arpc/log" 13 | "github.com/lesismal/arpc/util" 14 | ) 15 | 16 | // Discovery . 17 | type Discovery struct { 18 | client *redis.Client 19 | 20 | serviceNamespace string 21 | interval time.Duration 22 | serviceManager micro.ServiceManager 23 | 24 | serviceCache map[string]struct{} 25 | 26 | done chan struct{} 27 | } 28 | 29 | func (ds *Discovery) updateServices() error { 30 | ret := ds.client.HGetAll(context.Background(), ds.serviceNamespace) 31 | err := ret.Err() 32 | if err != nil { 33 | log.Error("Discovery updateServices failed: %v", err) 34 | } 35 | 36 | cache := map[string]struct{}{} 37 | services := ret.Val() 38 | if services != nil { 39 | for k, v := range services { 40 | deleteService := false 41 | strs := strings.Split(k, "/") 42 | if len(strs) == 3 { 43 | serviceName := strs[0] 44 | serviceAddr := strs[1] 45 | sweight := strs[2] 46 | weight, err := strconv.ParseInt(v, 10, 64) 47 | if err != nil || weight <= 0 { 48 | deleteService = true 49 | } else { 50 | expire, err := strconv.ParseInt(v, 10, 64) 51 | if err != nil { 52 | deleteService = true 53 | } else { 54 | if time.Now().Unix() > expire { 55 | deleteService = true 56 | } else { 57 | path := fmt.Sprintf("%v/%v/%v", ds.serviceNamespace, serviceName, serviceAddr) 58 | cache[path] = struct{}{} 59 | _, ok := ds.serviceCache[path] 60 | if !ok { 61 | ds.serviceManager.AddServiceNodes(path, sweight) 62 | } 63 | delete(ds.serviceCache, path) 64 | } 65 | } 66 | } 67 | } else { 68 | deleteService = true 69 | } 70 | if deleteService { 71 | ret := ds.client.HDel(context.Background(), ds.serviceNamespace, k) 72 | err = ret.Err() 73 | if err != nil { 74 | log.Error("Discovery delete expired service[%v: %v] failed: %v", ds.serviceNamespace, k, err) 75 | } 76 | } 77 | } 78 | } 79 | 80 | // for path := range cache { 81 | // delete(ds.serviceCache, path) 82 | // } 83 | for path := range ds.serviceCache { 84 | ds.serviceManager.DeleteServiceNodes(path) 85 | } 86 | 87 | ds.serviceCache = cache 88 | 89 | return nil 90 | } 91 | 92 | func (ds *Discovery) update() { 93 | ticker := time.NewTicker(ds.interval) 94 | defer ticker.Stop() 95 | 96 | for { 97 | select { 98 | case <-ds.done: 99 | return 100 | case <-ticker.C: 101 | util.Safe(func() { 102 | ds.updateServices() 103 | }) 104 | } 105 | } 106 | } 107 | 108 | // Stop . 109 | func (ds *Discovery) Stop() error { 110 | close(ds.done) 111 | return nil 112 | } 113 | 114 | // NewDiscovery . 115 | func NewDiscovery(addr string, serviceNamespace string, interval time.Duration, serviceManager micro.ServiceManager) (*Discovery, error) { 116 | client := redis.NewClient(&redis.Options{ 117 | Addr: addr, 118 | }) 119 | 120 | ds := &Discovery{ 121 | client: client, 122 | serviceNamespace: serviceNamespace, 123 | interval: interval, 124 | serviceManager: serviceManager, 125 | serviceCache: map[string]struct{}{}, 126 | done: make(chan struct{}), 127 | } 128 | 129 | err := ds.updateServices() 130 | if err != nil { 131 | return nil, err 132 | } 133 | go util.Safe(ds.update) 134 | 135 | log.Info("NewDiscovery [%v] success", serviceNamespace) 136 | 137 | return ds, nil 138 | } 139 | -------------------------------------------------------------------------------- /extension/micro/redis/register.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | redis "github.com/go-redis/redis/v8" 9 | "github.com/lesismal/arpc/log" 10 | "github.com/lesismal/arpc/util" 11 | ) 12 | 13 | //Register . 14 | type Register struct { 15 | serviceNamespace string 16 | serviceName string 17 | serviceAddr string 18 | weight int 19 | field string 20 | done chan struct{} 21 | interval time.Duration 22 | expire time.Duration 23 | client *redis.Client 24 | } 25 | 26 | // Stop . 27 | func (s *Register) keepalive() { 28 | ticker := time.NewTicker(s.interval) 29 | defer ticker.Stop() 30 | 31 | for { 32 | select { 33 | case <-s.done: 34 | return 35 | case <-ticker.C: 36 | s.client.HSet(context.Background(), s.serviceNamespace, s.field, time.Now().Add(s.expire).Unix()) 37 | } 38 | } 39 | } 40 | 41 | // Stop . 42 | func (s *Register) Stop() error { 43 | ret := s.client.HDel(context.Background(), s.serviceNamespace, s.field) 44 | err := ret.Err() 45 | if err != nil { 46 | log.Error("Register Stop failed: %v", err) 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | // NewRegister . 53 | func NewRegister(addr, serviceNamespace, serviceName, serviceAddr string, weight int, interval, expire time.Duration) (*Register, error) { 54 | if weight <= 0 { 55 | weight = 1 56 | } 57 | if interval <= 0 { 58 | interval = time.Second * 5 59 | } 60 | if expire <= interval { 61 | expire = interval + time.Second*5 62 | } 63 | 64 | client := redis.NewClient(&redis.Options{ 65 | Addr: addr, 66 | }) 67 | 68 | field := fmt.Sprintf("%v/%v/%v", serviceName, serviceAddr, weight) 69 | ret := client.HSetNX(context.Background(), serviceNamespace, field, time.Now().Add(expire).Unix()) 70 | err := ret.Err() 71 | if err != nil { 72 | log.Error("NewRegister [%v, %v] client.HSetNX failed: %v", serviceNamespace, fmt.Sprintf("%v/%v", serviceName, serviceAddr), err) 73 | return nil, err 74 | } 75 | 76 | register := &Register{ 77 | serviceNamespace: serviceNamespace, 78 | serviceName: serviceName, 79 | serviceAddr: serviceAddr, 80 | weight: weight, 81 | field: field, 82 | interval: interval, 83 | expire: expire, 84 | done: make(chan struct{}), 85 | client: client, 86 | } 87 | 88 | log.Info("NewRegister [%v: %v/%v] success", serviceNamespace, serviceName, serviceAddr) 89 | 90 | go util.Safe(register.keepalive) 91 | 92 | return register, nil 93 | } 94 | -------------------------------------------------------------------------------- /extension/micro/service.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "strconv" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/lesismal/arpc" 12 | "github.com/lesismal/arpc/log" 13 | "github.com/lesismal/arpc/util" 14 | ) 15 | 16 | var ( 17 | // ErrServiceNotFound . 18 | ErrServiceNotFound = errors.New("service not found") 19 | // ErrServiceUnreachable . 20 | ErrServiceUnreachable = errors.New("service unreachable") 21 | ) 22 | 23 | // ServiceManager . 24 | type ServiceManager interface { 25 | AddServiceNodes(path string, value string) 26 | DeleteServiceNodes(path string) 27 | ClientBy(serviceName string) (*arpc.Client, error) 28 | } 29 | 30 | // ServiceNode . 31 | type ServiceNode struct { 32 | name string 33 | addr string 34 | client *arpc.Client 35 | shutdown bool 36 | } 37 | 38 | type serviceNodeList struct { 39 | mux sync.RWMutex 40 | name string 41 | index uint64 42 | nodes []*ServiceNode 43 | } 44 | 45 | func (list *serviceNodeList) addByAddr(addr string, nodes []*ServiceNode) { 46 | list.mux.Lock() 47 | defer list.mux.Unlock() 48 | for _, v := range list.nodes { 49 | // addr already added 50 | if v.addr == addr { 51 | return 52 | } 53 | } 54 | list.nodes = append(list.nodes, nodes...) 55 | } 56 | 57 | func (list *serviceNodeList) update(node *ServiceNode) { 58 | var found = false 59 | list.mux.Lock() 60 | defer list.mux.Unlock() 61 | 62 | for _, v := range list.nodes { 63 | if v == node { 64 | found = true 65 | } 66 | } 67 | if !found { 68 | node.client.Stop() 69 | } 70 | } 71 | 72 | func (list *serviceNodeList) delete(addr string) { 73 | list.mux.Lock() 74 | defer list.mux.Unlock() 75 | l := len(list.nodes) 76 | for i := l - 1; i >= 0; i-- { 77 | node := list.nodes[i] 78 | if addr == node.addr { 79 | node.shutdown = true 80 | if node.client != nil { 81 | node.client.Stop() 82 | } 83 | list.nodes = append(list.nodes[:i], list.nodes[i+1:]...) 84 | } 85 | } 86 | } 87 | 88 | func (list *serviceNodeList) next() (*arpc.Client, error) { 89 | list.mux.RLock() 90 | defer list.mux.RUnlock() 91 | l := len(list.nodes) 92 | if l == 0 { 93 | return nil, ErrServiceNotFound 94 | } 95 | for i := 0; i < l; i++ { 96 | list.index++ 97 | node := list.nodes[list.index%uint64(len(list.nodes))] 98 | if node.client != nil && node.client.CheckState() == nil { 99 | return node.client, nil 100 | } 101 | } 102 | return nil, ErrServiceUnreachable 103 | } 104 | 105 | type serviceManager struct { 106 | mux sync.RWMutex 107 | dialer func(addr string) (net.Conn, error) 108 | serviceList map[string]*serviceNodeList 109 | } 110 | 111 | func (s *serviceManager) setServiceNode(name string, addr string, nodes []*ServiceNode) { 112 | s.mux.Lock() 113 | list, ok := s.serviceList[name] 114 | if !ok { 115 | list = &serviceNodeList{} 116 | s.serviceList[name] = list 117 | } 118 | s.mux.Unlock() 119 | list.addByAddr(addr, nodes) 120 | } 121 | 122 | func (s *serviceManager) updateServiceNode(name string, node *ServiceNode) { 123 | s.mux.Lock() 124 | list, ok := s.serviceList[name] 125 | s.mux.Unlock() 126 | if ok { 127 | list.update(node) 128 | } else { 129 | node.client.Stop() 130 | } 131 | } 132 | 133 | // AddServiceNodes add nodes by path's addr and weight/value, would be called by a Discovery when service was setted 134 | func (s *serviceManager) AddServiceNodes(path string, value string) { 135 | arr := strings.Split(path, "/") 136 | if len(arr) < 3 { 137 | return 138 | } 139 | 140 | app, name, addr := arr[0], arr[1], arr[2] 141 | weight := 1 142 | n, err := strconv.Atoi(value) 143 | if err == nil && n > 0 { 144 | weight = n 145 | } 146 | 147 | var nodes = make([]*ServiceNode, weight) 148 | for i := 0; i < weight; i++ { 149 | nodes[i] = &ServiceNode{name: name, addr: addr} 150 | } 151 | 152 | client, err := arpc.NewClient(func() (net.Conn, error) { 153 | return s.dialer(addr) 154 | }) 155 | for i := 0; i < weight; i++ { 156 | nodes[i].client = client 157 | s.setServiceNode(name, addr, nodes) 158 | } 159 | if err == nil { 160 | log.Info("AddServiceNodes: [%v, %v, %v, %v]", app, name, addr, weight) 161 | } else { 162 | log.Info("AddServiceNodes failed, retrying later: [%v, %v, %v, %v], %v", app, name, addr, weight, err) 163 | 164 | go util.Safe(func() { 165 | i := 0 166 | for !nodes[0].shutdown { 167 | i++ 168 | time.Sleep(time.Second) 169 | log.Info("AddServiceNodes: [%v, %v, %v, %v] retrying %v...", app, name, addr, weight, i) 170 | client, err := arpc.NewClient(func() (net.Conn, error) { 171 | return s.dialer(addr) 172 | }) 173 | if err == nil { 174 | time.Sleep(time.Second / 100) 175 | for i := 0; i < weight; i++ { 176 | nodes[i].client = client 177 | s.updateServiceNode(name, nodes[i]) 178 | } 179 | 180 | return 181 | } 182 | log.Info("AddServiceNodes [%v, %v, %v, %v] retrying %v failed: %v", app, name, addr, weight, i, err) 183 | } 184 | }) 185 | } 186 | } 187 | 188 | // DeleteServiceNodes deletes all nods for path's addr, would be called by a Discovery when service was setted. 189 | func (s *serviceManager) DeleteServiceNodes(path string) { 190 | arr := strings.Split(path, "/") 191 | if len(arr) < 3 { 192 | return 193 | } 194 | app, name, addr := arr[0], arr[1], arr[2] 195 | s.mux.RLock() 196 | defer s.mux.RUnlock() 197 | list, ok := s.serviceList[name] 198 | if ok { 199 | list.delete(addr) 200 | log.Info("DeleteServiceNodes: [%v, %v, %v]", app, name, addr) 201 | } 202 | } 203 | 204 | // ClientBy returns a reachable client by service's name. 205 | func (s *serviceManager) ClientBy(serviceName string) (*arpc.Client, error) { 206 | s.mux.RLock() 207 | defer s.mux.RUnlock() 208 | list, ok := s.serviceList[serviceName] 209 | if ok { 210 | return list.next() 211 | } 212 | return nil, ErrServiceNotFound 213 | } 214 | 215 | // NewServiceManager . 216 | func NewServiceManager(dialer func(addr string) (net.Conn, error)) ServiceManager { 217 | return &serviceManager{ 218 | dialer: dialer, 219 | serviceList: map[string]*serviceNodeList{}, 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /extension/middleware/coder/appender.go: -------------------------------------------------------------------------------- 1 | package coder 2 | 3 | import ( 4 | "github.com/lesismal/arpc" 5 | "github.com/lesismal/arpc/util" 6 | ) 7 | 8 | // Appender . 9 | type Appender struct { 10 | AppenderName string 11 | FlagBitIndex int 12 | valueToBytes func(interface{}) ([]byte, error) 13 | bytesToValue func([]byte) (interface{}, error) 14 | } 15 | 16 | // Encode implements arpc MessageCoder. 17 | func (ap *Appender) Encode(client *arpc.Client, msg *arpc.Message) *arpc.Message { 18 | if ap.AppenderName == "" || ap.valueToBytes == nil { 19 | return msg 20 | } 21 | key := ap.AppenderName 22 | value, ok := msg.Get(key) 23 | if !ok { 24 | return msg 25 | } 26 | if err := msg.SetFlagBit(ap.FlagBitIndex, true); err != nil { 27 | return msg 28 | } 29 | valueData, err := ap.valueToBytes(value) 30 | if err != nil { 31 | return msg 32 | } 33 | msg.Buffer = append(msg.Buffer, make([]byte, len(key)+len(valueData)+2)...) 34 | appendData := msg.Buffer[len(msg.Buffer)-len(key)-len(valueData)-2:] 35 | copy(appendData, key) 36 | copy(appendData[len(key):], valueData) 37 | appendLen := uint16(len(appendData)) 38 | appendData[appendLen-2], appendData[appendLen-1] = byte(appendLen>>8), byte(appendLen&0xFF) 39 | msg.SetBodyLen(len(msg.Buffer) - 16) 40 | return msg 41 | } 42 | 43 | // Decode implements arpc MessageCoder. 44 | func (ap *Appender) Decode(client *arpc.Client, msg *arpc.Message) *arpc.Message { 45 | if msg.IsFlagBitSet(ap.FlagBitIndex) { 46 | bufLen := len(msg.Buffer) 47 | if bufLen > 2 && ap.bytesToValue != nil { 48 | key := ap.AppenderName 49 | appendLen := (int(msg.Buffer[bufLen-2]) << 8) | int(msg.Buffer[bufLen-1]) 50 | if bufLen >= appendLen { 51 | appenderName := util.BytesToStr(msg.Buffer[bufLen-appendLen : bufLen-appendLen+len(key)]) 52 | if appenderName != key { 53 | return msg 54 | } 55 | payloadBody := msg.Buffer[bufLen-appendLen+len(appenderName) : bufLen-2] 56 | if value, err := ap.bytesToValue(payloadBody); err == nil { 57 | msg.Set(key, value) 58 | } 59 | } 60 | msg.Buffer = msg.Buffer[:len(msg.Buffer)-appendLen] 61 | msg.SetFlagBit(ap.FlagBitIndex, false) 62 | msg.SetBodyLen(len(msg.Buffer) - 16) 63 | } 64 | } 65 | return msg 66 | } 67 | 68 | // NewAppender returns the trace coding middleware. 69 | func NewAppender(appenderName string, 70 | flagBitIndex int, 71 | valueToBytes func(interface{}) ([]byte, error), 72 | bytesToValue func([]byte) (interface{}, error)) *Appender { 73 | return &Appender{ 74 | AppenderName: appenderName, 75 | FlagBitIndex: flagBitIndex, 76 | valueToBytes: valueToBytes, 77 | bytesToValue: bytesToValue, 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /extension/middleware/coder/config.go: -------------------------------------------------------------------------------- 1 | package coder 2 | 3 | const ( 4 | // FlagBitOpenTracing . 5 | FlagBitOpenTracing = 0 6 | // FlagBitGZip . 7 | FlagBitGZip = 7 8 | ) 9 | -------------------------------------------------------------------------------- /extension/middleware/coder/gzip/gzip.go: -------------------------------------------------------------------------------- 1 | package gzip 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "io/ioutil" 7 | 8 | "github.com/lesismal/arpc" 9 | "github.com/lesismal/arpc/extension/middleware/coder" 10 | ) 11 | 12 | func gzipCompress(data []byte) []byte { 13 | var in bytes.Buffer 14 | w := gzip.NewWriter(&in) 15 | w.Write(data) 16 | w.Close() 17 | return in.Bytes() 18 | } 19 | 20 | func gzipUnCompress(data []byte) ([]byte, error) { 21 | b := bytes.NewReader(data) 22 | r, err := gzip.NewReader(b) 23 | if err != nil { 24 | return nil, err 25 | } 26 | defer r.Close() 27 | undatas, err := ioutil.ReadAll(r) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return undatas, nil 32 | } 33 | 34 | // Gzip represents a gzip coding middleware. 35 | type Gzip int 36 | 37 | // Encode implements arpc MessageCoder. 38 | func (g *Gzip) Encode(client *arpc.Client, msg *arpc.Message) *arpc.Message { 39 | if len(msg.Buffer) > int(*g) && !msg.IsFlagBitSet(coder.FlagBitGZip) { 40 | buf := gzipCompress(msg.Buffer[arpc.HeaderIndexReserved+1:]) 41 | total := len(buf) + arpc.HeaderIndexReserved + 1 42 | if total < len(msg.Buffer) { 43 | copy(msg.Buffer[arpc.HeaderIndexReserved+1:], buf) 44 | msg.Buffer = msg.Buffer[:total] 45 | msg.SetBodyLen(total - 16) 46 | msg.SetFlagBit(coder.FlagBitGZip, true) 47 | } 48 | } 49 | return msg 50 | } 51 | 52 | // Decode implements arpc MessageCoder. 53 | func (g *Gzip) Decode(client *arpc.Client, msg *arpc.Message) *arpc.Message { 54 | if msg.IsFlagBitSet(coder.FlagBitGZip) { 55 | buf, err := gzipUnCompress(msg.Buffer[arpc.HeaderIndexReserved+1:]) 56 | if err == nil { 57 | msg.Buffer = append(msg.Buffer[:arpc.HeaderIndexReserved+1], buf...) 58 | msg.SetFlagBit(coder.FlagBitGZip, false) 59 | msg.SetBodyLen(len(msg.Buffer) - 16) 60 | } 61 | } 62 | return msg 63 | } 64 | 65 | // New returns the gzip coding middleware. 66 | func New(n int) *Gzip { 67 | var g = Gzip(n) 68 | return &g 69 | } 70 | -------------------------------------------------------------------------------- /extension/middleware/coder/msgpack/msgpack.go: -------------------------------------------------------------------------------- 1 | package msgpack 2 | 3 | import ( 4 | "github.com/lesismal/arpc" 5 | "github.com/vmihailenco/msgpack" 6 | ) 7 | 8 | type mpkv struct { 9 | Body []byte 10 | Values map[interface{}]interface{} 11 | } 12 | 13 | // MsgPack represents a gzip coding middleware. 14 | type MsgPack int 15 | 16 | // Encode implements arpc MessageCoder. 17 | func (mp *MsgPack) Encode(client *arpc.Client, msg *arpc.Message) *arpc.Message { 18 | body := msg.Data() 19 | v := &mpkv{ 20 | Body: body, 21 | Values: msg.Values(), 22 | } 23 | data, err := msgpack.Marshal(&v) 24 | if err == nil { 25 | ml := msg.MethodLen() 26 | msg.Buffer = append(msg.Buffer[:arpc.HeadLen+ml], data...) 27 | msg.SetBodyLen(ml + len(data)) 28 | } else { 29 | // some error log 30 | } 31 | return msg 32 | } 33 | 34 | // Decode implements arpc MessageCoder. 35 | func (mp *MsgPack) Decode(client *arpc.Client, msg *arpc.Message) *arpc.Message { 36 | v := &mpkv{ 37 | Body: msg.Data(), 38 | Values: msg.Values(), 39 | } 40 | err := msgpack.Unmarshal(msg.Data(), v) 41 | if err == nil { 42 | ml := msg.MethodLen() 43 | msg.Buffer = append(msg.Buffer[:arpc.HeadLen+ml], v.Body...) 44 | msg.SetBodyLen(ml + len(v.Body)) 45 | for k, v := range v.Values { 46 | msg.Set(k, v) 47 | } 48 | } else { 49 | // some error log 50 | } 51 | return msg 52 | } 53 | 54 | // New returns the MsgPack coding middleware. 55 | func New() *MsgPack { 56 | return new(MsgPack) 57 | } 58 | -------------------------------------------------------------------------------- /extension/middleware/coder/tracing/reporter.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/opentracing/basictracer-go" 7 | ) 8 | 9 | type defaultReporter struct{} 10 | 11 | // RecordSpan complies with the basictracer.Recorder interface. 12 | func (r *defaultReporter) RecordSpan(span basictracer.RawSpan) { 13 | fmt.Printf( 14 | "RecordSpan: %v[%v, %v us] --> %v logs. context: %v; baggage: %v\n", 15 | span.Operation, span.Start, span.Duration, len(span.Logs), 16 | span.Context, span.Context.Baggage) 17 | for i, l := range span.Logs { 18 | fmt.Printf( 19 | " log %v @ %v: %v\n", i, l.Timestamp, l.Fields) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /extension/middleware/coder/tracing/tracer.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "github.com/gogo/protobuf/proto" 5 | "github.com/lesismal/arpc" 6 | "github.com/lesismal/arpc/extension/middleware/coder" 7 | "github.com/opentracing/basictracer-go" 8 | "github.com/opentracing/basictracer-go/wire" 9 | opentracing "github.com/opentracing/opentracing-go" 10 | ) 11 | 12 | const ( 13 | appenderName = "arpc-opentracing" 14 | ) 15 | 16 | // Tracer . 17 | type Tracer struct { 18 | opentracing.Tracer 19 | *coder.Appender 20 | } 21 | 22 | // Inject . 23 | func (t *Tracer) Inject(sc opentracing.SpanContext, format interface{}, carrier interface{}) error { 24 | return t.Tracer.Inject(sc, format, carrier) 25 | } 26 | 27 | // Extract . 28 | func (t *Tracer) Extract(format interface{}, opaqueCarrier interface{}) (opentracing.SpanContext, error) { 29 | if ac, ok := opaqueCarrier.(*arpc.Context); ok { 30 | ispanCtx, ok := ac.Get(appenderName) 31 | if ok { 32 | spanCtx, ok := ispanCtx.(opentracing.SpanContext) 33 | if ok { 34 | return spanCtx, nil 35 | } 36 | } 37 | return nil, opentracing.ErrInvalidSpanContext 38 | } 39 | return t.Tracer.Extract(format, opaqueCarrier) 40 | } 41 | 42 | // ValueToBytes . 43 | func (t *Tracer) ValueToBytes(value interface{}) ([]byte, error) { 44 | sc, ok := value.(basictracer.SpanContext) 45 | if !ok { 46 | return nil, opentracing.ErrInvalidSpanContext 47 | } 48 | 49 | state := wire.TracerState{ 50 | TraceId: sc.TraceID, 51 | SpanId: sc.SpanID, 52 | Sampled: sc.Sampled, 53 | BaggageItems: sc.Baggage, 54 | } 55 | bytes, err := proto.Marshal(&state) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return bytes, nil 61 | } 62 | 63 | // BytesToValue . 64 | func (t *Tracer) BytesToValue(bytes []byte) (interface{}, error) { 65 | ctx := wire.TracerState{} 66 | if err := proto.Unmarshal(bytes, &ctx); err != nil { 67 | return nil, opentracing.ErrSpanContextCorrupted 68 | } 69 | sc := basictracer.SpanContext{ 70 | TraceID: ctx.TraceId, 71 | SpanID: ctx.SpanId, 72 | Sampled: ctx.Sampled, 73 | Baggage: ctx.BaggageItems, 74 | } 75 | return sc, nil 76 | } 77 | 78 | // NewTracer . 79 | func NewTracer(recorder basictracer.SpanRecorder) *Tracer { 80 | if recorder == nil { 81 | recorder = &defaultReporter{} 82 | } 83 | t := &Tracer{} 84 | t.Tracer = basictracer.New(recorder) 85 | t.Appender = coder.NewAppender(appenderName, coder.FlagBitOpenTracing, t.ValueToBytes, t.BytesToValue) 86 | return t 87 | } 88 | -------------------------------------------------------------------------------- /extension/middleware/coder/tracing/values.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | // Values converts user data to arpc.Message's values 4 | func Values(value interface{}) map[interface{}]interface{} { 5 | return map[interface{}]interface{}{ 6 | appenderName: value, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /extension/middleware/router/graceful.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "time" 7 | 8 | "github.com/lesismal/arpc" 9 | ) 10 | 11 | // ErrShutdown . 12 | var ErrShutdown = errors.New("shutting down") 13 | 14 | // Graceful represents a graceful middleware instance. 15 | type Graceful struct { 16 | shutdown bool 17 | gracefulWg sync.WaitGroup 18 | } 19 | 20 | // Handler returns the graceful middleware handler. 21 | func (g *Graceful) Handler() arpc.HandlerFunc { 22 | return func(ctx *arpc.Context) { 23 | if !g.shutdown { 24 | g.gracefulWg.Add(1) 25 | defer g.gracefulWg.Done() 26 | ctx.Next() 27 | } else { 28 | ctx.Error(ErrShutdown) 29 | } 30 | } 31 | } 32 | 33 | // Shutdown stops handling new requests and waits for all current requests to be processed. 34 | func (g *Graceful) Shutdown() { 35 | g.shutdown = true 36 | g.gracefulWg.Wait() 37 | time.Sleep(time.Second / 10) 38 | } 39 | -------------------------------------------------------------------------------- /extension/middleware/router/logger.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lesismal/arpc" 7 | "github.com/lesismal/arpc/log" 8 | ) 9 | 10 | var ( 11 | cmdName = map[byte]string{ 12 | arpc.CmdRequest: "request", 13 | arpc.CmdNotify: "notify", 14 | } 15 | ) 16 | 17 | // Logger returns the logger middleware. 18 | func Logger() arpc.HandlerFunc { 19 | return func(ctx *arpc.Context) { 20 | t := time.Now() 21 | 22 | ctx.Next() 23 | 24 | cmd := ctx.Message.Cmd() 25 | method := ctx.Message.Method() 26 | addr := ctx.Client.Conn.RemoteAddr() 27 | cost := time.Since(t).Milliseconds() 28 | 29 | switch cmd { 30 | case arpc.CmdRequest, arpc.CmdNotify: 31 | err := ctx.ResponseError() 32 | if err == nil { 33 | log.Info("[%v | %v] from %v success, %v ms cost", cmdName[cmd], method, addr, cost) 34 | } else { 35 | log.Info("[%v | %v], from %v error: [%v], %v ms cost", cmdName[cmd], method, addr, err, cost) 36 | } 37 | default: 38 | log.Error("invalid cmd: %d,\tdropped", cmd) 39 | ctx.Done() 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /extension/middleware/router/recover.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/lesismal/arpc" 5 | "github.com/lesismal/arpc/util" 6 | ) 7 | 8 | // Recover returns the recovery middleware handler. 9 | func Recover() arpc.HandlerFunc { 10 | return func(ctx *arpc.Context) { 11 | defer util.Recover() 12 | ctx.Next() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /extension/protocol/quic/quic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package quic 6 | 7 | import ( 8 | "context" 9 | "crypto/tls" 10 | "net" 11 | "time" 12 | 13 | quic "github.com/quic-go/quic-go" 14 | ) 15 | 16 | // Listener wraps quick.Listener to net.Listener 17 | type Listener struct { 18 | *quic.Listener 19 | } 20 | 21 | // Accept waits for and returns the next connection to the listener. 22 | func (ln *Listener) Accept() (net.Conn, error) { 23 | conn, err := ln.Listener.Accept(context.Background()) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | stream, err := conn.AcceptStream(context.Background()) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return &Conn{conn, stream}, err 34 | } 35 | 36 | // Conn wraps quick.Session to net.Conn 37 | type Conn struct { 38 | quic.Connection 39 | quic.Stream 40 | } 41 | 42 | // Listen wraps quic listen 43 | func Listen(addr string, config *tls.Config) (net.Listener, error) { 44 | ln, err := quic.ListenAddr(addr, config, nil) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return &Listener{ln}, err 49 | } 50 | 51 | // Dial wraps quic dial 52 | func Dial(addr string, tlsConf *tls.Config, quicConf *quic.Config, timeout time.Duration) (net.Conn, error) { 53 | var ( 54 | ctx = context.Background() 55 | cancel func() 56 | ) 57 | if timeout > 0 { 58 | ctx, cancel = context.WithTimeout(context.Background(), timeout) 59 | defer cancel() 60 | } 61 | 62 | session, err := quic.DialAddr(ctx, addr, tlsConf, quicConf) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | stream, err := session.OpenStreamSync(ctx) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return &Conn{session, stream}, err 73 | } 74 | -------------------------------------------------------------------------------- /extension/protocol/quic/quic_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package quic 6 | 7 | import ( 8 | "crypto/rand" 9 | "crypto/rsa" 10 | "crypto/tls" 11 | "crypto/x509" 12 | "encoding/pem" 13 | "io/ioutil" 14 | "math/big" 15 | "testing" 16 | ) 17 | 18 | func TestAll(t *testing.T) { 19 | addr := "localhost:15678" 20 | ln, err := Listen(addr, generateTLSConfig()) 21 | if err != nil { 22 | t.Fatalf("failed to listen: %v", err) 23 | } 24 | defer ln.Close() 25 | 26 | go func() { 27 | conn, err := ln.Accept() 28 | if err != nil { 29 | t.Fatalf("failed to listen: %v", err) 30 | } 31 | defer conn.Close() 32 | 33 | buf := make([]byte, 1024) 34 | nread, err := conn.Read(buf) 35 | if err != nil { 36 | t.Fatalf("failed to listen: %v", err) 37 | } 38 | conn.Write(buf[:nread]) 39 | }() 40 | 41 | tlsConf := &tls.Config{ 42 | InsecureSkipVerify: true, 43 | NextProtos: []string{"quic-echo-example"}, 44 | } 45 | conn, err := Dial(addr, tlsConf, nil, 0) 46 | if err != nil { 47 | t.Fatalf("failed to listen: %v", err) 48 | } 49 | defer conn.Close() 50 | str := "hello" 51 | nwrite, err := conn.Write([]byte(str)) 52 | if err != nil || nwrite != len(str) { 53 | t.Fatalf("failed to listen: %v", err) 54 | } 55 | buf, err := ioutil.ReadAll(conn) 56 | if err != nil || string(buf) != str { 57 | t.Fatalf("failed to listen: %v", err) 58 | } 59 | } 60 | 61 | // Setup a bare-bones TLS config for the server 62 | func generateTLSConfig() *tls.Config { 63 | key, err := rsa.GenerateKey(rand.Reader, 1024) 64 | if err != nil { 65 | panic(err) 66 | } 67 | template := x509.Certificate{SerialNumber: big.NewInt(1)} 68 | certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) 69 | if err != nil { 70 | panic(err) 71 | } 72 | keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) 73 | certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) 74 | 75 | tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) 76 | if err != nil { 77 | panic(err) 78 | } 79 | return &tls.Config{ 80 | Certificates: []tls.Certificate{tlsCert}, 81 | NextProtos: []string{"quic-echo-example"}, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /extension/protocol/websocket/websocket.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "errors" 9 | "net" 10 | "net/http" 11 | "sync/atomic" 12 | "time" 13 | 14 | "github.com/gorilla/websocket" 15 | ) 16 | 17 | var ( 18 | // ErrClosed . 19 | ErrClosed = errors.New("websocket listener closed") 20 | // ErrInvalidMessage . 21 | ErrInvalidMessage = errors.New("invalid message") 22 | // ErrInvalidMessageType . 23 | ErrInvalidMessageType = errors.New("invalid message type") 24 | ) 25 | 26 | // Listener . 27 | type Listener struct { 28 | addr net.Addr 29 | upgrader *websocket.Upgrader 30 | chAccept chan net.Conn 31 | chClose chan struct{} 32 | closed uint32 33 | } 34 | 35 | // Handler . 36 | func (ln *Listener) Handler(w http.ResponseWriter, r *http.Request) { 37 | c, err := ln.upgrader.Upgrade(w, r, nil) 38 | if err != nil { 39 | w.WriteHeader(http.StatusForbidden) 40 | return 41 | } 42 | 43 | wsc := &Conn{Conn: c, chHandler: make(chan func(), 1)} 44 | select { 45 | case ln.chAccept <- wsc: 46 | case <-ln.chClose: 47 | c.Close() 48 | } 49 | } 50 | 51 | // Close . 52 | func (ln *Listener) Close() error { 53 | if atomic.CompareAndSwapUint32(&ln.closed, 0, 1) { 54 | close(ln.chClose) 55 | } 56 | return nil 57 | } 58 | 59 | // Addr . 60 | func (ln *Listener) Addr() net.Addr { 61 | return ln.addr 62 | } 63 | 64 | // Accept . 65 | func (ln *Listener) Accept() (net.Conn, error) { 66 | select { 67 | case c := <-ln.chAccept: 68 | return c, nil 69 | case <-ln.chClose: 70 | } 71 | return nil, ErrClosed 72 | } 73 | 74 | // Conn wraps websocket.Conn to net.Conn 75 | type Conn struct { 76 | *websocket.Conn 77 | chHandler chan func() 78 | buffer []byte 79 | } 80 | 81 | // HandleWebsocket . 82 | func (c *Conn) HandleWebsocket(handler func()) { 83 | select { 84 | case c.chHandler <- handler: 85 | default: 86 | } 87 | } 88 | 89 | // Read . 90 | func (c *Conn) Read(b []byte) (int, error) { 91 | var ( 92 | err error 93 | ) 94 | if len(c.buffer) == 0 { 95 | _, c.buffer, err = c.ReadMessage() 96 | if err != nil { 97 | return 0, err 98 | } 99 | } 100 | 101 | cbl := len(c.buffer) 102 | if cbl <= len(b) { 103 | copy(b[:cbl], c.buffer) 104 | c.buffer = nil 105 | return cbl, nil 106 | } 107 | copy(b, c.buffer[:len(b)]) 108 | c.buffer = c.buffer[len(b):] 109 | return len(b), nil 110 | } 111 | 112 | // Write . 113 | func (c *Conn) Write(b []byte) (int, error) { 114 | err := c.WriteMessage(websocket.BinaryMessage, b) 115 | if err == nil { 116 | return len(b), nil 117 | } 118 | return 0, err 119 | } 120 | 121 | // SetDeadline . 122 | func (c *Conn) SetDeadline(t time.Time) error { 123 | err := c.SetReadDeadline(t) 124 | if err != nil { 125 | return err 126 | } 127 | return c.SetWriteDeadline(t) 128 | } 129 | 130 | // Listen wraps websocket listen 131 | func Listen(addr string, upgrader *websocket.Upgrader) (net.Listener, error) { 132 | tcpAddr, err := net.ResolveTCPAddr("tcp", addr) 133 | if err != nil { 134 | return nil, err 135 | } 136 | if upgrader == nil { 137 | upgrader = &websocket.Upgrader{ 138 | CheckOrigin: func(r *http.Request) bool { 139 | return true 140 | }, 141 | } 142 | } 143 | ln := &Listener{ 144 | addr: tcpAddr, 145 | upgrader: upgrader, 146 | chAccept: make(chan net.Conn, 4096), 147 | chClose: make(chan struct{}), 148 | } 149 | return ln, nil 150 | } 151 | 152 | // Dial wraps websocket dial 153 | func Dial(url string, args ...interface{}) (net.Conn, error) { 154 | dialer := websocket.DefaultDialer 155 | if len(args) > 0 { 156 | d, ok := args[0].(*websocket.Dialer) 157 | if ok { 158 | dialer = d 159 | } 160 | } 161 | c, _, err := dialer.Dial(url, nil) 162 | if err != nil { 163 | return nil, err 164 | } 165 | return &Conn{Conn: c}, nil 166 | } 167 | -------------------------------------------------------------------------------- /extension/protocol/websocket/websocket_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "log" 9 | "net/http" 10 | "testing" 11 | ) 12 | 13 | func TestAll(t *testing.T) { 14 | ln, _ := Listen("localhost:8888", nil) 15 | http.HandleFunc("/ws", ln.(*Listener).Handler) 16 | go func() { 17 | err := http.ListenAndServe("localhost:8888", nil) 18 | if err != nil { 19 | log.Fatal("ListenAndServe: ", err) 20 | } 21 | }() 22 | 23 | str := "hello" 24 | 25 | go func() { 26 | conn, err := ln.Accept() 27 | if err != nil { 28 | log.Fatalf("failed to listen: %v", err) 29 | } 30 | defer conn.Close() 31 | 32 | bread := make([]byte, len(str)) 33 | _, err = conn.Read(bread) 34 | if err != nil { 35 | log.Fatalf("failed to listen: %v", err) 36 | } 37 | conn.Write(bread) 38 | }() 39 | 40 | conn, err := Dial("ws://localhost:8888/ws") 41 | if err != nil { 42 | t.Fatalf("failed to listen: %v", err) 43 | } 44 | defer conn.Close() 45 | bread := make([]byte, len(str)) 46 | nwrite, err := conn.Write([]byte(str)) 47 | if err != nil || nwrite != len(str) { 48 | t.Fatalf("failed to listen: %v", err) 49 | } 50 | _, err = conn.Read(bread) 51 | if err != nil || string(bread) != str { 52 | t.Fatalf("failed to listen: %v", err) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /extension/pubsub/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pubsub 6 | 7 | import ( 8 | "net" 9 | "sync" 10 | "time" 11 | 12 | "github.com/lesismal/arpc" 13 | "github.com/lesismal/arpc/log" 14 | "github.com/lesismal/arpc/util" 15 | ) 16 | 17 | // Client . 18 | type Client struct { 19 | *arpc.Client 20 | 21 | Password string 22 | 23 | psmux sync.Mutex 24 | 25 | topicHandlerMap map[string]TopicHandler 26 | 27 | onPublishHandler TopicHandler 28 | } 29 | 30 | // Authenticate . 31 | func (c *Client) Authenticate() error { 32 | // if c.Password == "" { 33 | // return nil 34 | // } 35 | err := c.Call(routeAuthenticate, c.Password, nil, time.Second*5) 36 | if err == nil { 37 | log.Info("%v [Authenticate] success from\t%v", c.Handler.LogTag(), c.Conn.RemoteAddr()) 38 | } else { 39 | log.Error("%v [Authenticate] failed: %v, from\t%v", c.Handler.LogTag(), err, c.Conn.RemoteAddr()) 40 | } 41 | return err 42 | } 43 | 44 | // Subscribe . 45 | func (c *Client) Subscribe(topicName string, h TopicHandler, timeout time.Duration) error { 46 | topic, err := newTopic(topicName, nil) 47 | if err != nil { 48 | return err 49 | } 50 | bs, err := topic.toBytes() 51 | if err != nil { 52 | return err 53 | } 54 | 55 | c.psmux.Lock() 56 | // if _, ok := c.topicHandlerMap[topicName]; ok { 57 | // panic(fmt.Errorf("handler exist for topic [%v]", topicName)) 58 | // } 59 | c.topicHandlerMap[topicName] = h 60 | c.psmux.Unlock() 61 | 62 | err = c.Call(routeSubscribe, bs, nil, timeout) 63 | if err == nil { 64 | log.Info("%v [Subscribe] [topic: '%v'] success from\t%v", c.Handler.LogTag(), topicName, c.Conn.RemoteAddr()) 65 | } else { 66 | c.psmux.Lock() 67 | delete(c.topicHandlerMap, topicName) 68 | c.psmux.Unlock() 69 | log.Error("%v [Subscribe] [topic: '%v'] failed: %v, from\t%v", c.Handler.LogTag(), topicName, err, c.Conn.RemoteAddr()) 70 | } 71 | return err 72 | } 73 | 74 | // Unsubscribe . 75 | func (c *Client) Unsubscribe(topicName string, timeout time.Duration) error { 76 | topic, err := newTopic(topicName, nil) 77 | if err != nil { 78 | return err 79 | } 80 | bs, err := topic.toBytes() 81 | if err != nil { 82 | return err 83 | } 84 | err = c.Call(routeUnsubscribe, bs, nil, timeout) 85 | if err == nil { 86 | c.psmux.Lock() 87 | delete(c.topicHandlerMap, topic.Name) 88 | c.psmux.Unlock() 89 | log.Info("%v[Unsubscribe] [topic: '%v'] success from\t%v", c.Handler.LogTag(), topicName, c.Conn.RemoteAddr()) 90 | } else { 91 | log.Error("%v[Unsubscribe] [topic: '%v'] failed: %v, from\t%v", c.Handler.LogTag(), topicName, err, c.Conn.RemoteAddr()) 92 | } 93 | return err 94 | } 95 | 96 | // Publish . 97 | func (c *Client) Publish(topicName string, v interface{}, timeout time.Duration) error { 98 | topic, err := newTopic(topicName, util.ValueToBytes(c.Codec, v)) 99 | if err != nil { 100 | return err 101 | } 102 | bs, err := topic.toBytes() 103 | if err != nil { 104 | return err 105 | } 106 | 107 | err = c.Call(routePublish, bs, nil, timeout) 108 | if err != nil { 109 | log.Error("%v [Publish] [topic: '%v'] failed: %v, from\t%v", c.Handler.LogTag(), topicName, err, c.Conn.RemoteAddr()) 110 | } 111 | return err 112 | } 113 | 114 | // PublishToOne . 115 | func (c *Client) PublishToOne(topicName string, v interface{}, timeout time.Duration) error { 116 | topic, err := newTopic(topicName, util.ValueToBytes(c.Codec, v)) 117 | if err != nil { 118 | return err 119 | } 120 | bs, err := topic.toBytes() 121 | if err != nil { 122 | return err 123 | } 124 | 125 | err = c.Call(routePublishToOne, bs, nil, timeout) 126 | if err != nil { 127 | log.Error("%v [PublishToOne] [topic: '%v'] failed: %v, from\t%v", c.Handler.LogTag(), topicName, err, c.Conn.RemoteAddr()) 128 | } 129 | return err 130 | } 131 | 132 | // OnPublish . 133 | func (c *Client) OnPublish(h TopicHandler) { 134 | c.onPublishHandler = h 135 | } 136 | 137 | // func (c *Client) invalidTopic(topic string) bool { 138 | // c.psmux.Lock() 139 | // _, ok := c.topicHandlerMap[topic] 140 | // c.psmux.Unlock() 141 | 142 | // return !ok 143 | // } 144 | 145 | func (c *Client) initTopics() { 146 | c.psmux.Lock() 147 | for name := range c.topicHandlerMap { 148 | topicName := name 149 | go util.Safe(func() { 150 | for i := 0; i < 10; i++ { 151 | topic, _ := newTopic(topicName, util.ValueToBytes(c.Codec, nil)) 152 | bs, _ := topic.toBytes() 153 | err := c.Call(routeSubscribe, bs, nil, time.Second*10) 154 | if err == nil { 155 | log.Info("%v [Subscribe] [topic: '%v'] success from\t%v", c.Handler.LogTag(), topicName, c.Conn.RemoteAddr()) 156 | break 157 | } else { 158 | log.Error("%v [Subscribe] [topic: '%v'] %v times failed: %v, from\t%v", c.Handler.LogTag(), topicName, i+1, err, c.Conn.RemoteAddr()) 159 | } 160 | time.Sleep(time.Second) 161 | } 162 | }) 163 | } 164 | c.psmux.Unlock() 165 | } 166 | 167 | func (c *Client) onPublish(ctx *arpc.Context) { 168 | defer util.Recover() 169 | 170 | topic := &Topic{} 171 | msg := ctx.Message 172 | if msg.IsError() { 173 | log.Error("%v [Publish IN] failed [%v], to\t%v", c.Handler.LogTag(), msg.Error(), ctx.Client.Conn.RemoteAddr()) 174 | return 175 | } 176 | err := topic.fromBytes(ctx.Body()) 177 | if err != nil { 178 | log.Error("%v [Publish IN] failed [%v], to\t%v", c.Handler.LogTag(), err, ctx.Client.Conn.RemoteAddr()) 179 | return 180 | } 181 | 182 | if c.onPublishHandler == nil { 183 | c.psmux.Lock() 184 | if h, ok := c.topicHandlerMap[topic.Name]; ok { 185 | h(topic) 186 | c.psmux.Unlock() 187 | } else { 188 | c.psmux.Unlock() 189 | } 190 | } else { 191 | c.onPublishHandler(topic) 192 | } 193 | } 194 | 195 | // NewClient . 196 | func NewClient(dialer func() (net.Conn, error), args ...interface{}) (*Client, error) { 197 | var handler arpc.Handler 198 | if len(args) > 0 { 199 | if h, ok := args[0].(arpc.Handler); ok { 200 | handler = h.Clone() 201 | } 202 | } 203 | 204 | c, err := arpc.NewClient(dialer, handler) 205 | if err != nil { 206 | return nil, err 207 | } 208 | c.Handler.SetLogTag("[APS CLI]") 209 | cli := &Client{ 210 | Client: c, 211 | topicHandlerMap: map[string]TopicHandler{}, 212 | } 213 | // cli.Handler = cli.Handler.Clone() 214 | cli.Handler.Handle(routePublish, cli.onPublish) 215 | cli.Handler.HandleConnected(func(c *arpc.Client) { 216 | if cli.Authenticate() == nil { 217 | cli.initTopics() 218 | } 219 | }) 220 | return cli, nil 221 | } 222 | -------------------------------------------------------------------------------- /extension/pubsub/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pubsub 6 | 7 | import "errors" 8 | 9 | var ( 10 | // ErrInvalidPassword . 11 | ErrInvalidPassword = errors.New("invalid password") 12 | 13 | // ErrInvalidTopicEmpty . 14 | ErrInvalidTopicEmpty = errors.New("invalid topic, should not be \"\"") 15 | 16 | // ErrInvalidTopicBytes . 17 | ErrInvalidTopicBytes = errors.New("invalid topic bytes, should be more than 10 bytes") 18 | 19 | // ErrInvalidTopicNameLength . 20 | ErrInvalidTopicNameLength = errors.New("invalid topic name length, should not be more than 1024") 21 | ) 22 | -------------------------------------------------------------------------------- /extension/pubsub/pubsub_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pubsub 6 | 7 | import ( 8 | "fmt" 9 | "net" 10 | "testing" 11 | "time" 12 | 13 | "github.com/lesismal/arpc/log" 14 | ) 15 | 16 | func TestPubSub(t *testing.T) { 17 | var ( 18 | address = "localhost:8888" 19 | password = "123qwe" 20 | topicName = "test" 21 | chDone = make(chan int) 22 | ) 23 | 24 | go func() { 25 | s := NewServer() 26 | s.Password = password 27 | s.Run(address) 28 | }() 29 | 30 | go func() { 31 | time.Sleep(time.Second / 2) 32 | client := newClient(t, address, password) 33 | consumer(client, topicName, chDone) 34 | }() 35 | 36 | go func() { 37 | time.Sleep(time.Second / 2) 38 | client := newClient(t, address, password) 39 | producer(client, topicName, chDone) 40 | }() 41 | 42 | <-chDone 43 | } 44 | 45 | func newClient(t *testing.T, address, password string) *Client { 46 | client, err := NewClient(func() (net.Conn, error) { 47 | return net.DialTimeout("tcp", address, time.Second*3) 48 | }) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | client.Password = password 53 | 54 | // authentication 55 | err = client.Authenticate() 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | return client 61 | } 62 | 63 | func consumer(c *Client, topicName string, chDone chan int) { 64 | cnt := 0 65 | err := c.Subscribe(topicName, func(topic *Topic) { 66 | cnt++ 67 | log.Info("[OnTopic] [%v] \"%v\" %v", topic.Name, string(topic.Data), time.Unix(topic.Timestamp, 0).Format("15:04:05")) 68 | if cnt >= 3 { 69 | close(chDone) 70 | } 71 | }, time.Second) 72 | 73 | if err != nil { 74 | panic(err) 75 | } 76 | } 77 | 78 | func producer(c *Client, topicName string, chDone chan int) { 79 | ticker := time.NewTicker(time.Second) 80 | for i := 0; true; i++ { 81 | select { 82 | case <-ticker.C: 83 | c.Publish(topicName, fmt.Sprintf("message %d", i), time.Second) 84 | case <-chDone: 85 | return 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /extension/pubsub/router.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pubsub 6 | 7 | const ( 8 | routeAuthenticate = "in_A" 9 | routeSubscribe = "in_S" 10 | routeUnsubscribe = "in_U" 11 | routePublish = "in_P" 12 | routePublishToOne = "in_P1" 13 | ) 14 | -------------------------------------------------------------------------------- /extension/pubsub/topic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pubsub 6 | 7 | import ( 8 | "encoding/binary" 9 | "sync" 10 | "time" 11 | 12 | "github.com/lesismal/arpc" 13 | "github.com/lesismal/arpc/log" 14 | "github.com/lesismal/arpc/util" 15 | ) 16 | 17 | const ( 18 | // MaxTopicNameLen . 19 | MaxTopicNameLen = 1024 20 | ) 21 | 22 | // TopicHandler . 23 | type TopicHandler func(tp *Topic) 24 | 25 | // Topic . 26 | type Topic struct { 27 | Name string 28 | Data []byte 29 | Timestamp int64 30 | raw []byte 31 | } 32 | 33 | func (tp *Topic) toBytes() ([]byte, error) { 34 | nameLen := uint16(len(tp.Name)) 35 | tail := make([]byte, len(tp.Name)+10) 36 | copy(tail, tp.Name) 37 | binary.LittleEndian.PutUint16(tail[nameLen:], nameLen) 38 | binary.LittleEndian.PutUint64(tail[nameLen+2:], uint64(tp.Timestamp)) 39 | dataLen := len(tp.Data) 40 | tp.Data = append(tp.Data, tail...) 41 | tp.raw = tp.Data 42 | tp.Data = tp.raw[:dataLen] 43 | return tp.raw, nil 44 | } 45 | 46 | func (tp *Topic) fromBytes(data []byte) error { 47 | if len(data) < 10 { 48 | return ErrInvalidTopicBytes 49 | } 50 | nameLen := int(binary.LittleEndian.Uint16(data[len(data)-10:])) 51 | if nameLen == 0 || nameLen > MaxTopicNameLen { 52 | return ErrInvalidTopicNameLength 53 | } 54 | tp.Timestamp = int64(binary.LittleEndian.Uint64(data[len(data)-8:])) 55 | tp.Name = string(data[len(data)-nameLen-10 : len(data)-10]) 56 | tp.Data = data[:len(data)-nameLen-10] 57 | tp.raw = data 58 | return nil 59 | } 60 | 61 | func newTopic(topicName string, data []byte) (*Topic, error) { 62 | if topicName == "" { 63 | return nil, ErrInvalidTopicEmpty 64 | } 65 | if len(topicName) > MaxTopicNameLen { 66 | return nil, ErrInvalidTopicNameLength 67 | } 68 | return &Topic{Name: topicName, Data: data, Timestamp: time.Now().UnixNano()}, nil 69 | } 70 | 71 | // TopicAgent . 72 | type TopicAgent struct { 73 | Name string 74 | 75 | mux sync.RWMutex 76 | 77 | clients map[*arpc.Client]util.Empty 78 | } 79 | 80 | // Add . 81 | func (t *TopicAgent) Add(c *arpc.Client) { 82 | t.mux.Lock() 83 | t.clients[c] = util.Empty{} 84 | t.mux.Unlock() 85 | } 86 | 87 | // Delete . 88 | func (t *TopicAgent) Delete(c *arpc.Client) { 89 | t.mux.Lock() 90 | delete(t.clients, c) 91 | t.mux.Unlock() 92 | } 93 | 94 | // Publish . 95 | func (t *TopicAgent) Publish(s *Server, from *arpc.Client, topic *Topic) { 96 | msg := s.NewMessage(arpc.CmdNotify, routePublish, topic.raw) 97 | t.mux.RLock() 98 | for to := range t.clients { 99 | err := to.PushMsg(msg, arpc.TimeZero) 100 | if err != nil { 101 | if from != nil { 102 | log.Error("[Publish] [topic: '%v'] failed %v, from\t%v\tto\t%v", topic.Name, err, from.Conn.RemoteAddr(), to.Conn.RemoteAddr()) 103 | } else { 104 | log.Error("[Publish] [topic: '%v'] failed %v, from Server to\t%v", topic.Name, err, to.Conn.RemoteAddr()) 105 | } 106 | } 107 | } 108 | t.mux.RUnlock() 109 | if from != nil { 110 | log.Debug("%v [Publish] [topic: '%v'] from\t%v", s.Handler.LogTag(), topic.Name, from.Conn.RemoteAddr()) 111 | } else { 112 | log.Debug("%v [Publish] [topic: '%v'] from Server", s.Handler.LogTag(), topic.Name) 113 | } 114 | } 115 | 116 | // PublishToOne . 117 | func (t *TopicAgent) PublishToOne(s *Server, from *arpc.Client, topic *Topic) { 118 | msg := s.NewMessage(arpc.CmdNotify, routePublish, topic.raw) 119 | t.mux.RLock() 120 | for to := range t.clients { 121 | err := to.PushMsg(msg, arpc.TimeZero) 122 | if err != nil { 123 | if from != nil { 124 | log.Error("[PublishToOne] [topic: '%v'] failed %v, from\t%v\tto\t%v", topic.Name, err, from.Conn.RemoteAddr(), to.Conn.RemoteAddr()) 125 | } else { 126 | log.Error("[PublishToOne] [topic: '%v'] failed %v, from Server to\t%v", topic.Name, err, to.Conn.RemoteAddr()) 127 | } 128 | } else { 129 | if from != nil { 130 | log.Debug("%v [PublishToOne] [topic: '%v'] from\t%v", s.Handler.LogTag(), topic.Name, from.Conn.RemoteAddr()) 131 | } else { 132 | log.Debug("%v [PublishToOne] [topic: '%v'] from Server", s.Handler.LogTag(), topic.Name) 133 | } 134 | break 135 | } 136 | } 137 | t.mux.RUnlock() 138 | } 139 | 140 | func newTopicAgent(topic string) *TopicAgent { 141 | return &TopicAgent{ 142 | Name: topic, 143 | clients: map[*arpc.Client]util.Empty{}, 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lesismal/arpc 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package arpc 6 | 7 | import ( 8 | "io" 9 | "net" 10 | "testing" 11 | ) 12 | 13 | func Test_handler_Clone(t *testing.T) { 14 | if got := DefaultHandler.Clone(); got == nil { 15 | t.Errorf("handler.Clone() = nil") 16 | } 17 | } 18 | 19 | func Test_handler_LogTag(t *testing.T) { 20 | if got := DefaultHandler.LogTag(); got != "[ARPC CLI]" { 21 | t.Errorf("handler.LogTag() = %v, want %v", got, "[ARPC CLI]") 22 | } 23 | } 24 | 25 | func Test_handler_SetLogTag(t *testing.T) { 26 | logtag := "XYZ" 27 | DefaultHandler.SetLogTag(logtag) 28 | if got := DefaultHandler.LogTag(); got != logtag { 29 | t.Errorf("handler.LogTag() = %v, want %v", got, logtag) 30 | } 31 | } 32 | 33 | func Test_handler_HandleConnected(t *testing.T) { 34 | DefaultHandler.HandleConnected(func(*Client) {}) 35 | } 36 | 37 | func Test_handler_OnConnected(t *testing.T) { 38 | DefaultHandler.OnConnected(nil) 39 | } 40 | 41 | func Test_handler_HandleDisconnected(t *testing.T) { 42 | DefaultHandler.HandleDisconnected(func(*Client) {}) 43 | } 44 | 45 | func Test_handler_OnDisconnected(t *testing.T) { 46 | DefaultHandler.OnDisconnected(nil) 47 | } 48 | 49 | func Test_handler_HandleOverstock(t *testing.T) { 50 | DefaultHandler.HandleOverstock(func(c *Client, m *Message) {}) 51 | } 52 | 53 | func Test_handler_OnOverstock(t *testing.T) { 54 | DefaultHandler.OnOverstock(nil, nil) 55 | } 56 | 57 | func Test_handler_HandleSessionMiss(t *testing.T) { 58 | DefaultHandler.HandleSessionMiss(func(c *Client, m *Message) {}) 59 | } 60 | 61 | func Test_handler_OnSessionMiss(t *testing.T) { 62 | DefaultHandler.OnSessionMiss(nil, nil) 63 | } 64 | 65 | func Test_handler_BeforeRecv(t *testing.T) { 66 | DefaultHandler.BeforeRecv(func(net.Conn) error { return nil }) 67 | } 68 | 69 | func Test_handler_BeforeSend(t *testing.T) { 70 | DefaultHandler.BeforeSend(func(net.Conn) error { return nil }) 71 | } 72 | 73 | func Test_handler_BatchRecv(t *testing.T) { 74 | if got := DefaultHandler.BatchRecv(); got != true { 75 | t.Errorf("handler.BatchRecv() = %v, want %v", got, true) 76 | } 77 | } 78 | 79 | func Test_handler_SetBatchRecv(t *testing.T) { 80 | DefaultHandler.SetBatchRecv(false) 81 | if got := DefaultHandler.BatchRecv(); got != false { 82 | t.Errorf("handler.BatchRecv() = %v, want %v", got, false) 83 | } 84 | } 85 | 86 | func Test_handler_BatchSend(t *testing.T) { 87 | if got := DefaultHandler.BatchSend(); got != true { 88 | t.Errorf("handler.BatchSend() = %v, want %v", got, true) 89 | } 90 | } 91 | 92 | func Test_handler_SetBatchSend(t *testing.T) { 93 | DefaultHandler.SetBatchSend(false) 94 | if got := DefaultHandler.BatchSend(); got != false { 95 | t.Errorf("handler.BatchSend() = %v, want %v", got, false) 96 | } 97 | } 98 | 99 | func Test_handler_WrapReader(t *testing.T) { 100 | DefaultHandler.SetReaderWrapper(nil) 101 | if got := DefaultHandler.WrapReader(nil); got != nil { 102 | t.Errorf("handler.WrapReader() = %v, want %v", got, nil) 103 | } 104 | } 105 | 106 | func Test_handler_SetReaderWrapper(t *testing.T) { 107 | Test_handler_WrapReader(t) 108 | } 109 | 110 | func Test_handler_RecvBufferSize(t *testing.T) { 111 | if got := DefaultHandler.RecvBufferSize(); got != 8192 { 112 | t.Errorf("handler.RecvBufferSize() = %v, want %v", got, 8192) 113 | } 114 | } 115 | 116 | func Test_handler_SetRecvBufferSize(t *testing.T) { 117 | size := 1024 118 | DefaultHandler.SetRecvBufferSize(size) 119 | if got := DefaultHandler.RecvBufferSize(); got != size { 120 | t.Errorf("handler.RecvBufferSize() = %v, want %v", got, size) 121 | } 122 | } 123 | 124 | func Test_handler_SendQueueSize(t *testing.T) { 125 | if got := DefaultHandler.SendQueueSize(); got <= 0 { 126 | t.Errorf("handler.RecvBufferSize() = %v, want %v", got, 1024) 127 | } 128 | } 129 | 130 | func Test_handler_SetSendQueueSize(t *testing.T) { 131 | size := 2048 132 | DefaultHandler.SetSendQueueSize(size) 133 | if got := DefaultHandler.SendQueueSize(); got != size { 134 | t.Errorf("handler.RecvBufferSize() = %v, want %v", got, size) 135 | } 136 | } 137 | 138 | func Test_handler_Handle(t *testing.T) { 139 | DefaultHandler.Handle("/hello", func(*Context) {}) 140 | } 141 | 142 | func TestNewHandler(t *testing.T) { 143 | if got := NewHandler(); got == nil { 144 | t.Errorf("NewHandler() = nil") 145 | } 146 | } 147 | 148 | func TestSetHandler(t *testing.T) { 149 | d := DefaultHandler 150 | h := NewHandler() 151 | SetHandler(h) 152 | SetLogTag("nothing") 153 | HandleConnected(func(*Client) {}) 154 | HandleConnected(nil) 155 | HandleDisconnected(func(*Client) {}) 156 | HandleDisconnected(nil) 157 | HandleOverstock(func(c *Client, m *Message) {}) 158 | HandleMessageDropped(func(c *Client, m *Message) {}) 159 | HandleSessionMiss(func(c *Client, m *Message) {}) 160 | BeforeRecv(func(net.Conn) error { return nil }) 161 | BeforeSend(func(net.Conn) error { return nil }) 162 | SetBatchRecv(true) 163 | SetBatchSend(true) 164 | SetAsyncResponse(true) 165 | SetReaderWrapper(func(c net.Conn) io.Reader { return c }) 166 | SetRecvBufferSize(4096) 167 | SetSendQueueSize(4096) 168 | Use(func(*Context) {}) 169 | UseCoder(nil) 170 | Handle("nothing", func(*Context) {}, true) 171 | HandleNotFound(func(*Context) {}) 172 | HandleMalloc(func(int) []byte { return nil }) 173 | HandleFree(func([]byte) {}) 174 | SetHandler(d) 175 | } 176 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package log 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "os" 11 | "time" 12 | ) 13 | 14 | var ( 15 | // TimeFormat is used to format time parameters. 16 | TimeFormat = "2006/01/02 15:04:05.000" 17 | 18 | // Output is used to receive log output. 19 | Output io.Writer = os.Stdout 20 | 21 | // DefaultLogger is the default logger and is used by arpc 22 | DefaultLogger Logger = &logger{level: LevelInfo} 23 | ) 24 | 25 | const ( 26 | // LevelAll enables all logs. 27 | LevelAll = iota 28 | // LevelDebug logs are usually disabled in production. 29 | LevelDebug 30 | // LevelInfo is the default logging priority. 31 | LevelInfo 32 | // LevelWarn . 33 | LevelWarn 34 | // LevelError . 35 | LevelError 36 | // LevelNone disables all logs. 37 | LevelNone 38 | ) 39 | 40 | // Logger defines log interface 41 | type Logger interface { 42 | SetLevel(lvl int) 43 | Debug(format string, v ...interface{}) 44 | Info(format string, v ...interface{}) 45 | Warn(format string, v ...interface{}) 46 | Error(format string, v ...interface{}) 47 | } 48 | 49 | // SetLogger sets default logger. 50 | func SetLogger(l Logger) { 51 | DefaultLogger = l 52 | } 53 | 54 | // SetLevel sets default logger's priority. 55 | func SetLevel(lvl int) { 56 | switch lvl { 57 | case LevelAll, LevelDebug, LevelInfo, LevelWarn, LevelError, LevelNone: 58 | DefaultLogger.SetLevel(lvl) 59 | break 60 | default: 61 | fmt.Fprintf(Output, "invalid log level: %v", lvl) 62 | } 63 | } 64 | 65 | // logger implements Logger and is used in arpc by default. 66 | type logger struct { 67 | level int 68 | } 69 | 70 | // SetLevel sets logs priority. 71 | func (l *logger) SetLevel(lvl int) { 72 | switch lvl { 73 | case LevelAll, LevelDebug, LevelInfo, LevelWarn, LevelError, LevelNone: 74 | l.level = lvl 75 | break 76 | default: 77 | fmt.Fprintf(Output, "invalid log level: %v", lvl) 78 | } 79 | } 80 | 81 | // Debug uses fmt.Printf to log a message at LevelDebug. 82 | func (l *logger) Debug(format string, v ...interface{}) { 83 | if LevelDebug >= l.level { 84 | fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [DBG] "+format+"\n", v...) 85 | } 86 | } 87 | 88 | // Info uses fmt.Printf to log a message at LevelInfo. 89 | func (l *logger) Info(format string, v ...interface{}) { 90 | if LevelInfo >= l.level { 91 | fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [INF] "+format+"\n", v...) 92 | } 93 | } 94 | 95 | // Warn uses fmt.Printf to log a message at LevelWarn. 96 | func (l *logger) Warn(format string, v ...interface{}) { 97 | if LevelWarn >= l.level { 98 | fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [WRN] "+format+"\n", v...) 99 | } 100 | } 101 | 102 | // Error uses fmt.Printf to log a message at LevelError. 103 | func (l *logger) Error(format string, v ...interface{}) { 104 | if LevelError >= l.level { 105 | fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [ERR] "+format+"\n", v...) 106 | } 107 | } 108 | 109 | // Debug uses DefaultLogger to log a message at LevelDebug. 110 | func Debug(format string, v ...interface{}) { 111 | if DefaultLogger != nil { 112 | DefaultLogger.Debug(format, v...) 113 | } 114 | } 115 | 116 | // Info uses DefaultLogger to log a message at LevelInfo. 117 | func Info(format string, v ...interface{}) { 118 | if DefaultLogger != nil { 119 | DefaultLogger.Info(format, v...) 120 | } 121 | } 122 | 123 | // Warn uses DefaultLogger to log a message at LevelWarn. 124 | func Warn(format string, v ...interface{}) { 125 | if DefaultLogger != nil { 126 | DefaultLogger.Warn(format, v...) 127 | } 128 | } 129 | 130 | // Error uses DefaultLogger to log a message at LevelError. 131 | func Error(format string, v ...interface{}) { 132 | if DefaultLogger != nil { 133 | DefaultLogger.Error(format, v...) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /log/log_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package log 6 | 7 | import "testing" 8 | 9 | func TestSetLogger(t *testing.T) { 10 | l := &logger{level: LevelDebug} 11 | SetLogger(l) 12 | } 13 | 14 | func TestSetLevel(t *testing.T) { 15 | SetLevel(LevelAll) 16 | func() { 17 | defer func() { recover() }() 18 | SetLevel(1000) 19 | }() 20 | } 21 | 22 | func Test_logger_SetLevel(t *testing.T) { 23 | l := &logger{level: LevelDebug} 24 | l.SetLevel(LevelAll) 25 | } 26 | 27 | func Test_logger_Debug(t *testing.T) { 28 | l := &logger{level: LevelDebug} 29 | l.Debug("logger debug test") 30 | } 31 | 32 | func Test_logger_Info(t *testing.T) { 33 | l := &logger{level: LevelDebug} 34 | l.Info("logger info test") 35 | } 36 | 37 | func Test_logger_Warn(t *testing.T) { 38 | l := &logger{level: LevelDebug} 39 | l.Warn("logger warn test") 40 | } 41 | 42 | func Test_logger_Error(t *testing.T) { 43 | l := &logger{level: LevelDebug} 44 | l.Error("logger error test") 45 | } 46 | 47 | func Test_Debug(t *testing.T) { 48 | Debug("log.Debug") 49 | } 50 | 51 | func Test_Info(t *testing.T) { 52 | Info("log.Info") 53 | } 54 | 55 | func Test_Warn(t *testing.T) { 56 | Warn("log.Warn") 57 | } 58 | 59 | func Test_Error(t *testing.T) { 60 | Error("log.Error") 61 | } 62 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package arpc 6 | 7 | import ( 8 | "context" 9 | "net" 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | 14 | "github.com/lesismal/arpc/codec" 15 | "github.com/lesismal/arpc/log" 16 | "github.com/lesismal/arpc/util" 17 | ) 18 | 19 | // Server represents an arpc Server. 20 | type Server struct { 21 | Accepted int64 22 | CurrLoad int64 23 | MaxLoad int64 24 | 25 | // 64-aligned on 32-bit 26 | seq uint64 27 | 28 | Codec codec.Codec 29 | Handler Handler 30 | 31 | Listener net.Listener 32 | 33 | mux sync.Mutex 34 | 35 | running bool 36 | chStop chan error 37 | clients map[*Client]util.Empty 38 | } 39 | 40 | // Serve starts service with listener. 41 | func (s *Server) Serve(ln net.Listener) error { 42 | s.Listener = ln 43 | s.chStop = make(chan error) 44 | log.Info("%v Running On: \"%v\"", s.Handler.LogTag(), ln.Addr()) 45 | defer log.Info("%v Stopped", s.Handler.LogTag()) 46 | return s.runLoop() 47 | } 48 | 49 | // Run starts tcp service on addr. 50 | func (s *Server) Run(addr string) error { 51 | ln, err := net.Listen("tcp", addr) 52 | if err != nil { 53 | log.Info("%v Running failed: %v", s.Handler.LogTag(), err) 54 | return err 55 | } 56 | s.Listener = ln 57 | s.chStop = make(chan error) 58 | log.Info("%v Running On: \"%v\"", s.Handler.LogTag(), ln.Addr()) 59 | // defer log.Info("%v Stopped", s.Handler.LogTag()) 60 | return s.runLoop() 61 | } 62 | 63 | func (s *Server) Broadcast(method string, v interface{}, args ...interface{}) { 64 | msg := s.NewMessage(CmdNotify, method, v, args...) 65 | s.mux.Lock() 66 | defer func() { 67 | msg.Release() 68 | s.mux.Unlock() 69 | }() 70 | 71 | for c := range s.clients { 72 | msg.Retain() 73 | c.PushMsg(msg, TimeZero) 74 | } 75 | } 76 | 77 | func (s *Server) BroadcastWithFilter(method string, v interface{}, filter func(*Client) bool, args ...interface{}) { 78 | msg := s.NewMessage(CmdNotify, method, v, args...) 79 | s.mux.Lock() 80 | defer func() { 81 | msg.Release() 82 | s.mux.Unlock() 83 | }() 84 | 85 | for c := range s.clients { 86 | if filter == nil || filter(c) { 87 | msg.Retain() 88 | c.PushMsg(msg, TimeZero) 89 | } 90 | } 91 | } 92 | 93 | func (s *Server) ForEach(h func(*Client)) { 94 | s.mux.Lock() 95 | defer s.mux.Unlock() 96 | for c := range s.clients { 97 | h(c) 98 | } 99 | } 100 | 101 | func (s *Server) ForEachWithFilter(h func(*Client), filter func(*Client) bool) { 102 | s.mux.Lock() 103 | defer s.mux.Unlock() 104 | for c := range s.clients { 105 | if filter == nil || filter(c) { 106 | h(c) 107 | } 108 | } 109 | } 110 | 111 | // Stop stops service. 112 | func (s *Server) Stop() error { 113 | defer log.Info("%v \"%v\" Stop", s.Handler.LogTag(), s.Listener.Addr()) 114 | s.running = false 115 | s.Listener.Close() 116 | select { 117 | case <-s.chStop: 118 | case <-time.After(time.Second): 119 | return ErrTimeout 120 | default: 121 | } 122 | return nil 123 | } 124 | 125 | // Shutdown shutdown service. 126 | func (s *Server) Shutdown(ctx context.Context) error { 127 | defer log.Info("%v \"%v\" Shutdown", s.Handler.LogTag(), s.Listener.Addr()) 128 | s.running = false 129 | s.Listener.Close() 130 | select { 131 | case <-s.chStop: 132 | case <-ctx.Done(): 133 | return ErrTimeout 134 | } 135 | return nil 136 | } 137 | 138 | // NewMessage creates a Message. 139 | func (s *Server) NewMessage(cmd byte, method string, v interface{}, args ...interface{}) *Message { 140 | if len(args) == 0 { 141 | return newMessage(cmd, method, v, false, false, atomic.AddUint64(&s.seq, 1), s.Handler, s.Codec, nil) 142 | } 143 | return newMessage(cmd, method, v, false, false, atomic.AddUint64(&s.seq, 1), s.Handler, s.Codec, args[0].(map[interface{}]interface{})) 144 | } 145 | 146 | func (s *Server) addLoad() int64 { 147 | return atomic.AddInt64(&s.CurrLoad, 1) 148 | } 149 | 150 | func (s *Server) subLoad() int64 { 151 | return atomic.AddInt64(&s.CurrLoad, -1) 152 | } 153 | 154 | func (s *Server) addClient(c *Client) { 155 | s.mux.Lock() 156 | s.clients[c] = util.Empty{} 157 | s.mux.Unlock() 158 | } 159 | 160 | func (s *Server) deleteClient(c *Client) { 161 | s.mux.Lock() 162 | delete(s.clients, c) 163 | s.mux.Unlock() 164 | } 165 | 166 | func (s *Server) clearClients() { 167 | s.mux.Lock() 168 | for c := range s.clients { 169 | go c.Stop() 170 | } 171 | s.clients = map[*Client]util.Empty{} 172 | s.mux.Unlock() 173 | } 174 | 175 | func (s *Server) runLoop() error { 176 | var ( 177 | err error 178 | cli *Client 179 | conn net.Conn 180 | ) 181 | 182 | s.running = true 183 | defer func() { 184 | s.clearClients() 185 | close(s.chStop) 186 | }() 187 | 188 | for s.running { 189 | conn, err = s.Listener.Accept() 190 | if err == nil { 191 | load := s.addLoad() 192 | if s.MaxLoad <= 0 || load <= s.MaxLoad { 193 | s.Accepted++ 194 | cli = newClientWithConn(conn, s.Codec, s.Handler, func(c *Client) { 195 | s.deleteClient(c) 196 | s.subLoad() 197 | }) 198 | s.addClient(cli) 199 | s.Handler.OnConnected(cli) 200 | } else { 201 | conn.Close() 202 | s.subLoad() 203 | } 204 | } else if s.running { 205 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 206 | log.Error("%v Accept error: %v; retrying...", s.Handler.LogTag(), err) 207 | time.Sleep(time.Second / 20) 208 | } else { 209 | log.Error("%v Accept error: %v", s.Handler.LogTag(), err) 210 | break 211 | } 212 | } 213 | } 214 | 215 | return err 216 | } 217 | 218 | // NewServer creates an arpc Server. 219 | func NewServer() *Server { 220 | h := DefaultHandler.Clone() 221 | h.SetLogTag("[ARPC SVR]") 222 | return &Server{ 223 | Codec: codec.DefaultCodec, 224 | Handler: h, 225 | clients: map[*Client]util.Empty{}, 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package arpc 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "log" 11 | "net" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | var testServerAddr = "localhost:12000" 17 | 18 | func TestServer_addLoad(t *testing.T) { 19 | var ( 20 | half int64 = 10000 21 | total int64 = half * 2 22 | ) 23 | 24 | s := NewServer() 25 | for i := int64(0); i < total; i++ { 26 | s.addLoad() 27 | } 28 | if s.CurrLoad != total { 29 | t.Fatalf("addLoad failed: %v != %v", s.CurrLoad, total) 30 | } 31 | } 32 | 33 | func TestServer_subLoad(t *testing.T) { 34 | var ( 35 | half int64 = 10000 36 | total int64 = half * 2 37 | ) 38 | 39 | s := NewServer() 40 | for i := int64(0); i < total; i++ { 41 | s.addLoad() 42 | } 43 | if s.CurrLoad != total { 44 | t.Fatalf("addLoad failed: %v != %v", s.CurrLoad, total) 45 | } 46 | 47 | for i := int64(0); i < half; i++ { 48 | s.subLoad() 49 | } 50 | if s.CurrLoad != half { 51 | t.Fatalf("subLoad failed: %v != %v", s.CurrLoad, half) 52 | } 53 | 54 | for i := int64(0); i < half; i++ { 55 | s.subLoad() 56 | } 57 | if s.CurrLoad != 0 { 58 | t.Fatalf("subLoad failed: %v != %v", s.CurrLoad, half) 59 | } 60 | } 61 | 62 | func TestServer_NewMessage(t *testing.T) { 63 | s := &Server{} 64 | for cmd := byte(1); cmd <= 3; cmd++ { 65 | method := fmt.Sprintf("method_%v", cmd) 66 | message := fmt.Sprintf("message_%v", cmd) 67 | msg := s.NewMessage(cmd, method, message) 68 | if msg == nil { 69 | t.Fatalf("Server.NewMessage() = nil") 70 | } 71 | if msg.Cmd() != cmd { 72 | t.Fatalf("Server.NewMessage() error, cmd is: %v, want: %v", msg.Cmd(), cmd) 73 | } 74 | if msg.Method() != method { 75 | t.Fatalf("Server.NewMessage() error, cmd is: %v, want: %v", msg.Method(), method) 76 | } 77 | if msg.Method() != method { 78 | t.Fatalf("Server.NewMessage() error, cmd is: %v, want: %v", string(msg.Data()), message) 79 | } 80 | } 81 | } 82 | 83 | func TestServer_Serve(t *testing.T) { 84 | ln, err := net.Listen("tcp", testServerAddr) 85 | if err != nil { 86 | log.Fatalf("failed to listen: %v", err) 87 | } 88 | 89 | svr := NewServer() 90 | svr.MaxLoad = 3 91 | go svr.Serve(ln) 92 | time.Sleep(time.Second / 100) 93 | for i := 0; i < 3; i++ { 94 | if _, err := net.Dial("tcp", testServerAddr); err != nil { 95 | log.Fatalf("failed to Dial: %v", err) 96 | } 97 | } 98 | for i := 0; i < 3; i++ { 99 | if conn, err := net.Dial("tcp", testServerAddr); err != nil { 100 | log.Fatalf("failed to Dial: %v", err) 101 | } else { 102 | conn.SetReadDeadline(time.Now().Add(time.Second / 10)) 103 | if _, err = conn.Read([]byte{1}); err == nil { 104 | log.Fatalf("conn.Read success, should be closed by server(limited by MaxLoad)") 105 | } 106 | } 107 | } 108 | svr.Stop() 109 | } 110 | 111 | func TestServer_Run(t *testing.T) { 112 | svr := NewServer() 113 | go svr.Run(testServerAddr) 114 | go svr.Run(testServerAddr) 115 | time.Sleep(time.Second / 2) 116 | svr.Stop() 117 | 118 | } 119 | 120 | func TestServer_Shutdown(t *testing.T) { 121 | svr := NewServer() 122 | go svr.Run("localhost:8899") 123 | time.Sleep(time.Second / 2) 124 | svr.Shutdown(context.Background()) 125 | } 126 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | package arpc 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "sync/atomic" 7 | 8 | "github.com/lesismal/arpc/util" 9 | ) 10 | 11 | // Stream . 12 | type Stream struct { 13 | id uint64 14 | cli *Client 15 | method string 16 | local bool 17 | chData chan *Message 18 | stateRecv int32 19 | stateSend int32 20 | stateCloseCnt int32 21 | } 22 | 23 | func (s *Stream) Id() uint64 { 24 | return s.id 25 | } 26 | 27 | func (s *Stream) onMessage(msg *Message) { 28 | if len(msg.Data()) == 0 { 29 | return 30 | } 31 | if atomic.LoadInt32(&s.stateRecv) == 1 { 32 | s.cli.Handler.OnMessageDone(s.cli, msg) 33 | return 34 | } 35 | if msg != nil { 36 | select { 37 | case s.chData <- msg: 38 | case <-s.cli.chClose: 39 | } 40 | } 41 | } 42 | 43 | func (s *Stream) CloseRecv() { 44 | if atomic.CompareAndSwapInt32(&s.stateRecv, 0, 1) { 45 | close(s.chData) 46 | s.halfClose() 47 | } 48 | } 49 | 50 | func (s *Stream) CloseRecvContext(ctx context.Context) { 51 | s.CloseRecv() 52 | } 53 | 54 | func (s *Stream) CloseSend() { 55 | go s.CloseSendContext(context.Background()) 56 | } 57 | 58 | func (s *Stream) CloseSendContext(ctx context.Context) { 59 | if atomic.CompareAndSwapInt32(&s.stateSend, 0, 1) { 60 | eof := true 61 | s.send(ctx, []byte{}, eof) 62 | s.halfClose() 63 | } 64 | } 65 | 66 | func (s *Stream) Recv(v interface{}) error { 67 | if atomic.LoadInt32(&s.stateRecv) == 1 && len(s.chData) == 0 { 68 | return io.EOF 69 | } 70 | select { 71 | case msg, ok := <-s.chData: 72 | if !ok { 73 | return io.EOF 74 | } 75 | data := msg.Data() 76 | err := util.BytesToValue(s.cli.Codec, data, v) 77 | s.cli.Handler.OnMessageDone(s.cli, msg) 78 | return err 79 | case <-s.cli.chClose: 80 | return ErrClientStopped 81 | } 82 | } 83 | 84 | func (s *Stream) RecvContext(ctx context.Context, v interface{}) error { 85 | select { 86 | case msg := <-s.chData: 87 | data := msg.Data() 88 | err := util.BytesToValue(s.cli.Codec, data, v) 89 | s.cli.Handler.OnMessageDone(s.cli, msg) 90 | return err 91 | case <-ctx.Done(): 92 | return ErrTimeout 93 | case <-s.cli.chClose: 94 | return ErrClientStopped 95 | } 96 | } 97 | 98 | func (s *Stream) RecvWith(ctx context.Context, v interface{}) error { 99 | return s.RecvContext(ctx, v) 100 | } 101 | 102 | func (s *Stream) newMessage(v interface{}, args ...interface{}) *Message { 103 | if len(args) == 0 { 104 | return newMessage(CmdStream, s.method, v, false, false, s.id, s.cli.Handler, s.cli.Codec, nil) 105 | } 106 | return newMessage(CmdStream, s.method, v, false, false, s.id, s.cli.Handler, s.cli.Codec, args[0].(map[interface{}]interface{})) 107 | } 108 | 109 | func (s *Stream) Send(v interface{}, args ...interface{}) error { 110 | return s.SendContext(context.Background(), v, args...) 111 | } 112 | 113 | func (s *Stream) SendContext(ctx context.Context, v interface{}, args ...interface{}) error { 114 | eof := false 115 | return s.checkStateAndSend(ctx, v, eof, args...) 116 | } 117 | 118 | func (s *Stream) SendWith(ctx context.Context, v interface{}, args ...interface{}) error { 119 | return s.SendContext(ctx, v, args...) 120 | } 121 | 122 | func (s *Stream) SendAndClose(v interface{}, args ...interface{}) error { 123 | return s.SendAndCloseContext(context.Background(), v, args...) 124 | } 125 | 126 | func (s *Stream) SendAndCloseContext(ctx context.Context, v interface{}, args ...interface{}) error { 127 | eof := true 128 | return s.checkStateAndSend(ctx, v, eof, args...) 129 | } 130 | 131 | func (s *Stream) SendAndCloseWith(ctx context.Context, v interface{}, args ...interface{}) error { 132 | return s.SendAndCloseContext(ctx, v, args...) 133 | } 134 | 135 | func (s *Stream) checkStateAndSend(ctx context.Context, v interface{}, eof bool, args ...interface{}) error { 136 | if atomic.LoadInt32(&s.stateSend) == 1 { 137 | return ErrStreamClosedSend 138 | } 139 | return s.send(ctx, v, eof, args...) 140 | } 141 | 142 | func (s *Stream) send(ctx context.Context, v interface{}, eof bool, args ...interface{}) error { 143 | c := s.cli 144 | err := c.CheckState() 145 | if err != nil { 146 | return err 147 | } 148 | 149 | data := util.ValueToBytes(c.Codec, v) 150 | msg := s.newMessage(data, args...) 151 | msg.SetStreamLocal(s.local) 152 | msg.SetStreamEOF(eof) 153 | 154 | if eof && atomic.CompareAndSwapInt32(&s.stateSend, 0, 1) { 155 | s.halfClose() 156 | } 157 | 158 | if c.Handler.AsyncWrite() { 159 | select { 160 | case c.chSend <- msg: 161 | case <-c.chClose: 162 | // c.Handler.OnOverstock(c, msg) 163 | c.Handler.OnMessageDone(c, msg) 164 | return ErrClientStopped 165 | case <-ctx.Done(): 166 | return ErrTimeout 167 | } 168 | } else { 169 | if !c.reconnecting { 170 | coders := c.Handler.Coders() 171 | for j := 0; j < len(coders); j++ { 172 | msg = coders[j].Encode(c, msg) 173 | } 174 | _, err := c.Handler.Send(c.Conn, msg.Buffer) 175 | if err != nil { 176 | c.Conn.Close() 177 | } 178 | c.Handler.OnMessageDone(c, msg) 179 | return err 180 | } else { 181 | c.dropMessage(msg) 182 | return ErrClientReconnecting 183 | } 184 | } 185 | 186 | return nil 187 | } 188 | 189 | func (s *Stream) halfClose() { 190 | if atomic.AddInt32(&s.stateCloseCnt, 1) == 2 { 191 | s.cli.deleteStream(s.id, s.local) 192 | } 193 | } 194 | 195 | // NewStream creates a stream. 196 | func (client *Client) NewStream(method string) *Stream { 197 | return client.newStream(method, 0, true) 198 | } 199 | 200 | func (client *Client) newStream(method string, id uint64, local bool) *Stream { 201 | if id == 0 { 202 | id = atomic.AddUint64(&client.seq, 1) 203 | } 204 | stream := &Stream{ 205 | id: id, 206 | cli: client, 207 | method: method, 208 | chData: make(chan *Message, client.Handler.StreamQueueSize()), 209 | local: local, 210 | } 211 | client.mux.Lock() 212 | if local { 213 | client.streamLocalMap[stream.id] = stream 214 | } else { 215 | client.streamRemoteMap[stream.id] = stream 216 | } 217 | client.mux.Unlock() 218 | return stream 219 | } 220 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package util 6 | 7 | import ( 8 | "errors" 9 | "runtime" 10 | "unsafe" 11 | 12 | acodec "github.com/lesismal/arpc/codec" 13 | "github.com/lesismal/arpc/log" 14 | ) 15 | 16 | // Empty struct 17 | type Empty struct{} 18 | 19 | // Recover handles panic and logs stack info 20 | func Recover() { 21 | if err := recover(); err != nil { 22 | const size = 64 << 10 23 | buf := make([]byte, size) 24 | buf = buf[:runtime.Stack(buf, false)] 25 | log.Error("runtime error: %v\ntraceback:\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) 26 | } 27 | } 28 | 29 | // Safe wraps a function-calling with panic recovery 30 | func Safe(call func()) { 31 | defer Recover() 32 | call() 33 | } 34 | 35 | // StrToBytes hacks string to []byte 36 | func StrToBytes(s string) []byte { 37 | x := (*[2]uintptr)(unsafe.Pointer(&s)) 38 | h := [3]uintptr{x[0], x[1], x[1]} 39 | return *(*[]byte)(unsafe.Pointer(&h)) 40 | } 41 | 42 | // BytesToStr hacks []byte to string 43 | func BytesToStr(b []byte) string { 44 | return *(*string)(unsafe.Pointer(&b)) 45 | } 46 | 47 | // ValueToBytes converts values to []byte 48 | func ValueToBytes(codec acodec.Codec, v interface{}) []byte { 49 | if v == nil { 50 | return nil 51 | } 52 | var ( 53 | err error 54 | data []byte 55 | ) 56 | switch vt := v.(type) { 57 | case []byte: 58 | data = vt 59 | case *[]byte: 60 | data = *vt 61 | case string: 62 | data = StrToBytes(vt) 63 | case *string: 64 | data = StrToBytes(*vt) 65 | case error: 66 | data = StrToBytes(vt.Error()) 67 | case *error: 68 | data = StrToBytes((*vt).Error()) 69 | default: 70 | if codec == nil { 71 | codec = acodec.DefaultCodec 72 | } 73 | data, err = codec.Marshal(vt) 74 | if err != nil { 75 | log.Error("ValueToBytes: %v", err) 76 | } 77 | } 78 | 79 | return data 80 | } 81 | 82 | // BytesToValue converts []byte to values 83 | func BytesToValue(codec acodec.Codec, data []byte, v interface{}) error { 84 | var err error 85 | if v != nil { 86 | switch vt := v.(type) { 87 | case *[]byte: 88 | *vt = make([]byte, len(data)) 89 | copy(*vt, data) 90 | case *string: 91 | *vt = string(data) 92 | case *error: 93 | *vt = errors.New(string(data)) 94 | default: 95 | if codec == nil { 96 | codec = acodec.DefaultCodec 97 | } 98 | err = codec.Unmarshal(data, vt) 99 | if err != nil { 100 | log.Error("ValueToBytes: %v", err) 101 | } 102 | } 103 | } 104 | return err 105 | } 106 | -------------------------------------------------------------------------------- /util/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 lesismal. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package util 6 | 7 | import ( 8 | "errors" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/lesismal/arpc/codec" 13 | ) 14 | 15 | func Test_StrToBytes(t *testing.T) { 16 | if got := StrToBytes("hello world"); !reflect.DeepEqual(got, []byte("hello world")) { 17 | t.Errorf("StrToBytes() = %v, want %v", got, []byte("hello world")) 18 | } 19 | } 20 | 21 | func Test_BytesToStr(t *testing.T) { 22 | if got := BytesToStr([]byte("hello world")); got != "hello world" { 23 | t.Errorf("BytesToStr() = %v, want %v", got, "hello world") 24 | } 25 | } 26 | 27 | func Test_ValueToBytes(t *testing.T) { 28 | if got := ValueToBytes(codec.DefaultCodec, nil); got != nil { 29 | t.Errorf("ValueToBytes() = %v, want %v", got, nil) 30 | } 31 | if got := ValueToBytes(codec.DefaultCodec, "test"); !reflect.DeepEqual(got, []byte("test")) { 32 | t.Errorf("ValueToBytes() = %v, want %v", got, []byte("test")) 33 | } 34 | str := "test" 35 | if got := ValueToBytes(codec.DefaultCodec, &str); !reflect.DeepEqual(got, []byte("test")) { 36 | t.Errorf("ValueToBytes() = %v, want %v", got, []byte("test")) 37 | } 38 | if got := ValueToBytes(codec.DefaultCodec, []byte("test")); !reflect.DeepEqual(got, []byte("test")) { 39 | t.Errorf("ValueToBytes() = %v, want %v", got, []byte("test")) 40 | } 41 | bts := []byte("test") 42 | if got := ValueToBytes(codec.DefaultCodec, &bts); !reflect.DeepEqual(got, []byte("test")) { 43 | t.Errorf("ValueToBytes() = %v, want %v", got, []byte("test")) 44 | } 45 | if got := ValueToBytes(codec.DefaultCodec, errors.New("test")); !reflect.DeepEqual(got, []byte("test")) { 46 | t.Errorf("ValueToBytes() = %v, want %v", got, []byte("test")) 47 | } 48 | err := errors.New("test") 49 | if got := ValueToBytes(nil, &err); !reflect.DeepEqual(got, []byte("test")) { 50 | t.Errorf("ValueToBytes() = %v, want %v", got, []byte("test")) 51 | } 52 | if got := ValueToBytes(&codec.JSONCodec{}, &struct{ I int }{I: 3}); !reflect.DeepEqual(got, []byte(`{"I":3}`)) { 53 | t.Errorf("ValueToBytes() = %v, want %v", got, []byte(`{"I":3}`)) 54 | } 55 | if got := ValueToBytes(nil, 0); len(got) < 0 { 56 | t.Errorf("ValueToBytes() len = %v, want 0", len(got)) 57 | } 58 | } 59 | 60 | func Test_Safe(t *testing.T) { 61 | Safe(func() {}) 62 | } 63 | --------------------------------------------------------------------------------