├── .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 |
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 |
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 |
--------------------------------------------------------------------------------