Showing preview only (458K chars total). Download the full file or copy to clipboard to get everything.
Repository: smallnest/rpcx
Branch: master
Commit: d57f1f75012c
Files: 128
Total size: 428.0 KB
Directory structure:
gitextract_r3mw_qpx/
├── .github/
│ └── workflows/
│ └── go.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── _testutils/
│ ├── GoUnusedProtection__.go
│ ├── arith_service.go
│ ├── arith_service.proto
│ ├── thrift_arith_service-consts.go
│ ├── thrift_arith_service.go
│ └── thrift_arith_service.thrift
├── client/
│ ├── cache_client_builder.go
│ ├── circuit_breaker.go
│ ├── circuit_breaker_test.go
│ ├── client.go
│ ├── client_test.go
│ ├── connection.go
│ ├── connection_iouring.go
│ ├── connection_iouring_test.go
│ ├── connection_kcp.go
│ ├── connection_memu.go
│ ├── connection_nonkcp.go
│ ├── connection_nonquic.go
│ ├── connection_quic.go
│ ├── connection_rdma.go
│ ├── discovery.go
│ ├── dns_discovery.go
│ ├── failmode_enumer.go
│ ├── geo_utils.go
│ ├── hash_utils.go
│ ├── mdns_discovery.go
│ ├── mode.go
│ ├── multiple_servers_discovery.go
│ ├── oneclient.go
│ ├── oneclient_pool.go
│ ├── oneclient_pool_test.go
│ ├── peer2peer_discovery.go
│ ├── ping_utils.go
│ ├── plugin.go
│ ├── selectmode_enumer.go
│ ├── selector.go
│ ├── selector_test.go
│ ├── smooth_weighted_round_robin.go
│ ├── xclient.go
│ ├── xclient_pool.go
│ ├── xclient_pool_test.go
│ └── xclient_test.go
├── clientplugin/
│ └── req_rate_limiting_redis.go
├── codec/
│ ├── codec.go
│ ├── codec_test.go
│ └── testdata/
│ ├── GoUnusedProtection__.go
│ ├── gen.sh
│ ├── protobuf.pb.go
│ ├── protobuf.proto
│ ├── thrift_colorgroup-consts.go
│ ├── thrift_colorgroup.go
│ └── thrift_colorgroup.thrift
├── errors/
│ ├── error.go
│ └── error_test.go
├── go.mod
├── go.sum
├── log/
│ ├── default_logger.go
│ ├── dummy_logger.go
│ └── logger.go
├── protocol/
│ ├── compressor.go
│ ├── compressor_test.go
│ ├── message.go
│ ├── message_chan_test.go
│ ├── message_test.go
│ └── testdata/
│ ├── benchmark.pb.go
│ ├── benchmark.proto
│ └── gen.sh
├── reflection/
│ ├── server_reflection.go
│ └── server_reflection_test.go
├── server/
│ ├── context.go
│ ├── converter.go
│ ├── converter_test.go
│ ├── file_transfer.go
│ ├── gateway.go
│ ├── jsonrpc2.go
│ ├── jsonrpc2_wire.go
│ ├── kcp.go
│ ├── listener.go
│ ├── listener_linux.go
│ ├── listener_rdma.go
│ ├── listener_unix.go
│ ├── memconn.go
│ ├── option.go
│ ├── option_test.go
│ ├── options.go
│ ├── plugin.go
│ ├── plugin_test.go
│ ├── pool.go
│ ├── pool_test.go
│ ├── quic.go
│ ├── router.go
│ ├── server.go
│ ├── server_test.go
│ ├── service.go
│ ├── service_test.go
│ └── stream.go
├── serverplugin/
│ ├── alias.go
│ ├── blacklist.go
│ ├── mdns.go
│ ├── metrics.go
│ ├── rate_limiting.go
│ ├── redis.go
│ ├── registry_test.go
│ ├── req_rate_limiting.go
│ ├── req_rate_limiting_redis.go
│ ├── tee.go
│ └── whitelist.go
├── share/
│ ├── context.go
│ ├── context_test.go
│ ├── share.go
│ └── share_test.go
├── tool/
│ └── xgen/
│ ├── README.md
│ ├── parser/
│ │ └── parser.go
│ └── xgen.go
└── util/
├── buffer_pool.go
├── buffer_pool_test.go
├── compress.go
├── compress_test.go
├── converter.go
├── net.go
└── net_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/go.yml
================================================
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.13
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
================================================
FILE: .gitignore
================================================
#
vendor/
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
.idea
cover.html
.history
================================================
FILE: .travis.yml
================================================
language: go
go:
- 1.17.x
env:
- GO111MODULE=on
before_script:
- rm -f go.sum
- go get -tags "quic kcp" -v github.com/smallnest/rpcx@HEAD
- go get github.com/mattn/goveralls
script:
- go test -v ./...
- goveralls -service=travis-ci
notifications:
email:
recipients: smallnest@gmail.com
on_success: change
on_failure: always
================================================
FILE: CHANGELOG.md
================================================
# [rpcx](http://rpcx.io)
## 1.9.0
- unregister all services on close automatically
- add PostHTTPRequestPlugin
- support io_uring
- add CacheDiscovery
- add Oneshot method for XClient
- support RDMA
## 1.8.0
- supports distributed rate limiter based on go-redis/redis-rate
- move zookeeper plugin to https://github.com/smallnest/rpcx-zookeepr
- move consul plugin to https://github.com/smallnest/rpcx-consul
- move redis plugin to https://github.com/smallnest/rpcx-redis
- move influxd/opentelemetry plugin to https://github.com/smallnest/rpcx-plugins
- you can write customized error, for example `{"code": 500, err: "internal error"}`
- server support the work pool by `WithPool`
- support to write services like `go std http router` style without reflect
- simplify async write for service
- improve performance
## 1.7.0
- move etcd support to github.com/rpcxio/rpcx-etcd
- Broken API: NewXXXDiscovery returns error instead of panic
- support AdvertiseAddr in FileTransfer
- support Auth for OneClientPool
- support Auth for XClientPool
- Broken API: add meta parameter for SendFile/DownloadFile
- support streaming between server side and client side
- support DNS as service discovery
- support rpcx flow tracing
- support websocket as the transport like tcp,kcp and quic
- add CMuxPlugin to allow developing customzied services by using the same single port
- re-tag rpcx to make sure the version is less than 2 (for go module)
- support visit grpc services by rpcx clients: https://github.com/rpcxio/rpcxplus/tree/master/grpcx
- support configing grpc servicves in rpcx server side
- improve rpcx performance
- add Inform method in XClient
- add memory connection for unit tests
- supports opentelemetry
## 1.6.0
- support reflection
- add kubernetes config example
- improve nacos support
- improve message.Encode performance
- re-register services in etcd v3
- avoid duplicated client creation
- add SelectNodePlugin that can interrupt the Select method
- support TcpCopy by TeePlugin
- support reuseport for http invoke
- return reply even in case of server errors
- Change two methods' name of client plugin!
- Broken API: add error parameter in `PreWriteResponse`(#486)
- Broken API: change ReadTimeout/WriteTimeout to IdleTimeout
- Support passing Deadline of client contexts to server side
- remove InprocessClient plugin
- use heartbeat/tcp_keepalive to avoid client hanging
## 1.5.0
- support jsonrpc 2.0
- support CORS for jsonrpc 2.0
- support opentracing and opencensus
- upload/download files by streaming
- add Pool for XClient and OneClient
- remove rudp support
- add ConnCreated plugin. Yu can use it to set KCP UDPSession
- update client plugins. All plugin returns error instead of bool
- support ETCD 3.0 API
- support redis as registry
- support redis DB selection
- fix RegisterFunction issues
- add Filter for clients
- remove most of build tags such as etcd, zookeeper,consul,reuseport
- add Nacos as registry http://nacos.io
- support blacklist and whitlist
## 1.4.0
- Support utp and rudp
- Add OneClient to support invoke multile servicesby one client
- Finish compress feature
- Add more plugins for monitoring connection
- Support dynamic port allocation
- Use go module to manage dependencies
- Support shutdown graceful
- Add [rpcx-java](https://github.com/smallnest/rpcx-java) to support develop raw java services and clients
- Support thrift codec
- Setup rpcx offcial site: http://rpcx.io
- Add Chinese document: http://cn.doc.rpcx.io or https://smallnest.gitbooks.io/go-rpc-programming-guide
## 1.3.1
- Add http gateway: https://github.com/rpcxio/rpcx-gateway
- Add direct http invoke
- Add bidirectional communication
- Add xgen tool to generate codes for services automatically
fix bugs.
## 1.3.0
- Rewrite rpcx. It implements its protocol and won't implemented based on wrapper of go standard rpc lib
- Add go tags for pluggable plugins
- Add English document: https://github.com/smallnest/rpcx-programming
- Add rpcx 3.0 examples: https://github.com/rpcxio/rpcx-examples
rpcx 3.0 is not compatible with rpcx 2.0 and below
================================================
FILE: LICENSE
================================================
Copyright 2017 Chao yuepan
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
WORKDIR=`pwd`
default: build
vet:
go vet ./...
tools:
go get github.com/golangci/golangci-lint/cmd/golangci-lint
go get github.com/golang/lint/golint
go get github.com/axw/gocov/gocov
go get github.com/matm/gocov-html
golangci-lint:
golangci-lint run -D errcheck --build-tags 'quic kcp'
lint:
golint ./...
doc:
godoc -http=:6060
deps:
go list -f '{{ join .Deps "\n"}}' ./... |grep "/" | grep -v "github.com/smallnest/rpcx"| grep "\." | sort |uniq
fmt:
go fmt ./...
build:
go build ./...
build-all:
go build -tags "kcp quic" ./...
test:
go test -race -tags "kcp quic" ./...
cover:
gocov test -tags "kcp quic" ./... | gocov-html > cover.html
open cover.html
check-libs:
GIT_TERMINAL_PROMPT=1 GO111MODULE=on go list -m -u all | column -t
update-libs:
GIT_TERMINAL_PROMPT=1 GO111MODULE=on go get -u -v ./...
mod-tidy:
GIT_TERMINAL_PROMPT=1 GO111MODULE=on go mod tidy
================================================
FILE: README.md
================================================
- **stable branch**: v1.7.x
- **development branch**: master
<a href="https://rpcx.io/"><img height="160" src="http://rpcx.io/logos/rpcx-logo-text.png"></a>
Official site: [http://rpcx.io](http://rpcx.io/)
[](https://opensource.org/licenses/Apache-2.0) [](http://godoc.org/github.com/smallnest/rpcx) [](https://github.com/smallnest/rpcx/actions/workflows/go.yml/badge.svg) [](https://goreportcard.com/report/github.com/smallnest/rpcx) [](https://coveralls.io/github/smallnest/rpcx?branch=master) [](_documents/rpcx_dev_qq3.jpg)
**Notice: etcd**
since rpcx 1.7.6, some plugins have been moved to the independent project:
- `etcd` plugin has been moved to [rpcx-etcd](https://github.com/rpcxio/rpcx-etcd)
- `zookeeper` plugin has been moved to [rpcx-zookeeper](https://github.com/rpcxio/rpcx-zookeeper)
- `consul` plugin has been moved to [rpcx-consul](https://github.com/rpcxio/rpcx-consul)
- `redis` plugin has been moved to [rpcx-redis](https://github.com/rpcxio/rpcx-redis)
- `influxdb` plugin has been moved to [rpcx-plugins](https://github.com/rpcxio/rpcx-plugins)
- `opentelemetry` plugin has been moved to [rpcx-plugins](https://github.com/rpcxio/rpcx-plugins)
## Announce
A tcpdump-like tool added: [rpcxdump](https://github.com/smallnest/rpcxdump)。 You can use it to debug communications between rpcx services and clients.

## Cross-Languages
you can use other programming languages besides Go to access rpcx services.
- **rpcx-gateway**: You can write clients in any programming languages to call rpcx services via [rpcx-gateway](https://github.com/rpcxio/rpcx-gateway)
- **http invoke**: you can use the same http requests to access rpcx gateway
- **Java Services/Clients**: You can use [rpcx-java](https://github.com/smallnest/rpcx-java) to implement/access rpcx services via raw protocol.
- **rust rpcx**: You can write rpcx services in rust by [rpcx-rs](https://github.com/smallnest/rpcx-rs)
> If you can write Go methods, you can also write rpc services. It is so easy to write rpc applications with rpcx.
## Installation
install the basic features:
`go get -v github.com/smallnest/rpcx/...`
If you want to use `quic`、`kcp` registry, use those tags to `go get` 、 `go build` or `go run`. For example, if you want to use all features, you can:
```sh
go get -v -tags "quic kcp" github.com/smallnest/rpcx/...
```
**_tags_**:
- **quic**: support quic transport
- **kcp**: support kcp transport
## Which companies are using rpcx?
<p float="left">
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993220-b967f000-4557-11eb-9747-703a6cbb9fb1.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/113414067-a8e4d280-93ee-11eb-9f42-1373d7e766c1.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993433-267b8580-4558-11eb-9e45-4e1a86d61688.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993530-4743db00-4558-11eb-9f76-1ee69e992b82.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993612-722e2f00-4558-11eb-849a-3264c430aef9.png" width="200" />
<img style="padding-bottom: 20px;" src="https://user-images.githubusercontent.com/865763/102993785-c20cf600-4558-11eb-82b9-27b801aca4ff.png" width="200" />
</p>
## Features
rpcx is a RPC framework like [Alibaba Dubbo](http://dubbo.io/) and [Weibo Motan](https://github.com/weibocom/motan).
**rpcx** is created for targets:
1. **Simple**: easy to learn, easy to develop, easy to integrate and easy to deploy
2. **Performance**: high performance (>= grpc-go)
3. **Cross-platform**: support _raw slice of bytes_, _JSON_, _Protobuf_ and _MessagePack_. Theoretically it can be used with java, php, python, c/c++, node.js, c# and other platforms
4. **Service discovery and service governance**: support zookeeper, etcd and consul.
It contains below features
- Support raw Go functions. There's no need to define proto files.
- Pluggable. Features can be extended such as service discovery, tracing.
- Support TCP, HTTP, [QUIC](https://en.wikipedia.org/wiki/QUIC) and [KCP](https://github.com/skywind3000/kcp)
- Support multiple codecs such as JSON, Protobuf, [MessagePack](https://msgpack.org/index.html) and raw bytes.
- Service discovery. Support peer2peer, configured peers, [zookeeper](https://zookeeper.apache.org), [etcd](https://github.com/coreos/etcd), [consul](https://www.consul.io) and [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS).
- Fault tolerance:Failover, Failfast, Failtry.
- Load banlancing:support Random, RoundRobin, Consistent hashing, Weighted, network quality and Geography.
- Support Compression.
- Support passing metadata.
- Support Authorization.
- Support heartbeat and one-way request.
- Other features: metrics, log, timeout, alias, circuit breaker.
- Support bidirectional communication.
- Support access via HTTP so you can write clients in any programming languages.
- Support API gateway.
- Support backup request, forking and broadcast.
rpcx uses a binary protocol and platform-independent, which means you can develop services in other languages such as Java, python, nodejs, and you can use other prorgramming languages to invoke services developed in Go.
There is a UI manager: [rpcx-ui](https://github.com/smallnest/rpcx-ui).
## Performance
Test results show rpcx has better performance than other rpc framework except standard rpc lib.
The benchmark code is at [rpcx-benchmark](https://github.com/rpcx-ecosystem/rpcx-benchmark).
**Listen to others, but test by yourself**.
**_Test Environment_**
- **CPU**: Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz, 32 cores
- **Memory**: 32G
- **Go**: 1.9.0
- **OS**: CentOS 7 / 3.10.0-229.el7.x86_64
**_Use_**
- protobuf
- the client and the server on the same server
- 581 bytes payload
- 500/2000/5000 concurrent clients
- mock processing time: 0ms, 10ms and 30ms
**_Test Result_**
### mock 0ms process time
<table><tr><th>Throughputs</th><th>Mean Latency</th><th>P99 Latency</th></tr><tr><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p0-throughput.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p0-latency.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p0-p99.png"></td></tr></table>
### mock 10ms process time
<table><tr><th>Throughputs</th><th>Mean Latency</th><th>P99 Latency</th></tr><tr><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p10-throughput.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p10-latency.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p10-p99.png"></td></tr></table>
### mock 30ms process time
<table><tr><th>Throughputs</th><th>Mean Latency</th><th>P99 Latency</th></tr><tr><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p30-throughput.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p30-latency.png"></td><td width="30%"><img src="http://colobu.com/2018/01/31/benchmark-2018-spring-of-popular-rpc-frameworks/p30-p99.png"></td></tr></table>
## Examples
You can find all examples at [rpcxio/rpcx-examples](https://github.com/rpcxio/rpcx-examples).
The below is a simple example.
**Server**
```go
// define example.Arith
……
s := server.NewServer()
s.RegisterName("Arith", new(example.Arith), "")
s.Serve("tcp", addr)
```
**Client**
```go
// prepare requests
……
d, err := client.NewPeer2PeerDiscovery("tcp@"+addr, "")
xclient := client.NewXClient("Arith", client.Failtry, client.RandomSelect, d, client.DefaultOption)
defer xclient.Close()
err = xclient.Call(context.Background(), "Mul", args, reply, nil)
```
## Contributors
<a href="https://github.com/smallnest/rpcx/graphs/contributors">
<img src="https://contrib.rocks/image?repo=smallnest/rpcx" />
</a>
## Contribute
see [contributors](https://github.com/smallnest/rpcx/graphs/contributors).
Welcome to contribute:
- submit issues or requirements
- send PRs
- write projects to use rpcx
- write tutorials or articles to introduce rpcx
## License
Apache License, Version 2.0
================================================
FILE: _testutils/GoUnusedProtection__.go
================================================
// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.
package testutils
var GoUnusedProtection__ int
================================================
FILE: _testutils/arith_service.go
================================================
// Code generated by protoc-gen-gogo.
// source: arith_service.proto
// DO NOT EDIT!
/*
Package client is a generated protocol buffer package.
It is generated from these files:
arith_service.proto
It has these top-level messages:
ProtoArgs
ProtoReply
*/
package testutils
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type ProtoArgs struct {
A int32 `protobuf:"varint,1,opt,name=A,proto3" json:"A,omitempty"`
B int32 `protobuf:"varint,2,opt,name=B,proto3" json:"B,omitempty"`
}
func (m *ProtoArgs) Reset() { *m = ProtoArgs{} }
func (m *ProtoArgs) String() string { return proto.CompactTextString(m) }
func (*ProtoArgs) ProtoMessage() {}
func (*ProtoArgs) Descriptor() ([]byte, []int) { return fileDescriptorArithService, []int{0} }
func (m *ProtoArgs) GetA() int32 {
if m != nil {
return m.A
}
return 0
}
func (m *ProtoArgs) GetB() int32 {
if m != nil {
return m.B
}
return 0
}
type ProtoReply struct {
C int32 `protobuf:"varint,1,opt,name=C,proto3" json:"C,omitempty"`
}
func (m *ProtoReply) Reset() { *m = ProtoReply{} }
func (m *ProtoReply) String() string { return proto.CompactTextString(m) }
func (*ProtoReply) ProtoMessage() {}
func (*ProtoReply) Descriptor() ([]byte, []int) { return fileDescriptorArithService, []int{1} }
func (m *ProtoReply) GetC() int32 {
if m != nil {
return m.C
}
return 0
}
func init() {
proto.RegisterType((*ProtoArgs)(nil), "client.ProtoArgs")
proto.RegisterType((*ProtoReply)(nil), "client.ProtoReply")
}
func (m *ProtoArgs) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ProtoArgs) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if m.A != 0 {
dAtA[i] = 0x8
i++
i = encodeVarintArithService(dAtA, i, uint64(m.A))
}
if m.B != 0 {
dAtA[i] = 0x10
i++
i = encodeVarintArithService(dAtA, i, uint64(m.B))
}
return i, nil
}
func (m *ProtoReply) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ProtoReply) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if m.C != 0 {
dAtA[i] = 0x8
i++
i = encodeVarintArithService(dAtA, i, uint64(m.C))
}
return i, nil
}
func encodeFixed64ArithService(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
dAtA[offset+4] = uint8(v >> 32)
dAtA[offset+5] = uint8(v >> 40)
dAtA[offset+6] = uint8(v >> 48)
dAtA[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32ArithService(dAtA []byte, offset int, v uint32) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintArithService(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *ProtoArgs) Size() (n int) {
var l int
_ = l
if m.A != 0 {
n += 1 + sovArithService(uint64(m.A))
}
if m.B != 0 {
n += 1 + sovArithService(uint64(m.B))
}
return n
}
func (m *ProtoReply) Size() (n int) {
var l int
_ = l
if m.C != 0 {
n += 1 + sovArithService(uint64(m.C))
}
return n
}
func sovArithService(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozArithService(x uint64) (n int) {
return sovArithService(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *ProtoArgs) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowArithService
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ProtoArgs: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ProtoArgs: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field A", wireType)
}
m.A = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowArithService
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.A |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field B", wireType)
}
m.B = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowArithService
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.B |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipArithService(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthArithService
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *ProtoReply) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowArithService
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ProtoReply: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ProtoReply: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field C", wireType)
}
m.C = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowArithService
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.C |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipArithService(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthArithService
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipArithService(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowArithService
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowArithService
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowArithService
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthArithService
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowArithService
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipArithService(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthArithService = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowArithService = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("arith_service.proto", fileDescriptorArithService) }
var fileDescriptorArithService = []byte{
// 126 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0x2c, 0xca, 0x2c,
0xc9, 0x88, 0x2f, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
0x62, 0x4b, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0x51, 0x52, 0xe7, 0xe2, 0x0c, 0x00, 0x09, 0x38, 0x16,
0xa5, 0x17, 0x0b, 0xf1, 0x70, 0x31, 0x3a, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x06, 0x31, 0x3a,
0x82, 0x78, 0x4e, 0x12, 0x4c, 0x10, 0x9e, 0x93, 0x92, 0x14, 0x17, 0x17, 0x58, 0x61, 0x50, 0x6a,
0x41, 0x4e, 0x25, 0x48, 0xce, 0x19, 0xa6, 0xd2, 0xd9, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b,
0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0x21, 0x89, 0x0d, 0x6c, 0x8b,
0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xfa, 0x7e, 0x25, 0x8d, 0x7c, 0x00, 0x00, 0x00,
}
================================================
FILE: _testutils/arith_service.proto
================================================
// protoc --gogofaster_out=. arith_service.proto
// mv arith_service.pb.go arith_service_test.go
syntax = "proto3";
package client;
message ProtoArgs {
int32 A = 1;
int32 B = 2;
}
message ProtoReply {
int32 C = 1;
}
================================================
FILE: _testutils/thrift_arith_service-consts.go
================================================
// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.
package testutils
import (
"bytes"
"context"
"fmt"
"github.com/apache/thrift/lib/go/thrift"
"time"
)
// (needed to ensure safety because of naive import list construction.)
var _ = thrift.ZERO
var _ = fmt.Printf
var _ = context.Background
var _ = time.Now
var _ = bytes.Equal
func init() {
}
================================================
FILE: _testutils/thrift_arith_service.go
================================================
// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.
package testutils
import (
"bytes"
"context"
"fmt"
"github.com/apache/thrift/lib/go/thrift"
"time"
)
// (needed to ensure safety because of naive import list construction.)
var _ = thrift.ZERO
var _ = fmt.Printf
var _ = context.Background
var _ = time.Now
var _ = bytes.Equal
// Attributes:
// - A
// - B
type ThriftArgs_ struct {
A int32 `thrift:"a,1" db:"a" json:"a"`
B int32 `thrift:"b,2" db:"b" json:"b"`
}
func NewThriftArgs_() *ThriftArgs_ {
return &ThriftArgs_{}
}
func (p *ThriftArgs_) GetA() int32 {
return p.A
}
func (p *ThriftArgs_) GetB() int32 {
return p.B
}
func (p *ThriftArgs_) Read(ctx context.Context, iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
}
for {
_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx)
if err != nil {
return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 1:
if fieldTypeId == thrift.I32 {
if err := p.ReadField1(ctx, iprot); err != nil {
return err
}
} else {
if err := iprot.Skip(ctx, fieldTypeId); err != nil {
return err
}
}
case 2:
if fieldTypeId == thrift.I32 {
if err := p.ReadField2(ctx, iprot); err != nil {
return err
}
} else {
if err := iprot.Skip(ctx, fieldTypeId); err != nil {
return err
}
}
default:
if err := iprot.Skip(ctx, fieldTypeId); err != nil {
return err
}
}
if err := iprot.ReadFieldEnd(ctx); err != nil {
return err
}
}
if err := iprot.ReadStructEnd(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
}
return nil
}
func (p *ThriftArgs_) ReadField1(ctx context.Context, iprot thrift.TProtocol) error {
if v, err := iprot.ReadI32(ctx); err != nil {
return thrift.PrependError("error reading field 1: ", err)
} else {
p.A = v
}
return nil
}
func (p *ThriftArgs_) ReadField2(ctx context.Context, iprot thrift.TProtocol) error {
if v, err := iprot.ReadI32(ctx); err != nil {
return thrift.PrependError("error reading field 2: ", err)
} else {
p.B = v
}
return nil
}
func (p *ThriftArgs_) Write(ctx context.Context, oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin(ctx, "ThriftArgs"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
}
if p != nil {
if err := p.writeField1(ctx, oprot); err != nil {
return err
}
if err := p.writeField2(ctx, oprot); err != nil {
return err
}
}
if err := oprot.WriteFieldStop(ctx); err != nil {
return thrift.PrependError("write field stop error: ", err)
}
if err := oprot.WriteStructEnd(ctx); err != nil {
return thrift.PrependError("write struct stop error: ", err)
}
return nil
}
func (p *ThriftArgs_) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin(ctx, "a", thrift.I32, 1); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:a: ", p), err)
}
if err := oprot.WriteI32(ctx, int32(p.A)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.a (1) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 1:a: ", p), err)
}
return err
}
func (p *ThriftArgs_) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin(ctx, "b", thrift.I32, 2); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:b: ", p), err)
}
if err := oprot.WriteI32(ctx, int32(p.B)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.b (2) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 2:b: ", p), err)
}
return err
}
func (p *ThriftArgs_) Equals(other *ThriftArgs_) bool {
if p == other {
return true
} else if p == nil || other == nil {
return false
}
if p.A != other.A {
return false
}
if p.B != other.B {
return false
}
return true
}
func (p *ThriftArgs_) String() string {
if p == nil {
return "<nil>"
}
return fmt.Sprintf("ThriftArgs_(%+v)", *p)
}
// Attributes:
// - C
type ThriftReply struct {
C int32 `thrift:"c,1" db:"c" json:"c"`
}
func NewThriftReply() *ThriftReply {
return &ThriftReply{}
}
func (p *ThriftReply) GetC() int32 {
return p.C
}
func (p *ThriftReply) Read(ctx context.Context, iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
}
for {
_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx)
if err != nil {
return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 1:
if fieldTypeId == thrift.I32 {
if err := p.ReadField1(ctx, iprot); err != nil {
return err
}
} else {
if err := iprot.Skip(ctx, fieldTypeId); err != nil {
return err
}
}
default:
if err := iprot.Skip(ctx, fieldTypeId); err != nil {
return err
}
}
if err := iprot.ReadFieldEnd(ctx); err != nil {
return err
}
}
if err := iprot.ReadStructEnd(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
}
return nil
}
func (p *ThriftReply) ReadField1(ctx context.Context, iprot thrift.TProtocol) error {
if v, err := iprot.ReadI32(ctx); err != nil {
return thrift.PrependError("error reading field 1: ", err)
} else {
p.C = v
}
return nil
}
func (p *ThriftReply) Write(ctx context.Context, oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin(ctx, "ThriftReply"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
}
if p != nil {
if err := p.writeField1(ctx, oprot); err != nil {
return err
}
}
if err := oprot.WriteFieldStop(ctx); err != nil {
return thrift.PrependError("write field stop error: ", err)
}
if err := oprot.WriteStructEnd(ctx); err != nil {
return thrift.PrependError("write struct stop error: ", err)
}
return nil
}
func (p *ThriftReply) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin(ctx, "c", thrift.I32, 1); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:c: ", p), err)
}
if err := oprot.WriteI32(ctx, int32(p.C)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.c (1) field write error: ", p), err)
}
if err := oprot.WriteFieldEnd(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 1:c: ", p), err)
}
return err
}
func (p *ThriftReply) Equals(other *ThriftReply) bool {
if p == other {
return true
} else if p == nil || other == nil {
return false
}
if p.C != other.C {
return false
}
return true
}
func (p *ThriftReply) String() string {
if p == nil {
return "<nil>"
}
return fmt.Sprintf("ThriftReply(%+v)", *p)
}
================================================
FILE: _testutils/thrift_arith_service.thrift
================================================
struct ThriftArgs
{
1: i32 a,
2: i32 b,
}
struct ThriftReply
{
1: i32 c,
}
================================================
FILE: client/cache_client_builder.go
================================================
package client
import "sync"
// CacheClientBuilder defines builder interface to generate RPCCient.
type CacheClientBuilder interface {
SetCachedClient(client RPCClient, k, servicePath, serviceMethod string)
FindCachedClient(k, servicePath, serviceMethod string) RPCClient
DeleteCachedClient(client RPCClient, k, servicePath, serviceMethod string)
GenerateClient(k, servicePath, serviceMethod string) (client RPCClient, err error)
}
// RegisterCacheClientBuilder(network string, builder CacheClientBuilder)
var cacheClientBuildersMutex sync.RWMutex
var cacheClientBuilders = make(map[string]CacheClientBuilder)
func RegisterCacheClientBuilder(network string, builder CacheClientBuilder) {
cacheClientBuildersMutex.Lock()
defer cacheClientBuildersMutex.Unlock()
cacheClientBuilders[network] = builder
}
func getCacheClientBuilder(network string) (CacheClientBuilder, bool) {
cacheClientBuildersMutex.RLock()
defer cacheClientBuildersMutex.RUnlock()
builder, ok := cacheClientBuilders[network]
return builder, ok
}
================================================
FILE: client/circuit_breaker.go
================================================
package client
import (
"errors"
"sync/atomic"
"time"
)
var (
ErrBreakerOpen = errors.New("breaker open")
ErrBreakerTimeout = errors.New("breaker time out")
)
// ConsecCircuitBreaker is window sliding CircuitBreaker with failure threshold.
type ConsecCircuitBreaker struct {
lastFailureTime int64
failures uint64
failureThreshold uint64
window time.Duration
}
// NewConsecCircuitBreaker returns a new ConsecCircuitBreaker.
func NewConsecCircuitBreaker(failureThreshold uint64, window time.Duration) *ConsecCircuitBreaker {
return &ConsecCircuitBreaker{
failureThreshold: failureThreshold,
window: window,
}
}
// Call Circuit function
func (cb *ConsecCircuitBreaker) Call(fn func() error, d time.Duration) error {
var err error
if !cb.ready() {
return ErrBreakerOpen
}
if d == 0 {
err = fn()
} else {
c := make(chan error, 1)
go func() {
c <- fn()
close(c)
}()
t := time.NewTimer(d)
select {
case e := <-c:
err = e
case <-t.C:
err = ErrBreakerTimeout
}
t.Stop()
}
if err == nil {
cb.success()
} else {
cb.fail()
}
return err
}
func (cb *ConsecCircuitBreaker) ready() bool {
lastFailureTime := time.Unix(0, atomic.LoadInt64(&cb.lastFailureTime))
if time.Since(lastFailureTime) > cb.window {
cb.reset()
return true
}
failures := atomic.LoadUint64(&cb.failures)
return failures < cb.failureThreshold
}
func (cb *ConsecCircuitBreaker) success() {
cb.reset()
}
func (cb *ConsecCircuitBreaker) fail() {
atomic.AddUint64(&cb.failures, 1)
atomic.StoreInt64(&cb.lastFailureTime, time.Now().UnixNano())
}
func (cb *ConsecCircuitBreaker) Success() {
cb.success()
}
func (cb *ConsecCircuitBreaker) Fail() {
cb.fail()
}
func (cb *ConsecCircuitBreaker) Ready() bool {
return cb.ready()
}
func (cb *ConsecCircuitBreaker) reset() {
atomic.StoreUint64(&cb.failures, 0)
atomic.StoreInt64(&cb.lastFailureTime, time.Now().UnixNano())
}
================================================
FILE: client/circuit_breaker_test.go
================================================
package client
import (
"errors"
"math/rand"
"testing"
"time"
)
func TestConsecCircuitBreaker(t *testing.T) {
count := -1
fn := func() error {
count++
if count >= 5 && count < 10 {
return nil
}
return errors.New("test error")
}
cb := NewConsecCircuitBreaker(5, 100*time.Millisecond)
for i := 0; i < 25; i++ {
err := cb.Call(fn, 200*time.Millisecond)
switch {
case i < 5:
if err.Error() != "test error" {
t.Fatalf("expect %v, got %v", "test error", err)
}
case i >= 5 && i < 10:
if err != ErrBreakerOpen {
t.Fatalf("expect %v, got %v", ErrBreakerOpen, err)
}
case i >= 10 && i < 15:
if err != nil {
t.Fatalf("expect success, got %v", err)
}
case i >= 15 && i < 20:
if err.Error() != "test error" {
t.Fatalf("expect %v, got %v", "test error", err)
}
case i >= 20 && i < 25:
if err != ErrBreakerOpen {
t.Fatalf("expect %v, got %v", ErrBreakerOpen, err)
}
}
if i == 9 { // expired
time.Sleep(150 * time.Millisecond)
}
}
}
func TestCircuitBreakerRace(t *testing.T) {
cb := NewConsecCircuitBreaker(2, 50*time.Millisecond)
routines := 100
loop := 100000
fn := func() error {
if rand.Intn(2) == 1 {
return nil
}
return errors.New("test error")
}
for r := 0; r < routines; r++ {
go func() {
for i := 0; i < loop; i++ {
cb.Call(fn, 100*time.Millisecond)
}
}()
}
}
================================================
FILE: client/client.go
================================================
package client
import (
"bufio"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/url"
"strconv"
"strings"
"sync"
"time"
circuit "github.com/rubyist/circuitbreaker"
"github.com/smallnest/rpcx/log"
"github.com/smallnest/rpcx/protocol"
"github.com/smallnest/rpcx/share"
)
const (
XVersion = "X-RPCX-Version"
XMessageType = "X-RPCX-MesssageType"
XHeartbeat = "X-RPCX-Heartbeat"
XOneway = "X-RPCX-Oneway"
XMessageStatusType = "X-RPCX-MessageStatusType"
XSerializeType = "X-RPCX-SerializeType"
XMessageID = "X-RPCX-MessageID"
XServicePath = "X-RPCX-ServicePath"
XServiceMethod = "X-RPCX-ServiceMethod"
XMeta = "X-RPCX-Meta"
XErrorMessage = "X-RPCX-ErrorMessage"
)
// ServiceError is an error from server.
type ServiceError interface {
Error() string
IsServiceError() bool
}
// NewServiceError creates a ServiceError with the error message.
func NewServiceError(s string) ServiceError {
return strErr(s)
}
// ClientErrorFunc is a function to create a customized error.
var ClientErrorFunc func(res *protocol.Message, e string) ServiceError
type strErr string
func (s strErr) Error() string {
return string(s)
}
func (s strErr) IsServiceError() bool {
return true
}
// DefaultOption is a common option configuration for client.
var DefaultOption = Option{
Retries: 3,
RPCPath: share.DefaultRPCPath,
ConnectTimeout: time.Second,
SerializeType: protocol.MsgPack,
CompressType: protocol.None,
BackupLatency: 10 * time.Millisecond,
MaxWaitForHeartbeat: 30 * time.Second,
TCPKeepAlivePeriod: time.Minute,
BidirectionalBlock: false,
TimeToDisallow: time.Minute,
}
// Breaker is a CircuitBreaker interface.
type Breaker interface {
Call(func() error, time.Duration) error
Fail()
Success()
Ready() bool
}
// CircuitBreaker is a default circuit breaker (RateBreaker(0.95, 100)).
var CircuitBreaker Breaker = circuit.NewRateBreaker(0.95, 100)
// ErrShutdown connection is closed.
var (
ErrShutdown = errors.New("connection is shut down")
ErrUnsupportedCodec = errors.New("unsupported codec")
)
const (
// ReaderBuffsize is used for bufio reader.
ReaderBuffsize = 16 * 1024
// WriterBuffsize is used for bufio writer.
WriterBuffsize = 16 * 1024
)
type seqKey struct{}
// RPCClient is interface that defines one client to call one server.
type RPCClient interface {
Connect(network, address string) error
Go(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call
Call(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}) error
SendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error)
Close() error
RemoteAddr() string
RegisterServerMessageChan(ch chan<- *protocol.Message)
UnregisterServerMessageChan()
IsClosing() bool
IsShutdown() bool
GetConn() net.Conn
}
// Client represents a RPC client.
type Client struct {
option Option
Conn net.Conn
r *bufio.Reader
// w *bufio.Writer
mutex sync.Mutex // protects following
seq uint64
pending map[uint64]*Call
closing bool // user has called Close
shutdown bool // server has told us to stop
pluginClosed bool // the plugin has been called
Plugins PluginContainer
ServerMessageChan chan<- *protocol.Message
}
// NewClient returns a new Client with the option.
func NewClient(option Option) *Client {
return &Client{
option: option,
}
}
// RemoteAddr returns the remote address.
func (client *Client) RemoteAddr() string {
return client.Conn.RemoteAddr().String()
}
// GetConn returns the underlying conn.
func (client *Client) GetConn() net.Conn {
return client.Conn
}
// Option contains all options for creating clients.
type Option struct {
// Group is used to select the services in the same group. Services set group info in their meta.
// If it is empty, clients will ignore group.
Group string
// Retries retries to send
Retries int
// RetryInterval is the interval between retries
RetryInterval time.Duration
// Time to disallow the bad server not to be selected
TimeToDisallow time.Duration
// TLSConfig for tcp and quic
TLSConfig *tls.Config
// kcp.BlockCrypt
Block interface{}
// RPCPath for http connection
RPCPath string
// ConnectTimeout sets timeout for dialing
ConnectTimeout time.Duration
// IdleTimeout sets max idle time for underlying net.Conns
IdleTimeout time.Duration
// BackupLatency is used for Failbackup mode. rpcx will sends another request if the first response doesn't return in BackupLatency time.
BackupLatency time.Duration
// Breaker is used to config CircuitBreaker
GenBreaker func() Breaker
SerializeType protocol.SerializeType
CompressType protocol.CompressType
// send heartbeat message to service and check responses
Heartbeat bool
// interval for heartbeat
HeartbeatInterval time.Duration
MaxWaitForHeartbeat time.Duration
// TCPKeepAlive, if it is zero we don't set keepalive
TCPKeepAlivePeriod time.Duration
// bidirectional mode, if true serverMessageChan will block to wait message for consume. default false.
BidirectionalBlock bool
// alaways use the selected server until it is bad
Sticky bool
// not call server message handler
NilCallServerMessageHandler func(msg *protocol.Message)
}
// Call represents an active RPC.
type Call struct {
ServicePath string // The name of the service and method to call.
ServiceMethod string // The name of the service and method to call.
Metadata map[string]string // metadata
ResMetadata map[string]string
Args interface{} // The argument to the function (*struct).
Reply interface{} // The reply from the function (*struct).
Error error // After completion, the error status.
Done chan *Call // Strobes when call is complete.
Raw bool // raw message or not
}
func (call *Call) done() {
if call.Done == nil { // Oneshot
return
}
select {
case call.Done <- call:
// ok
default:
log.Debug("rpc: discarding Call reply due to insufficient Done chan capacity")
}
}
// RegisterServerMessageChan registers the channel that receives server requests.
func (client *Client) RegisterServerMessageChan(ch chan<- *protocol.Message) {
client.ServerMessageChan = ch
}
// UnregisterServerMessageChan removes ServerMessageChan.
func (client *Client) UnregisterServerMessageChan() {
client.ServerMessageChan = nil
}
// IsClosing client is closing or not.
func (client *Client) IsClosing() bool {
client.mutex.Lock()
defer client.mutex.Unlock()
return client.closing
}
// IsShutdown client is shutdown or not.
func (client *Client) IsShutdown() bool {
client.mutex.Lock()
defer client.mutex.Unlock()
return client.shutdown
}
// Go invokes the function asynchronously. It returns the Call structure representing
// the invocation. The done channel will signal when the call is complete by returning
// the same Call object. If done is nil, Go will allocate a new channel.
// If non-nil, done must be buffered or Go will deliberately crash.
func (client *Client) Go(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
call := new(Call)
call.ServicePath = servicePath
call.ServiceMethod = serviceMethod
meta := ctx.Value(share.ReqMetaDataKey)
if meta != nil { // copy meta in context to meta in requests
call.Metadata = meta.(map[string]string)
}
if !share.IsShareContext(ctx) {
ctx = share.NewContext(ctx)
}
call.Args = args
call.Reply = reply
// // we allow done is nil
// if done == nil {
// done = make(chan *Call, 10) // buffered.
// } else {
// // If caller passes done != nil, it must arrange that
// // done has enough buffer for the number of simultaneous
// // RPCs that will be using that channel. If the channel
// // is totally unbuffered, it's best not to run at all.
// if cap(done) == 0 {
// log.Panic("rpc: done channel is unbuffered")
// }
// }
call.Done = done
if share.Trace {
log.Debugf("client.Go send request for %s.%s, args: %+v in case of client call", servicePath, serviceMethod, args)
}
go client.send(ctx, call)
return call
}
// Call invokes the named function, waits for it to complete, and returns its error status.
func (client *Client) Call(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}) error {
return client.call(ctx, servicePath, serviceMethod, args, reply)
}
func (client *Client) call(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}) error {
seq := new(uint64)
if sharedCtx, ok := ctx.(*share.Context); ok {
sharedCtx.SetValue(seqKey{}, seq)
} else {
ctx = context.WithValue(ctx, seqKey{}, seq)
}
if share.Trace {
log.Debugf("client.call for %s.%s, args: %+v in case of client call", servicePath, serviceMethod, args)
defer func() {
log.Debugf("client.call done for %s.%s, args: %+v in case of client call", servicePath, serviceMethod, args)
}()
}
Done := client.Go(ctx, servicePath, serviceMethod, args, reply, make(chan *Call, 10)).Done
var err error
select {
case <-ctx.Done(): // cancel by context
client.mutex.Lock()
call := client.pending[*seq]
delete(client.pending, *seq)
client.mutex.Unlock()
if call != nil {
call.Error = ctx.Err()
call.done()
}
return ctx.Err()
case call := <-Done:
err = call.Error
meta := ctx.Value(share.ResMetaDataKey)
if meta != nil && len(call.ResMetadata) > 0 {
resMeta := meta.(map[string]string)
locker, ok := ctx.Value(share.ContextTagsLock).(*sync.Mutex)
if ok {
locker.Lock()
for k, v := range call.ResMetadata {
resMeta[k] = v
}
resMeta[share.ServerAddress] = client.Conn.RemoteAddr().String()
locker.Unlock()
} else {
for k, v := range call.ResMetadata {
resMeta[k] = v
}
resMeta[share.ServerAddress] = client.Conn.RemoteAddr().String()
}
}
}
return err
}
// SendRaw sends raw messages. You don't care args and replies.
func (client *Client) SendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error) {
if sharedCtx, ok := ctx.(*share.Context); ok {
sharedCtx.SetValue(seqKey{}, r.Seq())
} else {
ctx = context.WithValue(ctx, seqKey{}, r.Seq())
}
call := new(Call)
call.Raw = true
call.ServicePath = r.ServicePath
call.ServiceMethod = r.ServiceMethod
meta := ctx.Value(share.ReqMetaDataKey)
rmeta := make(map[string]string)
// copy meta to rmeta
if meta != nil {
for k, v := range meta.(map[string]string) {
rmeta[k] = v
}
}
// copy r.Metadata to rmeta
if r.Metadata != nil {
for k, v := range r.Metadata {
rmeta[k] = v
}
}
if meta != nil { // copy meta in context to meta in requests
call.Metadata = rmeta
}
r.Metadata = rmeta
if !share.IsShareContext(ctx) {
ctx = share.NewContext(ctx)
}
done := make(chan *Call, 10)
call.Done = done
seq := r.Seq()
client.mutex.Lock()
if client.pending == nil {
client.pending = make(map[uint64]*Call)
}
client.pending[seq] = call
client.mutex.Unlock()
data := r.EncodeSlicePointer()
_, err := client.Conn.Write(*data)
protocol.PutData(data)
if err != nil {
client.mutex.Lock()
call = client.pending[seq]
delete(client.pending, seq)
client.mutex.Unlock()
if call != nil {
call.Error = err
call.done()
}
return nil, nil, err
}
if r.IsOneway() {
client.mutex.Lock()
call = client.pending[seq]
delete(client.pending, seq)
client.mutex.Unlock()
if call != nil {
call.done()
}
return nil, nil, nil
}
var m map[string]string
var payload []byte
select {
case <-ctx.Done(): // cancel by context
client.mutex.Lock()
call := client.pending[seq]
delete(client.pending, seq)
client.mutex.Unlock()
if call != nil {
call.Error = ctx.Err()
call.done()
}
return nil, nil, ctx.Err()
case call := <-done:
err = call.Error
m = call.ResMetadata
if call.Reply != nil {
payload = call.Reply.([]byte)
}
}
return m, payload, err
}
func convertRes2Raw(res *protocol.Message) (map[string]string, []byte, error) {
m := make(map[string]string)
m[XVersion] = strconv.Itoa(int(res.Version()))
if res.IsHeartbeat() {
m[XHeartbeat] = "true"
}
if res.IsOneway() {
m[XOneway] = "true"
}
if res.MessageStatusType() == protocol.Error {
m[XMessageStatusType] = "Error"
} else {
m[XMessageStatusType] = "Normal"
}
// if res.CompressType() == protocol.Gzip {
// m["Content-Encoding"] = "gzip"
// }
m[XMeta] = urlencode(res.Metadata)
m[XSerializeType] = strconv.Itoa(int(res.SerializeType()))
m[XMessageID] = strconv.FormatUint(res.Seq(), 10)
m[XServicePath] = res.ServicePath
m[XServiceMethod] = res.ServiceMethod
return m, res.Payload, nil
}
func urlencode(data map[string]string) string {
if len(data) == 0 {
return ""
}
var buf strings.Builder
first := true
for k, v := range data {
if !first {
buf.WriteByte('&')
}
buf.WriteString(url.QueryEscape(k))
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(v))
first = false
}
return buf.String()
}
func (client *Client) send(ctx context.Context, call *Call) {
// Register this call.
client.mutex.Lock()
if client.shutdown || client.closing {
call.Error = ErrShutdown
client.mutex.Unlock()
call.done()
return
}
isHeartbeat := call.ServicePath == "" && call.ServiceMethod == ""
serializeType := client.option.SerializeType
if isHeartbeat {
serializeType = protocol.MsgPack
}
codec := share.Codecs[serializeType]
if codec == nil {
call.Error = ErrUnsupportedCodec
client.mutex.Unlock()
call.done()
return
}
if client.pending == nil {
client.pending = make(map[uint64]*Call)
}
seq := client.seq
client.seq++
client.pending[seq] = call
client.mutex.Unlock()
if cseq, ok := ctx.Value(seqKey{}).(*uint64); ok {
*cseq = seq
}
// req := protocol.NewMessage()
req := protocol.NewMessage()
req.SetMessageType(protocol.Request)
req.SetSeq(seq)
if call.Reply == nil {
req.SetOneway(true)
}
// heartbeat, and use default SerializeType (msgpack)
if isHeartbeat {
req.SetHeartbeat(true)
req.SetSerializeType(protocol.MsgPack)
} else {
req.SetSerializeType(client.option.SerializeType)
}
if call.Metadata != nil {
req.Metadata = call.Metadata
}
req.ServicePath = call.ServicePath
req.ServiceMethod = call.ServiceMethod
data, err := codec.Encode(call.Args)
if err != nil {
client.mutex.Lock()
delete(client.pending, seq)
client.mutex.Unlock()
call.Error = err
call.done()
return
}
if len(data) > 1024 && client.option.CompressType != protocol.None {
req.SetCompressType(client.option.CompressType)
}
req.Payload = data
if client.Plugins != nil {
_ = client.Plugins.DoClientBeforeEncode(req)
}
if share.Trace {
log.Debugf("client.send for %s.%s, args: %+v in case of client call", call.ServicePath, call.ServiceMethod, call.Args)
}
allData := req.EncodeSlicePointer()
_, err = client.Conn.Write(*allData)
protocol.PutData(allData)
if share.Trace {
log.Debugf("client.sent for %s.%s, args: %+v in case of client call", call.ServicePath, call.ServiceMethod, call.Args)
}
if err != nil {
if e, ok := err.(*net.OpError); ok {
if e.Err != nil {
err = fmt.Errorf("net.OpError: %s", e.Err.Error())
} else {
err = errors.New("net.OpError")
}
}
client.mutex.Lock()
call = client.pending[seq]
delete(client.pending, seq)
client.mutex.Unlock()
if call != nil {
call.Error = err
call.done()
}
return
}
isOneway := req.IsOneway()
if isOneway {
client.mutex.Lock()
call = client.pending[seq]
delete(client.pending, seq)
client.mutex.Unlock()
if call != nil {
call.done()
}
}
if client.option.IdleTimeout != 0 {
_ = client.Conn.SetDeadline(time.Now().Add(client.option.IdleTimeout))
}
}
func (client *Client) input() {
var err error
for err == nil {
res := protocol.NewMessage()
if client.option.IdleTimeout != 0 {
_ = client.Conn.SetDeadline(time.Now().Add(client.option.IdleTimeout))
}
err = res.Decode(client.r)
if err != nil {
break
}
if client.Plugins != nil {
_ = client.Plugins.DoClientAfterDecode(res)
}
seq := res.Seq()
var call *Call
isServerMessage := (res.MessageType() == protocol.Request && !res.IsHeartbeat() && res.IsOneway())
if !isServerMessage {
client.mutex.Lock()
call = client.pending[seq]
delete(client.pending, seq)
client.mutex.Unlock()
}
if share.Trace {
log.Debugf("client.input received %v", res)
}
switch {
case call == nil:
if isServerMessage {
if client.ServerMessageChan != nil {
client.handleServerRequest(res)
} else if client.option.NilCallServerMessageHandler != nil {
client.option.NilCallServerMessageHandler(res)
}
continue
}
case res.MessageStatusType() == protocol.Error:
// We've got an error response. Give this to the request
if len(res.Metadata) > 0 {
call.ResMetadata = res.Metadata
// convert server error to a customized error, which implements ServerError interface
if ClientErrorFunc != nil {
call.Error = ClientErrorFunc(res, res.Metadata[protocol.ServiceError])
} else {
call.Error = strErr(res.Metadata[protocol.ServiceError])
}
}
if call.Raw {
call.Metadata, call.Reply, _ = convertRes2Raw(res)
call.Metadata[XErrorMessage] = call.Error.Error()
call.ResMetadata = res.Metadata
} else if len(res.Payload) > 0 {
data := res.Payload
codec := share.Codecs[res.SerializeType()]
if codec != nil {
_ = codec.Decode(data, call.Reply)
}
}
call.done()
default:
if call.Raw {
call.Metadata, call.Reply, _ = convertRes2Raw(res)
call.ResMetadata = res.Metadata
} else {
data := res.Payload
if len(data) > 0 {
codec := share.Codecs[res.SerializeType()]
if codec == nil {
call.Error = strErr(ErrUnsupportedCodec.Error())
} else {
err = codec.Decode(data, call.Reply)
if err != nil {
call.Error = strErr(err.Error())
}
}
}
if len(res.Metadata) > 0 {
call.ResMetadata = res.Metadata
}
}
call.done()
}
}
// Terminate pending calls.
if client.ServerMessageChan != nil {
req := protocol.NewMessage()
req.SetMessageType(protocol.Request)
req.SetMessageStatusType(protocol.Error)
if req.Metadata == nil {
req.Metadata = make(map[string]string)
if err != nil {
req.Metadata[protocol.ServiceError] = err.Error()
}
}
req.Metadata["server"] = client.Conn.RemoteAddr().String()
client.handleServerRequest(req)
}
client.mutex.Lock()
if !client.pluginClosed {
if client.Plugins != nil {
client.Plugins.DoClientConnectionClose(client.Conn)
}
client.pluginClosed = true
}
client.Conn.Close()
client.shutdown = true
closing := client.closing
if e, ok := err.(*net.OpError); ok {
if e.Addr != nil || e.Err != nil {
err = fmt.Errorf("net.OpError: %s", e.Err.Error())
} else {
err = errors.New("net.OpError")
}
}
if err == io.EOF {
if closing {
err = ErrShutdown
} else {
err = io.ErrUnexpectedEOF
}
}
for _, call := range client.pending {
call.Error = err
call.done()
}
client.mutex.Unlock()
if err != nil && !closing {
log.Errorf("rpcx: client protocol error: %v", err)
}
}
func (client *Client) handleServerRequest(msg *protocol.Message) {
defer func() {
if r := recover(); r != nil {
log.Errorf("ServerMessageChan may be closed so client remove it. Please add it again if you want to handle server requests. error is %v", r)
client.ServerMessageChan = nil
}
}()
serverMessageChan := client.ServerMessageChan
if serverMessageChan != nil {
if client.option.BidirectionalBlock {
serverMessageChan <- msg
} else {
select {
case serverMessageChan <- msg:
default:
log.Warnf("ServerMessageChan may be full so the server request %d has been dropped", msg.Seq())
}
}
}
}
func (client *Client) heartbeat() {
t := time.NewTicker(client.option.HeartbeatInterval)
if client.option.MaxWaitForHeartbeat == 0 {
client.option.MaxWaitForHeartbeat = 30 * time.Second
}
for range t.C {
if client.IsShutdown() || client.IsClosing() {
t.Stop()
return
}
request := time.Now().UnixNano()
reply := int64(0)
ctx, cancel := context.WithTimeout(context.Background(), client.option.MaxWaitForHeartbeat)
err := client.Call(ctx, "", "", &request, &reply)
abnormal := false
if ctx.Err() != nil {
log.Warnf("failed to heartbeat to %s, context err: %v", client.Conn.RemoteAddr().String(), ctx.Err())
abnormal = true
}
cancel()
if err != nil {
log.Warnf("failed to heartbeat to %s: %v", client.Conn.RemoteAddr().String(), err)
abnormal = true
}
if reply != request {
log.Warnf("reply %d in heartbeat to %s is different from request %d", reply, client.Conn.RemoteAddr().String(), request)
}
if abnormal {
client.Close()
}
}
}
// Close calls the underlying connection's Close method. If the connection is already
// shutting down, ErrShutdown is returned.
func (client *Client) Close() error {
client.mutex.Lock()
for seq, call := range client.pending {
delete(client.pending, seq)
if call != nil {
call.Error = ErrShutdown
call.done()
}
}
var err error
if !client.pluginClosed {
if client.Plugins != nil {
client.Plugins.DoClientConnectionClose(client.Conn)
}
client.pluginClosed = true
err = client.Conn.Close()
}
if client.closing || client.shutdown {
client.mutex.Unlock()
return ErrShutdown
}
client.closing = true
client.mutex.Unlock()
return err
}
================================================
FILE: client/client_test.go
================================================
package client
import (
"context"
"fmt"
"math/rand"
"net"
"sync"
"testing"
"time"
testutils "github.com/smallnest/rpcx/_testutils"
"github.com/smallnest/rpcx/protocol"
"github.com/smallnest/rpcx/server"
)
type Args struct {
A int
B int
}
type Reply struct {
C int
}
type Arith int
func (t *Arith) Mul(ctx context.Context, args *Args, reply *Reply) error {
reply.C = args.A * args.B
return nil
}
type PBArith int
func (t *PBArith) Mul(ctx context.Context, args *testutils.ProtoArgs, reply *testutils.ProtoReply) error {
reply.C = args.A * args.B
return nil
}
func (t *Arith) ThriftMul(ctx context.Context, args *testutils.ThriftArgs_, reply *testutils.ThriftReply) error {
reply.C = args.A * args.B
return nil
}
type Bidirectional struct {
*server.Server
}
func (t *Bidirectional) Mul(ctx context.Context, args *Args, reply *Reply) error {
conn := ctx.Value(server.RemoteConnContextKey).(net.Conn)
reply.C = args.A * args.B
t.SendMessage(conn, "test_service_path", "test_service_method", nil, []byte("abcde"))
return nil
}
func TestClient_IT(t *testing.T) {
s := server.NewServer()
_ = s.RegisterName("Arith", new(Arith), "")
_ = s.RegisterName("PBArith", new(PBArith), "")
go func() {
_ = s.Serve("tcp", "127.0.0.1:0")
}()
defer s.Close()
time.Sleep(500 * time.Millisecond)
addr := s.Address().String()
client := &Client{
option: DefaultOption,
}
err := client.Connect("tcp", addr)
if err != nil {
t.Fatalf("failed to connect: %v", err)
}
defer client.Close()
args := &Args{
A: 10,
B: 20,
}
reply := &Reply{}
err = client.Call(context.Background(), "Arith", "Mul", args, reply)
if err != nil {
t.Fatalf("failed to call: %v", err)
}
if reply.C != 200 {
t.Fatalf("expect 200 but got %d", reply.C)
}
err = client.Call(context.Background(), "Arith", "Add", args, reply)
if err == nil {
t.Fatal("expect an error but got nil")
}
client.option.SerializeType = protocol.MsgPack
reply = &Reply{}
err = client.Call(context.Background(), "Arith", "Mul", args, reply)
if err != nil {
t.Fatalf("failed to call: %v", err)
}
if reply.C != 200 {
t.Fatalf("expect 200 but got %d", reply.C)
}
client.option.SerializeType = protocol.ProtoBuffer
pbArgs := &testutils.ProtoArgs{
A: 10,
B: 20,
}
pbReply := &testutils.ProtoReply{}
err = client.Call(context.Background(), "PBArith", "Mul", pbArgs, pbReply)
if err != nil {
t.Fatalf("failed to call: %v", err)
}
if pbReply.C != 200 {
t.Fatalf("expect 200 but got %d", pbReply.C)
}
}
func TestClient_IT_Concurrency(t *testing.T) {
s := server.NewServer()
_ = s.RegisterName("PBArith", new(PBArith), "")
go func() {
_ = s.Serve("tcp", "127.0.0.1:0")
}()
defer s.Close()
time.Sleep(500 * time.Millisecond)
addr := s.Address().String()
client := &Client{
option: DefaultOption,
}
err := client.Connect("tcp", addr)
if err != nil {
t.Fatalf("failed to connect: %v", err)
}
defer client.Close()
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
i := i
go testSendRaw(t, client, uint64(i), rand.Int31(), rand.Int31(), &wg)
}
wg.Wait()
}
func testSendRaw(t *testing.T, client *Client, seq uint64, x, y int32, wg *sync.WaitGroup) {
defer wg.Done()
rpcxReq := protocol.NewMessage()
rpcxReq.SetMessageType(protocol.Request)
rpcxReq.SetSeq(seq)
rpcxReq.ServicePath = "PBArith"
rpcxReq.ServiceMethod = "Mul"
rpcxReq.SetSerializeType(protocol.ProtoBuffer)
rpcxReq.SetOneway(false)
pbArgs := &testutils.ProtoArgs{
A: x,
B: y,
}
data, _ := pbArgs.Marshal()
rpcxReq.Payload = data
_, reply, err := client.SendRaw(context.Background(), rpcxReq)
if err != nil {
t.Errorf("failed to call SendRaw: %v", err)
return
}
pbReply := &testutils.ProtoReply{}
err = pbReply.Unmarshal(reply)
if err != nil {
t.Errorf("failed to unmarshal reply: %v", err)
return
}
if pbReply.C != x*y {
t.Errorf("expect %d but got %d", x*y, pbReply.C)
return
}
}
func TestClient_Res_Reset(t *testing.T) {
var res = protocol.NewMessage()
res.Payload = []byte{1, 2, 3, 4, 5, 6, 7, 8}
data := res.Payload
res.Reset()
if len(data) == 0 {
t.Fatalf("data has been set to empty after response has been reset: %v", data)
}
}
func TestClient_Bidirectional(t *testing.T) {
s := server.NewServer()
_ = s.RegisterName("Bidirectional", &Bidirectional{Server: s}, "")
go func() {
_ = s.Serve("tcp", "127.0.0.1:0")
}()
defer s.Close()
time.Sleep(500 * time.Millisecond)
addr := s.Address().String()
opt := DefaultOption
var receive string
opt.NilCallServerMessageHandler = func(msg *protocol.Message) {
fmt.Printf("receive msg from server: %s\n", msg.Payload)
receive = string(msg.Payload)
}
client := &Client{
option: opt,
}
err := client.Connect("tcp", addr)
if err != nil {
t.Fatalf("failed to connect: %v", err)
}
defer client.Close()
args := &Args{
A: 10,
B: 20,
}
reply := &Reply{}
err = client.Call(context.Background(), "Bidirectional", "Mul", args, reply)
if err != nil {
t.Fatalf("failed to call: %v", err)
}
if receive != "abcde" {
t.Fatalf("expect abcde but got %s", receive)
}
if reply.C != 200 {
t.Fatalf("expect 200 but got %d", reply.C)
}
}
================================================
FILE: client/connection.go
================================================
package client
import (
"bufio"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/http"
"time"
"github.com/smallnest/rpcx/log"
"github.com/smallnest/rpcx/share"
"golang.org/x/net/websocket"
)
type ConnFactoryFn func(c *Client, network, address string) (net.Conn, error)
var ConnFactories = make(map[string]ConnFactoryFn)
func init() {
ConnFactories["http"] = newDirectHTTPConn
ConnFactories["kcp"] = newDirectKCPConn
ConnFactories["quic"] = newDirectQuicConn
ConnFactories["unix"] = newDirectConn
ConnFactories["memu"] = newMemuConn
ConnFactories["iouring"] = newIOUringConn
}
// Connect connects the server via specified network.
func (client *Client) Connect(network, address string) error {
var conn net.Conn
var err error
switch network {
case "http":
conn, err = newDirectHTTPConn(client, network, address)
case "ws", "wss":
conn, err = newDirectWSConn(client, network, address)
default:
fn := ConnFactories[network]
if fn != nil {
conn, err = fn(client, network, address)
} else {
conn, err = newDirectConn(client, network, address)
}
}
if err == nil && conn != nil {
if tc, ok := conn.(*net.TCPConn); ok && client.option.TCPKeepAlivePeriod > 0 {
_ = tc.SetKeepAlive(true)
_ = tc.SetKeepAlivePeriod(client.option.TCPKeepAlivePeriod)
}
if client.option.IdleTimeout != 0 {
_ = conn.SetDeadline(time.Now().Add(client.option.IdleTimeout))
}
if client.Plugins != nil {
conn, err = client.Plugins.DoConnCreated(conn)
if err != nil {
return err
}
}
client.Conn = conn
client.r = bufio.NewReaderSize(conn, ReaderBuffsize)
// c.w = bufio.NewWriterSize(conn, WriterBuffsize)
// start reading and writing since connected
go client.input()
if client.option.Heartbeat && client.option.HeartbeatInterval > 0 {
go client.heartbeat()
}
}
if err != nil && client.Plugins != nil {
client.Plugins.DoConnCreateFailed(network, address)
}
return err
}
func newDirectConn(c *Client, network, address string) (net.Conn, error) {
var conn net.Conn
var tlsConn *tls.Conn
var err error
if c == nil {
err = fmt.Errorf("nil client")
return nil, err
}
if c.option.TLSConfig != nil {
dialer := &net.Dialer{
Timeout: c.option.ConnectTimeout,
}
tlsConn, err = tls.DialWithDialer(dialer, network, address, c.option.TLSConfig)
// or conn:= tls.Client(netConn, &config)
conn = net.Conn(tlsConn)
} else {
conn, err = net.DialTimeout(network, address, c.option.ConnectTimeout)
}
if err != nil {
log.Warnf("failed to dial server: %v", err)
return nil, err
}
return conn, nil
}
var connected = "200 Connected to rpcx"
func newDirectHTTPConn(c *Client, network, address string) (net.Conn, error) {
if c == nil {
return nil, errors.New("empty client")
}
path := c.option.RPCPath
if path == "" {
path = share.DefaultRPCPath
}
var conn net.Conn
var tlsConn *tls.Conn
var err error
if c.option.TLSConfig != nil {
dialer := &net.Dialer{
Timeout: c.option.ConnectTimeout,
}
tlsConn, err = tls.DialWithDialer(dialer, "tcp", address, c.option.TLSConfig)
// or conn:= tls.Client(netConn, &config)
conn = net.Conn(tlsConn)
} else {
conn, err = net.DialTimeout("tcp", address, c.option.ConnectTimeout)
}
if err != nil {
log.Errorf("failed to dial server: %v", err)
return nil, err
}
_, err = io.WriteString(conn, "CONNECT "+path+" HTTP/1.0\n\n")
if err != nil {
// Dial() success but Write() failed here, close the successfully
// created conn before return.
conn.Close()
log.Errorf("failed to make CONNECT: %v", err)
return nil, err
}
// Require successful HTTP response
// before switching to RPC protocol.
resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"})
if err == nil && resp.Status == connected {
return conn, nil
}
if err == nil {
log.Errorf("unexpected HTTP response: %v", err)
err = errors.New("unexpected HTTP response: " + resp.Status)
}
conn.Close()
return nil, &net.OpError{
Op: "dial-http",
Net: network + " " + address,
Addr: nil,
Err: err,
}
}
func newDirectWSConn(c *Client, network, address string) (net.Conn, error) {
if c == nil {
return nil, errors.New("empty client")
}
path := c.option.RPCPath
if path == "" {
path = share.DefaultRPCPath
}
var conn net.Conn
var err error
// url := "ws://localhost:12345/ws"
var url, origin string
if network == "ws" {
url = fmt.Sprintf("ws://%s%s", address, path)
origin = fmt.Sprintf("http://%s", address)
} else {
url = fmt.Sprintf("wss://%s%s", address, path)
origin = fmt.Sprintf("https://%s", address)
}
if c.option.TLSConfig != nil {
config, erri := websocket.NewConfig(url, origin)
if erri != nil {
return nil, erri
}
config.TlsConfig = c.option.TLSConfig
conn, err = websocket.DialConfig(config)
} else {
conn, err = websocket.Dial(url, "", origin)
}
return conn, err
}
================================================
FILE: client/connection_iouring.go
================================================
package client
import (
"net"
)
// experimental
func newIOUringConn(c *Client, network, address string) (net.Conn, error) {
return newDirectConn(c, "tcp", address)
}
================================================
FILE: client/connection_iouring_test.go
================================================
//go:build linux
// +build linux
package client
// func TestXClient_IOUring(t *testing.T) {
// s := server.NewServer()
// s.RegisterName("Arith", new(Arith), "")
// go s.Serve("iouring", "127.0.0.1:8972")
// defer s.Close()
// time.Sleep(500 * time.Millisecond)
// addr := s.Address().String()
// d, err := NewPeer2PeerDiscovery("iouring@"+addr, "desc=a test service")
// if err != nil {
// t.Fatalf("failed to NewPeer2PeerDiscovery: %v", err)
// }
// xclient := NewXClient("Arith", Failtry, RandomSelect, d, DefaultOption)
// defer xclient.Close()
// args := &Args{
// A: 10,
// B: 20,
// }
// reply := &Reply{}
// err = xclient.Call(context.Background(), "Mul", args, reply)
// if err != nil {
// t.Fatalf("failed to call: %v", err)
// }
// if reply.C != 200 {
// t.Fatalf("expect 200 but got %d", reply.C)
// }
// }
================================================
FILE: client/connection_kcp.go
================================================
// +build kcp
package client
import (
"net"
kcp "github.com/xtaci/kcp-go"
)
func newDirectKCPConn(c *Client, network, address string) (net.Conn, error) {
return kcp.DialWithOptions(address, c.option.Block.(kcp.BlockCrypt), 10, 3)
}
================================================
FILE: client/connection_memu.go
================================================
package client
import (
"net"
"github.com/akutz/memconn"
)
func newMemuConn(c *Client, network, address string) (net.Conn, error) {
return memconn.Dial(network, address)
}
================================================
FILE: client/connection_nonkcp.go
================================================
// +build !kcp
package client
import (
"errors"
"net"
)
func newDirectKCPConn(c *Client, network, address string) (net.Conn, error) {
return nil, errors.New("kcp unsupported")
}
================================================
FILE: client/connection_nonquic.go
================================================
//go:build !quic
// +build !quic
package client
import (
"errors"
"net"
)
func newDirectQuicConn(c *Client, network, address string) (net.Conn, error) {
return nil, errors.New("quic unsupported")
}
================================================
FILE: client/connection_quic.go
================================================
//go:build quic
// +build quic
package client
import (
"crypto/tls"
"net"
"github.com/quic-go/quic-go"
"github.com/smallnest/quick"
)
func newDirectQuicConn(c *Client, network, address string) (net.Conn, error) {
tlsConf := c.option.TLSConfig
if tlsConf == nil {
tlsConf = &tls.Config{InsecureSkipVerify: true}
}
if len(tlsConf.NextProtos) == 0 {
tlsConf.NextProtos = []string{"rpcx"}
}
quicConfig := &quic.Config{}
return quick.Dial(address, tlsConf, quicConfig)
}
================================================
FILE: client/connection_rdma.go
================================================
//go:build rdma
// +build rdma
package client
import (
"errors"
"net"
"github.com/smallnest/rsocket"
)
func init() {
ConnFactories["rdma"] = newRDMAConn
}
func newRDMAConn(c *Client, network, address string) (net.Conn, error) {
if network != "rdma" {
return nil, errors.New("network is not rdma")
}
return rsocket.DialTCP(address)
}
================================================
FILE: client/discovery.go
================================================
package client
import (
"encoding/json"
"os"
"path/filepath"
"sync"
)
// ServiceDiscoveryFilter can be used to filter services with customized logics.
// Servers can register its services but clients can use the customized filter to select some services.
// It returns true if ServiceDiscovery wants to use this service, otherwise it returns false.
type ServiceDiscoveryFilter func(kvp *KVPair) bool
// ServiceDiscovery defines ServiceDiscovery of zookeeper, etcd and consul
type ServiceDiscovery interface {
GetServices() []*KVPair // return all services in the registry
WatchService() chan []*KVPair // watch the change of services, it's a golang channel
RemoveWatcher(ch chan []*KVPair)
Clone(servicePath string) (ServiceDiscovery, error)
SetFilter(ServiceDiscoveryFilter) // set customized filter to filter services
Close()
}
type cachedServiceDiscovery struct {
threshold int
cachedFile string
cached []*KVPair
chansLock sync.RWMutex
chans map[chan []*KVPair]chan []*KVPair
ServiceDiscovery
}
// CacheDiscovery caches the services in a file, it will return the cached services if the number of services is greater than threshold.
// It is very useful when the register center is lost.
func CacheDiscovery(threshold int, cachedFile string, discovery ServiceDiscovery) ServiceDiscovery {
if cachedFile == "" {
cachedFile = ".cache/discovery.json"
}
cachedFileDir := filepath.Dir(cachedFile)
if _, err := os.Stat(cachedFileDir); os.IsNotExist(err) {
// 目录不存在,创建目录
if err := os.MkdirAll(cachedFileDir, os.ModePerm); err != nil {
panic(err)
}
}
return &cachedServiceDiscovery{
threshold: threshold,
cachedFile: cachedFile,
ServiceDiscovery: discovery,
chans: make(map[chan []*KVPair]chan []*KVPair),
}
}
func (cd *cachedServiceDiscovery) GetServices() []*KVPair {
kvPairs := cd.ServiceDiscovery.GetServices()
n := len(kvPairs)
if n > cd.threshold {
if n > len(cd.cached) { // strictly we should compare the content of the cached file, but only compare the length for performance
cd.cached = kvPairs
cd.storeCached(kvPairs)
}
return kvPairs
}
if len(cd.cached) == 0 {
cd.loadCached()
}
return cd.cached
}
func (cd *cachedServiceDiscovery) WatchService() chan []*KVPair {
ch := cd.ServiceDiscovery.WatchService()
cachedCh := make(chan []*KVPair, 10)
cd.chansLock.Lock()
cd.chans[cachedCh] = ch
cd.chansLock.Unlock()
go func() {
defer recover()
for {
kvPairs, ok := <-ch
if !ok {
close(cachedCh)
return
}
n := len(kvPairs)
if n > len(cd.cached) {
cd.cached = kvPairs
cd.storeCached(kvPairs)
}
cachedCh <- kvPairs
}
}()
return cachedCh
}
func (cd *cachedServiceDiscovery) RemoveWatcher(ch chan []*KVPair) {
cd.chansLock.Lock()
origin := cd.chans[ch]
delete(cd.chans, ch)
cd.chansLock.Unlock()
if origin != nil {
cd.ServiceDiscovery.RemoveWatcher(origin)
}
}
func (cd *cachedServiceDiscovery) storeCached(kvPairs []*KVPair) {
data, _ := json.Marshal(kvPairs)
os.WriteFile(cd.cachedFile, data, 0644)
}
func (cd *cachedServiceDiscovery) loadCached() (kvPairs []*KVPair) {
data, err := os.ReadFile(cd.cachedFile)
if err != nil || len(data) == 0 {
return
}
json.Unmarshal(data, &kvPairs)
return kvPairs
}
================================================
FILE: client/dns_discovery.go
================================================
package client
import (
"fmt"
"net"
"sort"
"sync"
"time"
"github.com/smallnest/rpcx/log"
)
// DNSDiscovery is based on DNS a record.
// You must set port and network info when you create the DNSDiscovery.
type DNSDiscovery struct {
domain string
network string
port int
d time.Duration
pairsMu sync.RWMutex
pairs []*KVPair
chans []chan []*KVPair
mu sync.Mutex
filter ServiceDiscoveryFilter
stopCh chan struct{}
}
// NewDNSDiscovery returns a new DNSDiscovery.
func NewDNSDiscovery(domain string, network string, port int, d time.Duration) (*DNSDiscovery, error) {
discovery := &DNSDiscovery{domain: domain, network: network, port: port, d: d}
discovery.lookup()
go discovery.watch()
return discovery, nil
}
// Clone clones this ServiceDiscovery with new servicePath.
func (d *DNSDiscovery) Clone(servicePath string) (ServiceDiscovery, error) {
return NewDNSDiscovery(d.domain, d.network, d.port, d.d)
}
// SetFilter sets the filer.
func (d *DNSDiscovery) SetFilter(filter ServiceDiscoveryFilter) {
d.filter = filter
}
// GetServices returns the static server
func (d *DNSDiscovery) GetServices() []*KVPair {
d.pairsMu.RLock()
defer d.pairsMu.RUnlock()
return d.pairs
}
// WatchService returns a nil chan.
func (d *DNSDiscovery) WatchService() chan []*KVPair {
d.mu.Lock()
defer d.mu.Unlock()
ch := make(chan []*KVPair, 10)
d.chans = append(d.chans, ch)
return ch
}
func (d *DNSDiscovery) RemoveWatcher(ch chan []*KVPair) {
d.mu.Lock()
defer d.mu.Unlock()
var chans []chan []*KVPair
for _, c := range d.chans {
if c == ch {
continue
}
chans = append(chans, c)
}
d.chans = chans
}
func (d *DNSDiscovery) lookup() {
var pairs []*KVPair // latest servers
ips, err := net.LookupIP(d.domain)
if err != nil {
log.Errorf("failed to lookup %s: %v", d.domain, err)
return
}
for _, ip := range ips {
pair := &KVPair{Key: fmt.Sprintf("%s@%s:%d", d.network, ip.String(), d.port)}
if d.filter != nil && !d.filter(pair) {
continue
}
pairs = append(pairs, pair)
}
if len(pairs) > 0 {
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].Key < pairs[j].Key
})
}
d.pairsMu.Lock()
d.pairs = pairs
d.pairsMu.Unlock()
d.mu.Lock()
for _, ch := range d.chans {
ch := ch
go func() {
defer func() {
recover()
}()
select {
case ch <- pairs:
case <-time.After(time.Minute):
log.Warn("chan is full and new change has been dropped")
}
}()
}
d.mu.Unlock()
}
func (d *DNSDiscovery) watch() {
tick := time.NewTicker(d.d)
defer tick.Stop()
for {
select {
case <-d.stopCh:
return
case <-tick.C:
d.lookup()
}
}
}
func (d *DNSDiscovery) Close() {
close(d.stopCh)
}
================================================
FILE: client/failmode_enumer.go
================================================
// Code generated by "enumer -type=FailMode"; DO NOT EDIT.
package client
import (
"fmt"
)
const _FailModeName = "FailoverFailfastFailtryFailbackup"
var _FailModeIndex = [...]uint8{0, 8, 16, 23, 33}
func (i FailMode) String() string {
if i < 0 || i >= FailMode(len(_FailModeIndex)-1) {
return fmt.Sprintf("FailMode(%d)", i)
}
return _FailModeName[_FailModeIndex[i]:_FailModeIndex[i+1]]
}
var _FailModeValues = []FailMode{0, 1, 2, 3}
var _FailModeNameToValueMap = map[string]FailMode{
_FailModeName[0:8]: 0,
_FailModeName[8:16]: 1,
_FailModeName[16:23]: 2,
_FailModeName[23:33]: 3,
}
// FailModeString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func FailModeString(s string) (FailMode, error) {
if val, ok := _FailModeNameToValueMap[s]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to FailMode values", s)
}
// FailModeValues returns all values of the enum
func FailModeValues() []FailMode {
return _FailModeValues
}
// IsAFailMode returns "true" if the value is listed in the enum definition. "false" otherwise
func (i FailMode) IsAFailMode() bool {
for _, v := range _FailModeValues {
if i == v {
return true
}
}
return false
}
================================================
FILE: client/geo_utils.go
================================================
package client
import (
"math"
)
// https://gist.github.com/cdipaolo/d3f8db3848278b49db68
func getDistanceFrom(lat1, lon1, lat2, lon2 float64) float64 {
var la1, lo1, la2, lo2, r float64
la1 = lat1 * math.Pi / 180
lo1 = lon1 * math.Pi / 180
la2 = lat2 * math.Pi / 180
lo2 = lon2 * math.Pi / 180
r = 6378100 // Earth radius in METERS
// calculate
h := hsin(la2-la1) + math.Cos(la1)*math.Cos(la2)*hsin(lo2-lo1)
return 2 * r * math.Asin(math.Sqrt(h))
}
func hsin(theta float64) float64 {
return math.Pow(math.Sin(theta/2), 2)
}
================================================
FILE: client/hash_utils.go
================================================
package client
import (
"fmt"
"hash/fnv"
)
// Hash consistently chooses a hash bucket number in the range [0, numBuckets) for the given key. numBuckets must be >= 1.
func Hash(key uint64, buckets int32) int32 {
if buckets <= 0 {
buckets = 1
}
var b, j int64
for j < int64(buckets) {
b = j
key = key*2862933555777941757 + 1
j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))
}
return int32(b)
}
// HashString get a hash value of a string
func HashString(s string) uint64 {
h := fnv.New64a()
h.Write([]byte(s))
return h.Sum64()
}
// HashServiceAndArgs define a hash function
type HashServiceAndArgs func(len int, options ...interface{}) int
// ConsistentFunction define a hash function
// Return service address, like "tcp@127.0.0.1:8970"
type ConsistentAddrStrFunction func(options ...interface{}) string
func genKey(options ...interface{}) uint64 {
keyString := ""
for _, opt := range options {
keyString = keyString + "/" + toString(opt)
}
return HashString(keyString)
}
// JumpConsistentHash selects a server by serviceMethod and args
func JumpConsistentHash(len int, options ...interface{}) int {
return int(Hash(genKey(options...), int32(len)))
}
func toString(obj interface{}) string {
return fmt.Sprintf("%v", obj)
}
================================================
FILE: client/mdns_discovery.go
================================================
package client
import (
"context"
"encoding/json"
"net/url"
"sync"
"time"
"github.com/grandcat/zeroconf"
"github.com/smallnest/rpcx/log"
)
type serviceMeta struct {
Service string `json:"service,omitempty"`
Meta string `json:"meta,omitempty"`
ServiceAddress string `json:"service_address,omitempty"`
}
// MDNSDiscovery is a mdns service discovery.
// It always returns the registered servers in mdns.
type MDNSDiscovery struct {
Timeout time.Duration
WatchInterval time.Duration
domain string
service string
pairsMu sync.RWMutex
pairs []*KVPair
chans []chan []*KVPair
mu sync.Mutex
filter ServiceDiscoveryFilter
stopCh chan struct{}
}
// NewMDNSDiscovery returns a new MDNSDiscovery.
// If domain is empty, use "local." in default.
func NewMDNSDiscovery(service string, timeout time.Duration, watchInterval time.Duration, domain string) (*MDNSDiscovery, error) {
if domain == "" {
domain = "local."
}
d := &MDNSDiscovery{service: service, Timeout: timeout, WatchInterval: watchInterval, domain: domain}
d.stopCh = make(chan struct{})
var err error
d.pairsMu.Lock()
d.pairs, err = d.browse()
d.pairsMu.Unlock()
if err != nil {
log.Warnf("failed to browse services: %v", err)
}
go d.watch()
return d, nil
}
// Clone clones this ServiceDiscovery with new servicePath.
func (d *MDNSDiscovery) Clone(servicePath string) (ServiceDiscovery, error) {
return NewMDNSDiscovery(servicePath, d.Timeout, d.WatchInterval, d.domain)
}
// SetFilter sets the filer.
func (d *MDNSDiscovery) SetFilter(filter ServiceDiscoveryFilter) {
d.filter = filter
}
// GetServices returns the servers
func (d *MDNSDiscovery) GetServices() []*KVPair {
d.pairsMu.RLock()
defer d.pairsMu.RUnlock()
return d.pairs
}
// WatchService returns a nil chan.
func (d *MDNSDiscovery) WatchService() chan []*KVPair {
d.mu.Lock()
defer d.mu.Unlock()
ch := make(chan []*KVPair, 10)
d.chans = append(d.chans, ch)
return ch
}
func (d *MDNSDiscovery) RemoveWatcher(ch chan []*KVPair) {
d.mu.Lock()
defer d.mu.Unlock()
var chans []chan []*KVPair
for _, c := range d.chans {
if c == ch {
continue
}
chans = append(chans, c)
}
d.chans = chans
}
func (d *MDNSDiscovery) watch() {
t := time.NewTicker(d.WatchInterval)
for {
select {
case <-d.stopCh:
t.Stop()
log.Info("discovery has been closed")
return
case <-t.C:
pairs, err := d.browse()
if err == nil {
d.pairsMu.Lock()
d.pairs = pairs
d.pairsMu.Unlock()
d.mu.Lock()
for _, ch := range d.chans {
ch := ch
go func() {
defer func() {
recover()
}()
select {
case ch <- pairs:
case <-time.After(time.Minute):
log.Warn("chan is full and new change has ben dropped")
}
}()
}
d.mu.Unlock()
}
}
}
}
func (d *MDNSDiscovery) browse() ([]*KVPair, error) {
resolver, err := zeroconf.NewResolver(nil)
if err != nil {
log.Warnf("Failed to initialize resolver: %v", err)
return nil, err
}
entries := make(chan *zeroconf.ServiceEntry)
var totalServices []*KVPair
var services []*serviceMeta
done := make(chan struct{})
go func(results <-chan *zeroconf.ServiceEntry) {
for entry := range entries {
s, _ := url.QueryUnescape(entry.Text[0])
err := json.Unmarshal([]byte(s), &services)
if err != nil {
log.Warnf("Failed to browse: %v", err)
continue
}
for _, sm := range services {
pair := &KVPair{
Key: sm.ServiceAddress,
Value: sm.Meta,
}
if d.filter != nil && !d.filter(pair) {
continue
}
totalServices = append(totalServices, pair)
}
}
close(done)
}(entries)
ctx, cancel := context.WithTimeout(context.Background(), d.Timeout)
defer cancel()
err = resolver.Browse(ctx, "_rpcxservices", d.domain, entries)
if err != nil {
log.Warnf("Failed to browse: %v", err)
}
<-done
return totalServices, nil
}
func (d *MDNSDiscovery) Close() {
close(d.stopCh)
}
================================================
FILE: client/mode.go
================================================
package client
// FailMode decides how clients action when clients fail to invoke services
type FailMode int
const (
// Failover selects another server automaticaly
Failover FailMode = iota
// Failfast returns error immediately
Failfast
// Failtry use current client again
Failtry
// Failbackup select another server if the first server doesn't respond in specified time and use the fast response.
Failbackup
)
// SelectMode defines the algorithm of selecting a services from candidates.
type SelectMode int
const (
// RandomSelect is selecting randomly
RandomSelect SelectMode = iota
// RoundRobin is selecting by round robin
RoundRobin
// WeightedRoundRobin is selecting by weighted round robin
WeightedRoundRobin
// WeightedICMP is selecting by weighted Ping time
WeightedICMP
// ConsistentHash is selecting by hashing
ConsistentHash
// Closest is selecting the closest server
Closest
// SelectByUser is selecting by implementation of users
SelectByUser = 1000
)
================================================
FILE: client/multiple_servers_discovery.go
================================================
package client
import (
"sync"
"time"
"github.com/smallnest/rpcx/log"
)
// MultipleServersDiscovery is a multiple servers service discovery.
// It always returns the current servers and users can change servers dynamically.
type MultipleServersDiscovery struct {
pairsMu sync.RWMutex
pairs []*KVPair
chans []chan []*KVPair
mu sync.Mutex
}
// NewMultipleServersDiscovery returns a new MultipleServersDiscovery.
func NewMultipleServersDiscovery(pairs []*KVPair) (*MultipleServersDiscovery, error) {
return &MultipleServersDiscovery{
pairs: pairs,
}, nil
}
// Clone clones this ServiceDiscovery with new servicePath.
func (d *MultipleServersDiscovery) Clone(servicePath string) (ServiceDiscovery, error) {
return d, nil
}
// SetFilter sets the filer.
func (d *MultipleServersDiscovery) SetFilter(filter ServiceDiscoveryFilter) {
}
// GetServices returns the configured server
func (d *MultipleServersDiscovery) GetServices() []*KVPair {
d.pairsMu.RLock()
defer d.pairsMu.RUnlock()
return d.pairs
}
// WatchService returns a nil chan.
func (d *MultipleServersDiscovery) WatchService() chan []*KVPair {
d.mu.Lock()
defer d.mu.Unlock()
ch := make(chan []*KVPair, 10)
d.chans = append(d.chans, ch)
return ch
}
func (d *MultipleServersDiscovery) RemoveWatcher(ch chan []*KVPair) {
d.mu.Lock()
defer d.mu.Unlock()
var chans []chan []*KVPair
for _, c := range d.chans {
if c == ch {
continue
}
chans = append(chans, c)
}
d.chans = chans
}
// Update is used to update servers at runtime.
func (d *MultipleServersDiscovery) Update(pairs []*KVPair) {
d.mu.Lock()
defer d.mu.Unlock()
for _, ch := range d.chans {
ch := ch
go func() {
defer func() {
recover()
}()
select {
case ch <- pairs:
case <-time.After(time.Minute):
log.Warn("chan is full and new change has been dropped")
}
}()
}
d.pairsMu.Lock()
d.pairs = pairs
d.pairsMu.Unlock()
}
func (d *MultipleServersDiscovery) Close() {
}
================================================
FILE: client/oneclient.go
================================================
package client
import (
"context"
"fmt"
"io"
"net"
"sync"
"github.com/smallnest/rpcx/share"
multierror "github.com/hashicorp/go-multierror"
"github.com/smallnest/rpcx/protocol"
)
// OneClient wraps servicesPath and XClients.
// Users can use a shared oneclient to access multiple services.
type OneClient struct {
xclients map[string]XClient
mu sync.RWMutex
failMode FailMode
selectMode SelectMode
discovery ServiceDiscovery
option Option
selectors map[string]Selector
Plugins PluginContainer
latitude float64
longitude float64
auth string
serverMessageChan chan<- *protocol.Message
}
// NewOneClient creates a OneClient that supports service discovery and service governance.
func NewOneClient(failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) *OneClient {
return &OneClient{
failMode: failMode,
selectMode: selectMode,
discovery: discovery,
option: option,
xclients: make(map[string]XClient),
selectors: make(map[string]Selector),
}
}
// NewBidirectionalOneClient creates a new xclient that can receive notifications from servers.
func NewBidirectionalOneClient(failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option, serverMessageChan chan<- *protocol.Message) *OneClient {
return &OneClient{
failMode: failMode,
selectMode: selectMode,
discovery: discovery,
option: option,
xclients: make(map[string]XClient),
selectors: make(map[string]Selector),
serverMessageChan: serverMessageChan,
}
}
// SetSelector sets customized selector by users.
func (c *OneClient) SetSelector(servicePath string, s Selector) {
c.mu.Lock()
c.selectors[servicePath] = s
if xclient, ok := c.xclients[servicePath]; ok {
xclient.SetSelector(s)
}
c.mu.Unlock()
}
// SetPlugins sets client's plugins.
func (c *OneClient) SetPlugins(plugins PluginContainer) {
c.Plugins = plugins
c.mu.RLock()
for _, v := range c.xclients {
v.SetPlugins(plugins)
}
c.mu.RUnlock()
}
func (c *OneClient) GetPlugins() PluginContainer {
return c.Plugins
}
// ConfigGeoSelector sets location of client's latitude and longitude,
// and use newGeoSelector.
func (c *OneClient) ConfigGeoSelector(latitude, longitude float64) {
c.selectMode = Closest
c.latitude = latitude
c.longitude = longitude
c.mu.RLock()
for _, v := range c.xclients {
v.ConfigGeoSelector(latitude, longitude)
}
c.mu.RUnlock()
}
// Auth sets s token for Authentication.
func (c *OneClient) Auth(auth string) {
c.auth = auth
c.mu.RLock()
for _, v := range c.xclients {
v.Auth(auth)
}
c.mu.RUnlock()
}
// Go invokes the function asynchronously. It returns the Call structure representing the invocation. The done channel will signal when the call is complete by returning the same Call object. If done is nil, Go will allocate a new channel. If non-nil, done must be buffered or Go will deliberately crash.
// It does not use FailMode.
func (c *OneClient) Go(ctx context.Context, servicePath string, serviceMethod string, args interface{}, reply interface{}, done chan *Call) (*Call, error) {
c.mu.RLock()
xclient := c.xclients[servicePath]
c.mu.RUnlock()
if xclient == nil {
var err error
c.mu.Lock()
xclient = c.xclients[servicePath]
if xclient == nil {
xclient, err = c.newXClient(servicePath)
c.xclients[servicePath] = xclient
}
c.mu.Unlock()
if err != nil {
return nil, err
}
}
return xclient.Go(ctx, serviceMethod, args, reply, done)
}
func (c *OneClient) newXClient(servicePath string) (xclient XClient, err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = e
} else {
err = fmt.Errorf("%v", r)
}
}
}()
d, err := c.discovery.Clone(servicePath)
if err != nil {
return nil, err
}
if c.serverMessageChan == nil {
xclient = NewXClient(servicePath, c.failMode, c.selectMode, d, c.option)
} else {
xclient = NewBidirectionalXClient(servicePath, c.failMode, c.selectMode, d, c.option, c.serverMessageChan)
}
if c.Plugins != nil {
xclient.SetPlugins(c.Plugins)
}
if s, ok := c.selectors[servicePath]; ok {
xclient.SetSelector(s)
}
if c.selectMode == Closest {
xclient.ConfigGeoSelector(c.latitude, c.longitude)
}
if c.auth != "" {
xclient.Auth(c.auth)
}
return xclient, err
}
// Call invokes the named function, waits for it to complete, and returns its error status.
// It handles errors base on FailMode.
func (c *OneClient) Call(ctx context.Context, servicePath string, serviceMethod string, args interface{}, reply interface{}) error {
c.mu.RLock()
xclient := c.xclients[servicePath]
c.mu.RUnlock()
if xclient == nil {
var err error
c.mu.Lock()
xclient = c.xclients[servicePath]
if xclient == nil {
xclient, err = c.newXClient(servicePath)
c.xclients[servicePath] = xclient
}
c.mu.Unlock()
if err != nil {
return err
}
}
return xclient.Call(ctx, serviceMethod, args, reply)
}
func (c *OneClient) SendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error) {
servicePath := r.ServicePath
c.mu.RLock()
xclient := c.xclients[servicePath]
c.mu.RUnlock()
if xclient == nil {
var err error
c.mu.Lock()
xclient = c.xclients[servicePath]
if xclient == nil {
xclient, err = c.newXClient(servicePath)
c.xclients[servicePath] = xclient
}
c.mu.Unlock()
if err != nil {
return nil, nil, err
}
}
return xclient.SendRaw(ctx, r)
}
// Broadcast sends requests to all servers and Success only when all servers return OK.
// FailMode and SelectMode are meanless for this method.
// Please set timeout to avoid hanging.
func (c *OneClient) Broadcast(ctx context.Context, servicePath string, serviceMethod string, args interface{}, reply interface{}) error {
c.mu.RLock()
xclient := c.xclients[servicePath]
c.mu.RUnlock()
if xclient == nil {
var err error
c.mu.Lock()
xclient = c.xclients[servicePath]
if xclient == nil {
xclient, err = c.newXClient(servicePath)
c.xclients[servicePath] = xclient
}
c.mu.Unlock()
if err != nil {
return err
}
}
return xclient.Broadcast(ctx, serviceMethod, args, reply)
}
// Fork sends requests to all servers and Success once one server returns OK.
// FailMode and SelectMode are meanless for this method.
func (c *OneClient) Fork(ctx context.Context, servicePath string, serviceMethod string, args interface{}, reply interface{}) error {
c.mu.RLock()
xclient := c.xclients[servicePath]
c.mu.RUnlock()
if xclient == nil {
var err error
c.mu.Lock()
xclient = c.xclients[servicePath]
if xclient == nil {
xclient, err = c.newXClient(servicePath)
c.xclients[servicePath] = xclient
}
c.mu.Unlock()
if err != nil {
return err
}
}
return xclient.Fork(ctx, serviceMethod, args, reply)
}
func (c *OneClient) SendFile(ctx context.Context, fileName string, rateInBytesPerSecond int64, meta map[string]string) error {
c.mu.RLock()
xclient := c.xclients[share.SendFileServiceName]
c.mu.RUnlock()
if xclient == nil {
var err error
c.mu.Lock()
xclient = c.xclients[share.SendFileServiceName]
if xclient == nil {
xclient, err = c.newXClient(share.SendFileServiceName)
c.xclients[share.SendFileServiceName] = xclient
}
c.mu.Unlock()
if err != nil {
return err
}
}
return xclient.SendFile(ctx, fileName, rateInBytesPerSecond, meta)
}
func (c *OneClient) DownloadFile(ctx context.Context, requestFileName string, saveTo io.Writer, meta map[string]string) error {
c.mu.RLock()
xclient := c.xclients[share.SendFileServiceName]
c.mu.RUnlock()
if xclient == nil {
var err error
c.mu.Lock()
xclient = c.xclients[share.SendFileServiceName]
if xclient == nil {
xclient, err = c.newXClient(share.SendFileServiceName)
c.xclients[share.SendFileServiceName] = xclient
}
c.mu.Unlock()
if err != nil {
return err
}
}
return xclient.DownloadFile(ctx, requestFileName, saveTo, meta)
}
func (c *OneClient) Stream(ctx context.Context, meta map[string]string) (net.Conn, error) {
c.mu.RLock()
xclient := c.xclients[share.StreamServiceName]
c.mu.RUnlock()
if xclient == nil {
var err error
c.mu.Lock()
xclient = c.xclients[share.StreamServiceName]
if xclient == nil {
xclient, err = c.newXClient(share.StreamServiceName)
c.xclients[share.StreamServiceName] = xclient
}
c.mu.Unlock()
if err != nil {
return nil, err
}
}
return xclient.Stream(ctx, meta)
}
// Close closes all xclients and its underlying connections to services.
func (c *OneClient) Close() error {
var result error
c.mu.RLock()
for _, v := range c.xclients {
err := v.Close()
if err != nil {
result = multierror.Append(result, err)
}
}
c.mu.RUnlock()
return result
}
================================================
FILE: client/oneclient_pool.go
================================================
package client
import (
"sync/atomic"
"github.com/smallnest/rpcx/protocol"
)
// OneClientPool is a oneclient pool with fixed size.
// It uses roundrobin algorithm to call its xclients.
// All oneclients share the same configurations such as ServiceDiscovery and serverMessageChan.
type OneClientPool struct {
count uint64
index uint64
oneclients []*OneClient
auth string
Plugins PluginContainer
failMode FailMode
selectMode SelectMode
discovery ServiceDiscovery
option Option
serverMessageChan chan<- *protocol.Message
}
// NewOneClientPool creates a fixed size OneClient pool.
func NewOneClientPool(count int, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) *OneClientPool {
pool := &OneClientPool{
count: uint64(count),
oneclients: make([]*OneClient, count),
failMode: failMode,
selectMode: selectMode,
discovery: discovery,
option: option,
}
for i := 0; i < count; i++ {
oneclient := NewOneClient(failMode, selectMode, discovery, option)
pool.oneclients[i] = oneclient
}
return pool
}
// NewBidirectionalOneClientPool creates a BidirectionalOneClient pool with fixed size.
func NewBidirectionalOneClientPool(count int, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option, serverMessageChan chan<- *protocol.Message) *OneClientPool {
pool := &OneClientPool{
count: uint64(count),
oneclients: make([]*OneClient, count),
failMode: failMode,
selectMode: selectMode,
discovery: discovery,
option: option,
serverMessageChan: serverMessageChan,
}
for i := 0; i < count; i++ {
oneclient := NewBidirectionalOneClient(failMode, selectMode, discovery, option, serverMessageChan)
pool.oneclients[i] = oneclient
}
return pool
}
// Auth sets s token for Authentication.
func (p *OneClientPool) Auth(auth string) {
p.auth = auth
for _, v := range p.oneclients {
v.Auth(auth)
}
}
// SetPlugins sets client's plugins.
func (p *OneClientPool) SetPlugins(plugins PluginContainer) {
p.Plugins = plugins
for _, v := range p.oneclients {
v.SetPlugins(plugins)
}
}
// GetPlugins returns client's plugins.
func (p *OneClientPool) GetPlugins() PluginContainer {
return p.Plugins
}
// Get returns a OneClient.
// It does not remove this OneClient from its cache so you don't need to put it back.
// Don't close this OneClient because maybe other goroutines are using this OneClient.
func (p *OneClientPool) Get() *OneClient {
i := atomic.AddUint64(&p.index, 1)
picked := int(i % p.count)
return p.oneclients[picked]
}
// Close this pool.
// Please make sure it won't be used any more.
func (p *OneClientPool) Close() {
for _, c := range p.oneclients {
c.Close()
}
p.oneclients = nil
}
================================================
FILE: client/oneclient_pool_test.go
================================================
package client
import (
"testing"
)
func TestOneClientPool_SetPlugins(t *testing.T) {
// Create a simple discovery
pairs := []*KVPair{
{Key: "tcp@127.0.0.1:8972", Value: ""},
}
discovery, err := NewMultipleServersDiscovery(pairs)
if err != nil {
t.Fatalf("failed to create discovery: %v", err)
}
defer discovery.Close()
// Create a pool
pool := NewOneClientPool(3, Failtry, RandomSelect, discovery, DefaultOption)
defer pool.Close()
// Create plugins
plugins := NewPluginContainer()
tp := &testPlugin{name: "test-plugin"}
plugins.Add(tp)
// Test SetPlugins
pool.SetPlugins(plugins)
// Verify plugins are set on pool
if pool.GetPlugins() == nil {
t.Error("plugins should not be nil after SetPlugins")
}
if pool.GetPlugins() != plugins {
t.Error("pool plugins should be the same as the set plugins")
}
// Verify plugins are set on all oneclients
for i := 0; i < 3; i++ {
oneclient := pool.Get()
if oneclient.GetPlugins() == nil {
t.Errorf("oneclient %d plugins should not be nil", i)
}
if oneclient.GetPlugins() != plugins {
t.Errorf("oneclient %d plugins should be the same as the set plugins", i)
}
}
}
func TestOneClientPool_GetPlugins(t *testing.T) {
// Create a simple discovery
pairs := []*KVPair{
{Key: "tcp@127.0.0.1:8972", Value: ""},
}
discovery, err := NewMultipleServersDiscovery(pairs)
if err != nil {
t.Fatalf("failed to create discovery: %v", err)
}
defer discovery.Close()
// Create a pool
pool := NewOneClientPool(2, Failtry, RandomSelect, discovery, DefaultOption)
defer pool.Close()
// Initially, plugins should be nil
if pool.GetPlugins() != nil {
t.Error("plugins should be nil initially")
}
// Create and set plugins
plugins := NewPluginContainer()
tp := &testPlugin{name: "test-plugin"}
plugins.Add(tp)
pool.SetPlugins(plugins)
// Verify GetPlugins returns the correct plugins
retrievedPlugins := pool.GetPlugins()
if retrievedPlugins == nil {
t.Error("plugins should not be nil after SetPlugins")
}
if retrievedPlugins != plugins {
t.Error("GetPlugins should return the same plugins as SetPlugins")
}
// Verify plugins contain the test plugin
allPlugins := retrievedPlugins.All()
if len(allPlugins) != 1 {
t.Errorf("expected 1 plugin, got %d", len(allPlugins))
}
if p, ok := allPlugins[0].(*testPlugin); !ok || p.name != "test-plugin" {
t.Error("plugin should be the test plugin")
}
}
func TestOneClientPool_SetPlugins_Concurrent(t *testing.T) {
// Create a simple discovery
pairs := []*KVPair{
{Key: "tcp@127.0.0.1:8972", Value: ""},
}
discovery, err := NewMultipleServersDiscovery(pairs)
if err != nil {
t.Fatalf("failed to create discovery: %v", err)
}
defer discovery.Close()
// Create a pool
pool := NewOneClientPool(5, Failtry, RandomSelect, discovery, DefaultOption)
defer pool.Close()
// Test concurrent SetPlugins calls
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func(id int) {
plugins := NewPluginContainer()
tp := &testPlugin{name: "test-plugin"}
plugins.Add(tp)
pool.SetPlugins(plugins)
done <- true
}(i)
}
// Wait for all goroutines to complete
for i := 0; i < 10; i++ {
<-done
}
// Verify plugins are set
if pool.GetPlugins() == nil {
t.Error("plugins should not be nil after concurrent SetPlugins")
}
}
================================================
FILE: client/peer2peer_discovery.go
================================================
package client
// Peer2PeerDiscovery is a peer-to-peer service discovery.
// It always returns the static server.
type Peer2PeerDiscovery struct {
server string
metadata string
}
// NewPeer2PeerDiscovery returns a new Peer2PeerDiscovery.
func NewPeer2PeerDiscovery(server, metadata string) (*Peer2PeerDiscovery, error) {
return &Peer2PeerDiscovery{server: server, metadata: metadata}, nil
}
// Clone clones this ServiceDiscovery with new servicePath.
func (d *Peer2PeerDiscovery) Clone(servicePath string) (ServiceDiscovery, error) {
return d, nil
}
// SetFilter sets the filer.
func (d *Peer2PeerDiscovery) SetFilter(filter ServiceDiscoveryFilter) {
}
// GetServices returns the static server
func (d *Peer2PeerDiscovery) GetServices() []*KVPair {
return []*KVPair{&KVPair{Key: d.server, Value: d.metadata}}
}
// WatchService returns a nil chan.
func (d *Peer2PeerDiscovery) WatchService() chan []*KVPair {
return nil
}
func (d *Peer2PeerDiscovery) RemoveWatcher(ch chan []*KVPair) {}
func (d *Peer2PeerDiscovery) Close() {
}
================================================
FILE: client/ping_utils.go
================================================
package client
import (
"context"
"net"
"strings"
"time"
ping "github.com/go-ping/ping"
)
// weightedICMPSelector selects servers with ping result.
type weightedICMPSelector struct {
servers []*Weighted
wrs *weightedRoundRobinSelector
}
func newWeightedICMPSelector(servers map[string]string) Selector {
ss := createICMPWeighted(servers)
wicmps := weightedICMPSelector{
servers: ss,
wrs: &weightedRoundRobinSelector{servers: ss},
}
wicmps.wrs.servers = ss
wicmps.wrs.buildRing()
return &wicmps
}
func (s *weightedICMPSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {
return s.wrs.Select(ctx, servicePath, serviceMethod, args)
}
func (s *weightedICMPSelector) UpdateServer(servers map[string]string) {
ss := createICMPWeighted(servers)
s.wrs.servers = ss
s.servers = ss
s.wrs.buildRing()
}
func createICMPWeighted(servers map[string]string) []*Weighted {
var ss = make([]*Weighted, 0, len(servers))
for k := range servers {
w := &Weighted{Server: k, Weight: 1, EffectiveWeight: 1}
server := strings.Split(k, "@")
host, _, _ := net.SplitHostPort(server[1])
rtt, _ := Ping(host)
rtt = CalculateWeight(rtt)
w.Weight = rtt
w.EffectiveWeight = rtt
ss = append(ss, w)
}
return ss
}
// Ping gets network traffic by ICMP
func Ping(host string) (rtt int, err error) {
rtt = 1000 // default and timeout is 1000 ms
pinger, err := ping.NewPinger(host)
if err != nil {
return rtt, err
}
pinger.Count = 3
pinger.Timeout = 3 * time.Second
err = pinger.Run()
if err != nil {
return rtt, err
}
stats := pinger.Statistics()
// ping failed
if len(stats.Rtts) == 0 {
return rtt, err
}
rtt = int(stats.AvgRtt) / 1e6
return rtt, err
}
// CalculateWeight converts the rtt to weighted by:
// 1. weight=191 if t <= 10
// 2. weight=201 -t if 10 < t <=200
// 3. weight=1 if 200 < t < 1000
// 4. weight = 0 if t >= 1000
//
// It means servers that ping time t < 10 will be preferred
// and servers won't be selected if t > 1000.
// It is hard coded based on Ops experience.
func CalculateWeight(rtt int) int {
switch {
case rtt >= 0 && rtt <= 10:
return 191
case rtt > 10 && rtt <= 200:
return 201 - rtt
case rtt > 100 && rtt < 1000:
return 1
default:
return 0
}
}
================================================
FILE: client/plugin.go
================================================
package client
import (
"context"
"net"
"github.com/smallnest/rpcx/protocol"
)
// pluginContainer implements PluginContainer interface.
type pluginContainer struct {
plugins []Plugin
}
func NewPluginContainer() PluginContainer {
return &pluginContainer{}
}
// Plugin is the client plugin interface.
type Plugin interface{}
// Add adds a plugin.
func (p *pluginContainer) Add(plugin Plugin) {
p.plugins = append(p.plugins, plugin)
}
// Remove removes a plugin by its name.
func (p *pluginContainer) Remove(plugin Plugin) {
if p.plugins == nil {
return
}
var plugins []Plugin
for _, pp := range p.plugins {
if pp != plugin {
plugins = append(plugins, pp)
}
}
p.plugins = plugins
}
// All returns all plugins
func (p *pluginContainer) All() []Plugin {
return p.plugins
}
// DoPreCall executes before call
func (p *pluginContainer) DoPreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error {
for i := range p.plugins {
if plugin, ok := p.plugins[i].(PreCallPlugin); ok {
err := plugin.PreCall(ctx, servicePath, serviceMethod, args)
if err != nil {
return err
}
}
}
return nil
}
// DoPostCall executes after call
func (p *pluginContainer) DoPostCall(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, err error) error {
for i := range p.plugins {
if plugin, ok := p.plugins[i].(PostCallPlugin); ok {
err = plugin.PostCall(ctx, servicePath, serviceMethod, args, reply, err)
if err != nil {
return err
}
}
}
return nil
}
// DoConnCreated is called in case of client connection created.
func (p *pluginContainer) DoConnCreated(conn net.Conn) (net.Conn, error) {
var err error
for i := range p.plugins {
if plugin, ok := p.plugins[i].(ConnCreatedPlugin); ok {
conn, err = plugin.ConnCreated(conn)
if err != nil {
return conn, err
}
}
}
return conn, nil
}
// DoConnCreateFailed is called in case of client connection create failed.
func (p *pluginContainer) DoConnCreateFailed(network, address string) {
for i := range p.plugins {
if plugin, ok := p.plugins[i].(ConnCreateFailedPlugin); ok {
plugin.ConnCreateFailed(network, address)
}
}
}
// DoClientConnected is called in case of connected.
func (p *pluginContainer) DoClientConnected(conn net.Conn) (net.Conn, error) {
var err error
for i := range p.plugins {
if plugin, ok := p.plugins[i].(ClientConnectedPlugin); ok {
conn, err = plugin.ClientConnected(conn)
if err != nil {
return conn, err
}
}
}
return conn, nil
}
// DoClientConnectionClose is called in case of connection close.
func (p *pluginContainer) DoClientConnectionClose(conn net.Conn) error {
var err error
for i := range p.plugins {
if plugin, ok := p.plugins[i].(ClientConnectionClosePlugin); ok {
err = plugin.ClientConnectionClose(conn)
if err != nil {
return err
}
}
}
return err
}
// DoClientBeforeEncode is called when requests are encoded and sent.
func (p *pluginContainer) DoClientBeforeEncode(req *protocol.Message) error {
var err error
for i := range p.plugins {
if plugin, ok := p.plugins[i].(ClientBeforeEncodePlugin); ok {
err = plugin.ClientBeforeEncode(req)
if err != nil {
return err
}
}
}
return nil
}
// DoClientAfterDecode is called when requests are decoded and received.
func (p *pluginContainer) DoClientAfterDecode(req *protocol.Message) error {
var err error
for i := range p.plugins {
if plugin, ok := p.plugins[i].(ClientAfterDecodePlugin); ok {
err = plugin.ClientAfterDecode(req)
if err != nil {
return err
}
}
}
return nil
}
// DoWrapSelect is called when select a node.
func (p *pluginContainer) DoWrapSelect(fn SelectFunc) SelectFunc {
rt := fn
for i := range p.plugins {
if pn, ok := p.plugins[i].(SelectNodePlugin); ok {
rt = pn.WrapSelect(rt)
}
}
return rt
}
type (
// PreCallPlugin is invoked before the client calls a server.
PreCallPlugin interface {
PreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error
}
// PostCallPlugin is invoked after the client calls a server.
PostCallPlugin interface {
PostCall(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, err error) error
}
// ConnCreatedPlugin is invoked when the client connection has created.
ConnCreatedPlugin interface {
ConnCreated(net.Conn) (net.Conn, error)
}
ConnCreateFailedPlugin interface {
ConnCreateFailed(network, address string)
}
// ClientConnectedPlugin is invoked when the client has connected the server.
ClientConnectedPlugin interface {
ClientConnected(net.Conn) (net.Conn, error)
}
// ClientConnectionClosePlugin is invoked when the connection is closing.
ClientConnectionClosePlugin interface {
ClientConnectionClose(net.Conn) error
}
// ClientBeforeEncodePlugin is invoked when the message is encoded and sent.
ClientBeforeEncodePlugin interface {
ClientBeforeEncode(*protocol.Message) error
}
// ClientAfterDecodePlugin is invoked when the message is decoded.
ClientAfterDecodePlugin interface {
ClientAfterDecode(*protocol.Message) error
}
// SelectNodePlugin can interrupt selecting of xclient and add customized logics such as skipping some nodes.
SelectNodePlugin interface {
WrapSelect(SelectFunc) SelectFunc
}
// PluginContainer represents a plugin container that defines all methods to manage plugins.
// And it also defines all extension points.
PluginContainer interface {
Add(plugin Plugin)
Remove(plugin Plugin)
All() []Plugin
DoConnCreated(net.Conn) (net.Conn, error)
DoConnCreateFailed(network, address string)
DoClientConnected(net.Conn) (net.Conn, error)
DoClientConnectionClose(net.Conn) error
DoPreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error
DoPostCall(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, err error) error
DoClientBeforeEncode(*protocol.Message) error
DoClientAfterDecode(*protocol.Message) error
DoWrapSelect(SelectFunc) SelectFunc
}
)
================================================
FILE: client/selectmode_enumer.go
================================================
// Code generated by "enumer -type=SelectMode"; DO NOT EDIT.
package client
import (
"fmt"
)
const _SelectModeName = "RandomSelectRoundRobinWeightedRoundRobinWeightedICMPConsistentHashClosest"
var _SelectModeIndex = [...]uint8{0, 12, 22, 40, 52, 66, 73}
func (i SelectMode) String() string {
if i < 0 || i >= SelectMode(len(_SelectModeIndex)-1) {
return fmt.Sprintf("SelectMode(%d)", i)
}
return _SelectModeName[_SelectModeIndex[i]:_SelectModeIndex[i+1]]
}
var _SelectModeValues = []SelectMode{0, 1, 2, 3, 4, 5}
var _SelectModeNameToValueMap = map[string]SelectMode{
_SelectModeName[0:12]: 0,
_SelectModeName[12:22]: 1,
_SelectModeName[22:40]: 2,
_SelectModeName[40:52]: 3,
_SelectModeName[52:66]: 4,
_SelectModeName[66:73]: 5,
}
// SelectModeString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func SelectModeString(s string) (SelectMode, error) {
if val, ok := _SelectModeNameToValueMap[s]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to SelectMode values", s)
}
// SelectModeValues returns all values of the enum
func SelectModeValues() []SelectMode {
return _SelectModeValues
}
// IsASelectMode returns "true" if the value is listed in the enum definition. "false" otherwise
func (i SelectMode) IsASelectMode() bool {
for _, v := range _SelectModeValues {
if i == v {
return true
}
}
return false
}
================================================
FILE: client/selector.go
================================================
package client
import (
"container/ring"
"context"
"math"
"math/rand"
"net/url"
"sort"
"strconv"
"time"
"github.com/edwingeng/doublejump"
"github.com/valyala/fastrand"
)
type SelectFunc func(ctx context.Context, servicePath, serviceMethod string, args interface{}) string
// Selector defines selector that selects one service from candidates.
type Selector interface {
Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string // SelectFunc
UpdateServer(servers map[string]string)
}
func newSelector(selectMode SelectMode, servers map[string]string) Selector {
switch selectMode {
case RandomSelect:
return newRandomSelector(servers)
case RoundRobin:
return newRoundRobinSelector(servers)
case WeightedRoundRobin:
return newWeightedRoundRobinSelector(servers)
case WeightedICMP:
return newWeightedICMPSelector(servers)
case ConsistentHash:
return newConsistentHashSelector(servers)
case SelectByUser:
return nil
default:
return newRandomSelector(servers)
}
}
// randomSelector selects randomly.
type randomSelector struct {
servers []string
}
func newRandomSelector(servers map[string]string) Selector {
ss := make([]string, 0, len(servers))
for k := range servers {
ss = append(ss, k)
}
return &randomSelector{servers: ss}
}
func (s *randomSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {
ss := s.servers
if len(ss) == 0 {
return ""
}
i := fastrand.Uint32n(uint32(len(ss)))
return ss[i]
}
func (s *randomSelector) UpdateServer(servers map[string]string) {
ss := make([]string, 0, len(servers))
for k := range servers {
ss = append(ss, k)
}
s.servers = ss
}
// roundRobinSelector selects servers with roundrobin.
type roundRobinSelector struct {
servers []string
i int
}
func newRoundRobinSelector(servers map[string]string) Selector {
ss := make([]string, 0, len(servers))
for k := range servers {
ss = append(ss, k)
}
return &roundRobinSelector{servers: ss}
}
func (s *roundRobinSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {
ss := s.servers
if len(ss) == 0 {
return ""
}
i := s.i
i = i % len(ss)
s.i = i + 1
return ss[i]
}
func (s *roundRobinSelector) UpdateServer(servers map[string]string) {
ss := make([]string, 0, len(servers))
for k := range servers {
ss = append(ss, k)
}
s.servers = ss
}
// weightedRoundRobinSelector selects servers with weighted.
type weightedRoundRobinSelector struct {
servers []*Weighted
totalWeight int
rr *ring.Ring
}
func newWeightedRoundRobinSelector(servers map[string]string) Selector {
ss := createWeighted(servers)
s := &weightedRoundRobinSelector{servers: ss}
s.buildRing()
return s
}
func (s *weightedRoundRobinSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {
ss := s.servers
if len(ss) == 0 {
return ""
}
val := s.rr.Value
s.rr = s.rr.Next()
return val.(*Weighted).Server
}
func (s *weightedRoundRobinSelector) UpdateServer(servers map[string]string) {
newServer := newWeightedRoundRobinSelector(servers).(*weightedRoundRobinSelector)
*s = *newServer
}
func (s *weightedRoundRobinSelector) buildRing() {
s.totalWeight = 0
for _, w := range s.servers {
s.totalWeight += w.Weight
}
s.rr = ring.New(s.totalWeight)
for i := 0; i < s.totalWeight; i++ {
n := s.next()
s.rr.Value = n
s.rr = s.rr.Next()
}
}
func (s *weightedRoundRobinSelector) next() *Weighted {
if len(s.servers) == 0 {
return nil
}
n := len(s.servers)
if n == 0 {
return nil
}
if n == 1 {
return s.servers[0]
}
flag := 0
m := 0
for i := 0; i < n; i++ {
s.servers[i].CurrentWeight += s.servers[i].Weight
if s.servers[i].CurrentWeight > m {
m = s.servers[i].CurrentWeight
flag = i
}
}
s.servers[flag].CurrentWeight -= s.totalWeight
return s.servers[flag]
}
func createWeighted(servers map[string]string) []*Weighted {
ss := make([]*Weighted, 0, len(servers))
for k, metadata := range servers {
w := &Weighted{Server: k, Weight: 1}
if v, err := url.ParseQuery(metadata); err == nil {
ww := v.Get("weight")
if ww != "" {
if weight, err := strconv.Atoi(ww); err == nil {
w.Weight = weight
}
}
}
ss = append(ss, w)
}
return ss
}
type geoServer struct {
Server string
Latitude float64
Longitude float64
}
// geoSelector selects servers based on location.
type geoSelector struct {
servers []*geoServer
Latitude float64
Longitude float64
r *rand.Rand
}
func newGeoSelector(servers map[string]string, latitude, longitude float64) Selector {
ss := createGeoServer(servers)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return &geoSelector{servers: ss, Latitude: latitude, Longitude: longitude, r: r}
}
func (s *geoSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {
if len(s.servers) == 0 {
return ""
}
var server []string
minNum := math.MaxFloat64
for _, gs := range s.servers {
d := getDistanceFrom(s.Latitude, s.Longitude, gs.Latitude, gs.Longitude)
if d < minNum {
server = []string{gs.Server}
minNum = d
} else if d == minNum {
server = append(server, gs.Server)
}
}
if len(server) == 1 {
return server[0]
}
return server[s.r.Intn(len(server))]
}
func (s *geoSelector) UpdateServer(servers map[string]string) {
ss := createGeoServer(servers)
s.servers = ss
}
func createGeoServer(servers map[string]string) []*geoServer {
geoServers := make([]*geoServer, 0, len(servers))
for s, metadata := range servers {
if v, err := url.ParseQuery(metadata); err == nil {
latStr := v.Get("latitude")
lonStr := v.Get("longitude")
if latStr == "" || lonStr == "" {
continue
}
lat, err := strconv.ParseFloat(latStr, 64)
if err != nil {
continue
}
lon, err := strconv.ParseFloat(lonStr, 64)
if err != nil {
continue
}
geoServers = append(geoServers, &geoServer{Server: s, Latitude: lat, Longitude: lon})
}
}
return geoServers
}
// consistentHashSelector selects based on JumpConsistentHash.
type consistentHashSelector struct {
h *doublejump.Hash
servers []string
}
func newConsistentHashSelector(servers map[string]string) Selector {
h := doublejump.NewHash()
ss := make([]string, 0, len(servers))
for k := range servers {
ss = append(ss, k)
h.Add(k)
}
sort.Slice(ss, func(i, j int) bool { return ss[i] < ss[j] })
return &consistentHashSelector{servers: ss, h: h}
}
func (s *consistentHashSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {
ss := s.servers
if len(ss) == 0 {
return ""
}
key := genKey(servicePath, serviceMethod, args)
selected, _ := s.h.Get(key).(string)
return selected
}
func (s *consistentHashSelector) UpdateServer(servers map[string]string) {
ss := make([]string, 0, len(servers))
for k := range servers {
s.h.Add(k)
ss = append(ss, k)
}
sort.Slice(ss, func(i, j int) bool { return ss[i] < ss[j] })
for _, k := range s.servers {
if _, exist := servers[k]; !exist { // remove
s.h.Remove(k)
}
}
s.servers = ss
}
================================================
FILE: client/selector_test.go
================================================
package client
import (
"context"
"testing"
)
func Test_consistentHashSelector_Select(t *testing.T) {
servers := map[string]string{
"tcp@192.168.1.16:9392": "",
"tcp@192.168.1.16:9393": "",
}
s := newConsistentHashSelector(servers).(*consistentHashSelector)
key := uint64(9280147620691907957)
selected, _ := s.h.Get(key).(string)
for i := 0; i < 10000; i++ {
selected2, _ := s.h.Get(key).(string)
if selected != selected2 {
t.Errorf("expected %s but got %s", selected, selected2)
}
}
}
func Test_consistentHashSelector_UpdateServer(t *testing.T) {
servers := map[string]string{
"tcp@192.168.1.16:9392": "",
"tcp@192.168.1.16:9393": "",
}
s := newConsistentHashSelector(servers).(*consistentHashSelector)
if len(s.h.All()) != len(servers) {
t.Errorf("NewSelector: expected %d server but got %d", len(servers), len(s.h.All()))
}
s.UpdateServer(servers)
if len(s.h.All()) != len(servers) {
t.Errorf("UpdateServer: expected %d server but got %d", len(servers), len(s.h.All()))
}
}
func TestWeightedRoundRobinSelector_Select(t *testing.T) {
calc := make(map[string]int)
servers := make(map[string]string)
servers["ServerA"] = "weight=4"
servers["ServerB"] = "weight=2"
servers["ServerC"] = "weight=1"
weightSelector := newWeightedRoundRobinSelector(servers).(*weightedRoundRobinSelector)
ctx := context.Background()
for i := 0; i < 7; i++ {
s := weightSelector.Select(ctx, "", "", nil)
if _, ok := calc[s]; ok {
calc[s]++
} else {
calc[s] = 1
}
}
if calc["ServerA"] != 4 {
t.Errorf("expected %d but got %d", 4, calc["ServerA"])
}
if calc["ServerB"] != 2 {
t.Errorf("expected %d but got %d", 2, calc["ServerB"])
}
if calc["ServerC"] != 1 {
t.Errorf("expected %d but got %d", 1, calc["ServerC"])
}
}
func TestWeightedRoundRobinSelector_UpdateServer(t *testing.T) {
calc := make(map[string]int)
servers := make(map[string]string)
servers["ServerA"] = "weight=4"
servers["ServerB"] = "weight=2"
servers["ServerC"] = "weight=1"
weightSelector := newWeightedRoundRobinSelector(servers).(*weightedRoundRobinSelector)
ctx := context.Background()
servers["ServerA"] = "weight=5"
weightSelector.UpdateServer(servers)
for i := 0; i < 8; i++ {
s := weightSelector.Select(ctx, "", "", nil)
if _, ok := calc[s]; ok {
calc[s]++
} else {
calc[s] = 1
}
}
if calc["ServerA"] != 5 {
t.Errorf("expected %d but got %d", 4, calc["ServerA"])
}
if calc["ServerB"] != 2 {
t.Errorf("expected %d but got %d", 2, calc["ServerB"])
}
if calc["ServerC"] != 1 {
t.Errorf("expected %d but got %d", 1, calc["ServerC"])
}
}
func BenchmarkWeightedRoundRobinSelector_Select(b *testing.B) {
servers := make(map[string]string)
servers["ServerA"] = "weight=4"
servers["ServerB"] = "weight=2"
servers["ServerC"] = "weight=1"
ctx := context.Background()
weightSelector := newWeightedRoundRobinSelector(servers).(*weightedRoundRobinSelector)
for i := 0; i < b.N; i++ {
weightSelector.Select(ctx, "", "", nil)
}
}
//
//func TestWeightedICMPSelector(t *testing.T) {
// calc := make(map[string]int)
// servers := make(map[string]string)
// servers["@localhost:3333"] = ""
// servers["@www.baidu.com:3334"] = ""
// servers["@xxxx.xxxx:333"] = ""
// s := newWeightedICMPSelector(servers)
// ctx := context.Background()
// for i := 0; i < 10; i++ {
// host := s.Select(ctx, "", "", nil)
// if _, ok := calc[host]; ok {
// calc[host]++
// } else {
// calc[host] = 0
// }
// }
// if len(calc) != 2 {
// t.Errorf("expected %d but got %d", 2, len(servers))
// }
//}
//func TestWeightedICMPSelector_UpdateServer(t *testing.T) {
// calc := make(map[string]int)
// servers := make(map[string]string)
// servers["@localhost:3333"] = ""
// servers["@www.baidu.com:3334"] = ""
// servers["@xxxx.xxxx:333"] = ""
// s := newWeightedICMPSelector(servers)
// ctx := context.Background()
// servers["@www.sina.com:3333"] = ""
// s.UpdateServer(servers)
// for i := 0; i < 10; i++ {
// host := s.Select(ctx, "", "", nil)
// if _, ok := calc[host]; ok {
// calc[host]++
// } else {
// calc[host] = 0
// }
// }
// if len(calc) != 3 {
// t.Errorf("expected %d but got %d", 3, len(servers))
// }
//}
================================================
FILE: client/smooth_weighted_round_robin.go
================================================
package client
// Weighted is a wrapped server with weight
type Weighted struct {
Server string
Weight int
CurrentWeight int
EffectiveWeight int
}
================================================
FILE: client/xclient.go
================================================
package client
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"net"
"net/url"
"os"
"reflect"
"sort"
"strings"
"sync"
"time"
"github.com/juju/ratelimit"
"golang.org/x/sync/singleflight"
ex "github.com/smallnest/rpcx/errors"
"github.com/smallnest/rpcx/log"
"github.com/smallnest/rpcx/protocol"
"github.com/smallnest/rpcx/share"
)
const (
FileTransferBufferSize = 1024
)
var (
// ErrXClientShutdown xclient is shutdown.
ErrXClientShutdown = errors.New("xClient is shut down")
// ErrXClientNoServer selector can't found one server.
ErrXClientNoServer = errors.New("can not found any server")
// ErrServerUnavailable selected server is unavailable.
ErrServerUnavailable = errors.New("selected server is unavailable")
)
// Receipt represents the result of the service returned.
type Receipt struct {
Address string
Reply interface{}
Error error
}
// XClient is an interface that used by client with service discovery and service governance.
// One XClient is used only for one service. You should create multiple XClient for multiple services.
type XClient interface {
SetPlugins(plugins PluginContainer)
GetPlugins() PluginContainer
SetSelector(s Selector)
ConfigGeoSelector(latitude, longitude float64)
Auth(auth string)
Go(ctx context.Context, serviceMethod string, args interface{}, reply interface{}, done chan *Call) (*Call, error)
Call(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error
Oneshot(ctx context.Context, serviceMethod string, args interface{}) error
Broadcast(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error
Fork(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error
Inform(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) ([]Receipt, error)
SendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error)
SendFile(ctx context.Context, fileName string, rateInBytesPerSecond int64, meta map[string]string) error
DownloadFile(ctx context.Context, requestFileName string, saveTo io.Writer, meta map[string]string) error
Stream(ctx context.Context, meta map[string]string) (net.Conn, error)
Close() error
}
// SetSelector sets customized selector by users.
func (c *xClient) SetSelector(s Selector) {
c.mu.RLock()
s.UpdateServer(c.servers)
c.mu.RUnlock()
c.selector = s
}
// KVPair contains a key and a string.
type KVPair struct {
Key string
Value string
}
type xClient struct {
failMode FailMode
selectMode SelectMode
cachedClient map[string]RPCClient
breakers sync.Map
servicePath string
option Option
mu sync.RWMutex
servers map[string]string
unstableServers map[string]time.Time // 一些服务器重启,如果和它们建立链接,可能会耗费非常长的时间,这里记录袭来需要临时屏蔽
discovery ServiceDiscovery
selector Selector
stickyRPCClient RPCClient
stickyK string
slGroup singleflight.Group
isShutdown bool
// auth is a string for Authentication, for example, "Bearer mF_9.B5f-4.1JqM"
auth string
Plugins PluginContainer
ch chan []*KVPair
serverMessageChan chan<- *protocol.Message
}
// NewXClient creates a XClient that supports service discovery and service governance.
func NewXClient(servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) XClient {
client := &xClient{
failMode: failMode,
selectMode: selectMode,
discovery: discovery,
servicePath: servicePath,
cachedClient: make(map[string]RPCClient),
unstableServers: make(map[string]time.Time),
option: option,
}
pairs := discovery.GetServices()
sort.Slice(pairs, func(i, j int) bool {
return strings.Compare(pairs[i].Key, pairs[j].Key) <= 0
})
servers := make(map[string]string, len(pairs))
for _, p := range pairs {
servers[p.Key] = p.Value
}
filterByStateAndGroup(client.option.Group, servers)
client.servers = servers
if selectMode != Closest && selectMode != SelectByUser {
client.selector = newSelector(selectMode, servers)
}
client.Plugins = &pluginContainer{}
ch := client.discovery.WatchService()
if ch != nil {
client.ch = ch
go client.watch(ch)
}
return client
}
// NewBidirectionalXClient creates a new xclient that can receive notifications from servers.
func NewBidirectionalXClient(servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option, serverMessageChan chan<- *protocol.Message) XClient {
client := &xClient{
failMode: failMode,
selectMode: selectMode,
discovery: discovery,
servicePath: servicePath,
cachedClient: make(map[string]RPCClient),
option: option,
unstableServers: make(map[string]time.Time),
serverMessageChan: serverMessageChan,
}
pairs := discovery.GetServices()
sort.Slice(pairs, func(i, j int) bool {
return strings.Compare(pairs[i].Key, pairs[j].Key) <= 0
})
servers := make(map[string]string, len(pairs))
for _, p := range pairs {
servers[p.Key] = p.Value
}
filterByStateAndGroup(client.option.Group, servers)
client.servers = servers
if selectMode != Closest && selectMode != SelectByUser {
client.selector = newSelector(selectMode, servers)
}
client.Plugins = &pluginContainer{}
ch := client.discovery.WatchService()
if ch != nil {
client.ch = ch
go client.watch(ch)
}
return client
}
// SetPlugins sets client's plugins.
func (c *xClient) SetPlugins(plugins PluginContainer) {
c.Plugins = plugins
}
func (c *xClient) GetPlugins() PluginContainer {
return c.Plugins
}
// ConfigGeoSelector sets location of client's latitude and longitude,
// and use newGeoSelector.
func (c *xClient) ConfigGeoSelector(latitude, longitude float64) {
c.selector = newGeoSelector(c.servers, latitude, longitude)
c.selectMode = Closest
}
// Auth sets s token for Authentication.
func (c *xClient) Auth(auth string) {
c.auth = auth
}
// watch changes of service and update cached clients.
func (c *xClient) watch(ch chan []*KVPair) {
for pairs := range ch {
sort.Slice(pairs, func(i, j int) bool {
return strings.Compare(pairs[i].Key, pairs[j].Key) <= 0
})
servers := make(map[string]string, len(pairs))
for _, p := range pairs {
servers[p.Key] = p.Value
}
c.mu.Lock()
filterByStateAndGroup(c.option.Group, servers)
c.servers = servers
if c.selector != nil {
c.selector.UpdateServer(servers)
}
c.mu.Unlock()
}
}
func filterByStateAndGroup(group string, servers map[string]string) {
for k, v := range servers {
if values, err := url.ParseQuery(v); err == nil {
if state := values.Get("state"); state == "inactive" {
delete(servers, k)
}
groups := values["group"] // Directly access the map to get all values associated with "group" as a slice
if group != "" {
found := false
for _, g := range groups {
if group == g {
found = true
break // A matching group is found, stop the search
}
}
if !found {
delete(servers, k) // If no matching group is found, delete the corresponding server from the map
}
}
}
}
}
// selects a client from candidates base on c.selectMode
func (c *xClient) selectClient(ctx context.Context, servicePath, serviceMethod string, args interface{}) (string, RPCClient, error) {
c.mu.Lock()
if c.option.Sticky && c.stickyRPCClient != nil {
if c.stickyRPCClient.IsClosing() || c.stickyRPCClient.IsShutdown() {
c.stickyRPCClient = nil
}
}
if c.option.Sticky && c.stickyRPCClient != nil {
c.mu.Unlock()
return c.stickyK, c.stickyRPCClient, nil
}
fn := c.selector.Select
if c.Plugins != nil {
fn = c.Plugins.DoWrapSelect(fn)
}
k := fn(ctx, servicePath, serviceMethod, args)
c.mu.Unlock()
if k == "" {
return "", nil, ErrXClientNoServer
}
client, err := c.getCachedClient(k, servicePath, serviceMethod, args)
if c.option.Sticky && client != nil {
c.mu.Lock()
safeCloseClient(c.stickyRPCClient)
c.stickyK = k
c.stickyRPCClient = client
c.mu.Unlock()
}
return k, client, err
}
func safeCloseClient(client RPCClient) {
if client == nil {
return
}
defer func() {
_ = recover()
}()
client.Close()
}
func (c *xClient) getCachedClient(k string, servicePath, serviceMethod string, _ interface{}) (client RPCClient, err error) {
var needCallPlugin bool
defer func() {
if needCallPlugin {
_, err = c.Plugins.DoClientConnected(client.GetConn())
}
}()
if c.isShutdown {
return nil, errors.New("this xclient is closed")
}
// if this client is broken
breaker, ok := c.breakers.Load(k)
if ok && !breaker.(Breaker).Ready() {
return nil, ErrBreakerOpen
}
c.mu.Lock()
defer c.mu.Unlock()
client = c.findCachedClient(k, servicePath, serviceMethod)
if client != nil {
if !client.IsClosing() && !client.IsShutdown() {
return client, nil
}
c.deleteCachedClient(client, k, servicePath, serviceMethod)
}
client = c.findCachedClient(k, servicePath, serviceMethod)
if client == nil || client.IsShutdown() {
generatedClient, err, _ := c.slGroup.Do(k, func() (interface{}, error) {
return c.generateClient(k, servicePath, serviceMethod)
})
if err != nil {
c.slGroup.Forget(k)
return nil, err
}
client = generatedClient.(RPCClient)
if c.Plugins != nil {
needCallPlugin = true
}
client.RegisterServerMessageChan(c.serverMessageChan)
c.setCachedClient(client, k, servicePath, serviceMethod)
// forget k only when client is cached
c.slGroup.Forget(k)
}
return client, nil
}
func (c *xClient) setCachedClient(client RPCClient, k, servicePath, serviceMethod string) {
network, _ := splitNetworkAndAddress(k)
if builder, ok := getCacheClientBuilder(network); ok {
builder.SetCachedClient(client, k, servicePath, serviceMethod)
return
}
c.cachedClient[k] = client
}
func (c *xClient) findCachedClient(k, servicePath, serviceMethod string) RPCClient {
network, _ := splitNetworkAndAddress(k)
if builder, ok := getCacheClientBuilder(network); ok {
return builder.FindCachedClient(k, servicePath, serviceMethod)
}
return c.cachedClient[k]
}
func (c *xClient) deleteCachedClient(client RPCClient, k, servicePath, serviceMethod string) {
network, _ := splitNetworkAndAddress(k)
if builder, ok := getCacheClientBuilder(network); ok && client != nil {
builder.DeleteCachedClient(client, k, servicePath, serviceMethod)
client.Close()
return
}
delete(c.cachedClient, k)
if client != nil {
client.Close()
}
}
func (c *xClient) removeClient(k, servicePath, serviceMethod string, client RPCClient) {
c.mu.Lock()
if c.option.Sticky {
c.stickyK = ""
c.stickyRPCClient = nil
}
cl := c.findCachedClient(k, servicePath, serviceMethod)
if cl == client {
c.deleteCachedClient(client, k, servicePath, serviceMethod)
}
c.mu.Unlock()
if client != nil {
client.UnregisterServerMessageChan()
client.Close()
}
}
func (c *xClient) generateClient(k, servicePath, serviceMethod string) (client RPCClient, err error) {
network, addr := splitNetworkAndAddress(k)
if builder, ok := getCacheClientBuilder(network); ok && builder != nil {
return builder.GenerateClient(k, servicePath, serviceMethod)
}
client = &Client{
option: c.option,
Plugins: c.Plugins,
}
var breaker interface{}
if c.option.GenBreaker != nil {
breaker, _ = c.breakers.LoadOrStore(k, c.option.GenBreaker())
}
err = client.Connect(network, addr)
if err != nil {
if breaker != nil {
breaker.(Breaker).Fail()
}
return nil, err
}
return client, err
}
func (c *xClient) getCachedClientWithoutLock(k, servicePath, serviceMethod string) (RPCClient, bool, error) {
var needCallPlugin bool
client := c.findCachedClient(k, servicePath, serviceMethod)
if client != nil {
if !client.IsClosing() && !client.IsShutdown() {
return client, needCallPlugin, nil
}
c.deleteCachedClient(client, k, servicePath, serviceMethod)
// double check
client = c.findCachedClient(k, servicePath, serviceMethod)
}
if client == nil || client.IsShutdown() {
generatedClient, err, _ := c.slGroup.Do(k, func() (interface{}, error) {
return c.generateClient(k, servicePath, serviceMethod)
})
if err != nil {
c.slGroup.Forget(k)
return nil, needCallPlugin, err
}
client = generatedClient.(RPCClient)
if c.Plugins != nil {
needCallPlugin = true
}
client.RegisterServerMessageChan(c.serverMessageChan)
c.setCachedClient(client, k, servicePath, serviceMethod)
c.slGroup.Forget(k)
}
return client, needCallPlugin, nil
}
func splitNetworkAndAddress(server string) (string, string) {
ss := strings.SplitN(server, "@", 2)
if len(ss) == 1 {
return "tcp", server
}
return ss[0], ss[1]
}
func setServerTimeout(ctx context.Context) context.Context {
if deadline, ok := ctx.Deadline(); ok {
metadata := ctx.Value(share.ReqMetaDataKey)
if metadata == nil {
metadata = map[string]string{}
ctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)
}
m := metadata.(map[string]string)
m[share.ServerTimeout] = fmt.Sprintf("%d", time.Until(deadline).Milliseconds())
}
return ctx
}
// Go invokes the function asynchronously. It returns the Call structure representing the invocation. The done channel will signal when the call is complete by returning the same Call object. If done is nil, Go will allocate a new channel. If non-nil, done must be buffered or Go will deliberately crash.
// It does not use FailMode.
func (c *xClient) Go(ctx context.Context, serviceMethod string, args interface{}, reply interface{}, done chan *Call) (*Call, error) {
if c.isShutdown {
return nil, ErrXClientShutdown
}
if c.auth != "" {
metadata := ctx.Value(share.ReqMetaDataKey)
if metadata == nil {
metadata = map[string]string{}
ctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)
}
m := metadata.(map[string]string)
m[share.AuthKey] = c.auth
}
ctx = setServerTimeout(ctx)
if share.Trace {
log.Debugf("select a client for %s.%s, args: %+v in case of xclient Go", c.servicePath, serviceMethod, args)
}
_, client, err := c.selectClient(ctx, c.servicePath, serviceMethod, args)
if err != nil {
return nil, err
}
if share.Trace {
log.Debugf("selected a client %s for %s.%s, args: %+v in case of xclient Go", client.RemoteAddr(), c.servicePath, serviceMethod, args)
}
if done == nil {
done = make(chan *Call, 10)
}
return client.Go(ctx, c.servicePath, serviceMethod, args, reply, done), nil
}
// Call invokes the named function, waits for it to complete, and returns its error status.
// It handles errors base on FailMode.
func (c *xClient) Call(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error {
if c.isShutdown {
return ErrXClientShutdown
}
if c.auth != "" {
metadata := ctx.Value(share.ReqMetaDataKey)
if metadata == nil {
metadata = map[string]string{}
ctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)
}
m := metadata.(map[string]string)
m[share.AuthKey] = c.auth
}
ctx = setServerTimeout(ctx)
if share.Trace {
log.Debugf("select a client for %s.%s, failMode: %v, args: %+v in case of xclient Call", c.servicePath, serviceMethod, c.failMode, args)
}
var err error
k, client, err := c.selectClient(ctx, c.servicePath, serviceMethod, args)
if err != nil {
if c.failMode == Failfast || contextCanceled(err) {
return err
}
}
if share.Trace {
if client != nil {
log.Debugf("selected a client %s for %s.%s, failMode: %v, args: %+v in case of xclient Call", client.RemoteAddr(), c.servicePath, serviceMethod, c.failMode, args)
} else {
log.Debugf("selected a client %s for %s.%s, failMode: %v, args: %+v in case of xclient Call", "nil", c.servicePath, serviceMethod, c.failMode, args)
}
}
var e error
switch c.failMode {
case Failtry:
retries := c.option.Retries
retryInterval := c.option.RetryInterval
for retries >= 0 {
retries--
if client != nil {
err = c.wrapCall(ctx, client, serviceMethod, args, reply)
if err == nil {
return nil
}
if contextCanceled(err) {
return err
}
if e, ok := err.(ServiceError); ok && e.IsServiceError() {
return err
}
}
if uncoverError(err) {
c.removeClient(k, c.servicePath, serviceMethod, client)
}
client, e = c.getCachedClient(k, c.servicePath, serviceMethod, args)
time.Sleep(retryInterval)
}
if err == nil {
err = e
}
return err
case Failover:
retries := c.option.Retries
retryInterval := c.option.RetryInterval
for retries >= 0 {
retries--
if client != nil {
err = c.wrapCall(ctx, client, serviceMethod, args, reply)
if err == nil {
return nil
}
if contextCanceled(err) {
return err
}
if e, ok := err.(ServiceError); ok && e.IsServiceError() {
return err
}
}
if uncoverError(err) {
c.removeClient(k, c.servicePath, serviceMethod, client)
}
time.Sleep(retryInterval)
// select another server
k, client, e = c.selectClient(ctx, c.servicePath, serviceMethod, args)
}
if err == nil {
err = e
}
return err
case Failbackup:
ctx, cancelFn := context.WithCancel(ctx)
defer cancelFn()
call1 := make(chan *Call, 10)
call2 := make(chan *Call, 10)
var reply1, reply2 interface{}
if reply != nil {
reply1 = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()
reply2 = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()
}
_, err1 := c.Go(ctx, serviceMethod, args, reply1, call1)
t := time.NewTimer(c.option.BackupLatency)
select {
case <-ctx.Done(): // cancel by context
err = ctx.Err()
return err
case call := <-call1:
err = call.Error
if err == nil && reply != nil {
reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(reply1).Elem())
}
return err
case <-t.C:
}
_, err2 := c.Go(ctx, serviceMethod, args, reply2, call2)
if err2 != nil {
if uncoverError(err2) {
c.removeClient(k, c.servicePath, serviceMethod, client)
}
err = err1
return err
}
select {
case <-ctx.Done(): // cancel by context
err = ctx.Err()
case call := <-call1:
err = call.Error
if err == nil && reply != nil && reply1 != nil {
reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(reply1).Elem())
}
case call := <-call2:
err = call.Error
if err == nil && reply != nil && reply2 != nil {
reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(reply2).Elem())
}
}
return err
default: // Failfast
err = c.wrapCall(ctx, client, serviceMethod, args, reply)
if err != nil {
if uncoverError(err) {
c.removeClient(k, c.servicePath, serviceMethod, client)
}
}
return err
}
}
// Oneshot invokes the named function, ** DOEST NOT ** wait for it to complete, and returns immediately.
func (c *xClient) Oneshot(ctx context.Context, serviceMethod string, args interface{}) error {
if c.isShutdown {
return ErrXClientShutdown
}
if c.auth != "" {
metadata := ctx.Value(share.ReqMetaDataKey)
if metadata == nil {
metadata = map[string]string{}
ctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)
}
m := metadata.(map[string]string)
m[share.AuthKey] = c.auth
}
ctx = setServerTimeout(ctx)
if share.Trace {
log.Debugf("select a client for %s.%s, args: %+v in case of xclient Go", c.servicePath, serviceMethod, args)
}
_, client, err := c.selectClient(ctx, c.servicePath, serviceMethod, args)
if err != nil {
return err
}
if share.Trace {
log.Debugf("selected a client %s for %s.%s, args: %+v in case of xclient Go", client.RemoteAddr(), c.servicePath, serviceMethod, args)
}
client.Go(ctx, c.servicePath, serviceMethod, args, nil, nil)
return nil
}
func uncoverError(err error) bool {
if e, ok := err.(ServiceError); ok && e.IsServiceError() {
return false
}
if err == context.DeadlineExceeded {
return false
}
if err == context.Canceled {
return false
}
return true
}
func contextCanceled(err error) bool {
if err == context.DeadlineExceeded {
return true
}
if err == context.Canceled {
return true
}
return false
}
func (c *xClient) SendRaw(ctx context.Context, r *protocol.Message) (map[string]string, []byte, error) {
if c.isShutdown {
return nil, nil, ErrXClientShutdown
}
if c.auth != "" {
metadata := ctx.Value(share.ReqMetaDataKey)
if metadata == nil {
metadata = map[string]string{}
ctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)
}
m := metadata.(map[string]string)
m[share.AuthKey] = c.auth
}
ctx = setServerTimeout(ctx)
if share.Trace {
log.Debugf("select a client for %s.%s, failMode: %v, args: %+v in case of xclient SendRaw", r.ServicePath, r.ServiceMethod, c.failMode, r.Payload)
}
var err error
k, client, err := c.selectClient(ctx, r.ServicePath, r.ServiceMethod, r.Payload)
if err != nil {
if c.failMode == Failfast {
return nil, nil, err
}
if contextCanceled(err) {
return nil, nil, err
}
if e, ok := err.(ServiceError); ok && e.IsServiceError() {
return nil, nil, err
}
}
if share.Trace {
log.Debugf("selected a client %s for %s.%s, failMode: %v, args: %+v in case of xclient Call", client.RemoteAddr(), r.ServicePath, r.ServiceMethod, c.failMode, r.Payload)
}
var e error
switch c.failMode {
case Failtry:
retries := c.option.Retries
for retries >= 0 {
retries--
if client != nil {
m, payload, err := c.wrapSendRaw(ctx, client, r)
if err == nil {
return m, payload, nil
}
if contextCanceled(err) {
return nil, nil, err
}
if _, ok := err.(ServiceError); ok {
return nil, nil, err
}
}
if uncoverError(err) {
c.removeClient(k, r.ServicePath, r.ServiceMethod, client)
}
client, e = c.getCachedClient(k, r.ServicePath, r.ServiceMethod, r.Payload)
}
if err == nil {
err = e
}
return nil, nil, err
case Failover:
retries := c.option.Retries
for retries >= 0 {
retries--
if client != nil {
m, payload, err := c.wrapSendRaw(ctx, client, r)
if err == nil {
return m, payload, nil
}
if contextCanceled(err) {
return nil, nil, err
}
if e, ok := err.(ServiceError); ok && e.IsServiceError() {
return nil, nil, err
}
}
if uncoverError(err) {
c.removeClient(k, r.ServicePath, r.ServiceMethod, client)
}
// select another server
k, client, e = c.selectClient(ctx, r.ServicePath, r.ServiceMethod, r.Payload)
}
if err == nil {
err = e
}
return nil, nil, err
default: // Failfast
m, payload, err := c.wrapSendRaw(ctx, client, r)
if err != nil {
if uncoverError(err) {
c.removeClient(k, r.ServicePath, r.ServiceMethod, client)
}
}
return m, payload, nil
}
}
func (c *xClient) wrapCall(ctx context.Context, client RPCClient, serviceMethod string, args interface{}, reply interface{}) error {
if client == nil {
return ErrServerUnavailable
}
if share.Trace {
log.Debugf("call a client for %s.%s, args: %+v in case of xclient wrapCall", c.servicePath, serviceMethod, args)
}
if _, ok := ctx.(*share.Context); !ok {
ctx = share.NewContext(ctx)
}
err := c.Plugins.DoPreCall(ctx, c.servicePath, serviceMethod, args)
if err != nil {
return err
}
err = client.Call(ctx, c.servicePath, serviceMethod, args, reply)
c.Plugins.DoPostCall(ctx, c.servicePath, serviceMethod, args, reply, err)
if share.Trace {
log.Debugf("called a client for %s.%s, args: %+v, err: %v in case of xclient wrapCall", c.servicePath, serviceMethod, args, err)
}
return err
}
// wrapSendRaw wrap SendRaw to support client plugins
func (c *xClient) wrapSendRaw(ctx context.Context, client RPCClient, r *protocol.Message) (map[string]string, []byte, error) {
if client == nil {
return nil, nil, ErrServerUnavailable
}
if share.Trace {
log.Debugf("call a client for %s.%s, args: %+v in case of xclient wrapSendRaw", c.servicePath, r.ServiceMethod, r.Payload)
}
ctx = share.NewContext(ctx)
err := c.Plugins.DoPreCall(ctx, c.servicePath, r.ServiceMethod, r.Payload)
if err != nil {
return nil, nil, err
}
m, payload, err := client.SendRaw(ctx, r)
c.Plugins.DoPostCall(ctx, c.servicePath, r.ServiceMethod, r.Payload, nil, err)
if share.Trace {
log.Debugf("called a client for %s.%s, args: %+v, err: %v in case of xclient wrapSendRaw", c.servicePath, r.ServiceMethod, r.Payload, err)
}
return m, payload, err
}
// Broadcast sends requests to all servers and Success only when all servers return OK.
// FailMode and SelectMode are meanless for this method.
// Please set timeout to avoid hanging.
func (c *xClient) Broadcast(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error {
if c.isShutdown {
return ErrXClientShutdown
}
if c.auth != "" {
metadata := ctx.Value(share.ReqMetaDataKey)
if metadata == nil {
metadata = map[string]string{}
ctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)
}
m := metadata.(map[string]string)
m[share.AuthKey] = c.auth
}
var replyOnce sync.Once
ctx = setServerTimeout(ctx)
// add timeout after set server timeout, only prevent client hanging
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
callPlugins := make([]RPCClient, 0, len(c.servers))
clients := make(map[string]RPCClient)
c.mu.Lock()
for k := range c.servers {
client, needCallPlugin, err := c.getCachedClientWithoutLock(k, c.servicePath, serviceMethod)
if err != nil {
continue
}
clients[k] = client
if needCallPlugin {
callPlugins = append(callPlugins, client)
}
}
c.mu.Unlock()
for i := range callPlugins {
if c.Plugins != nil {
c.Plugins.DoClientConnected(callPlugins[i].GetConn())
}
}
if len(clients) == 0 {
return ErrXClientNoServer
}
err := &ex.MultiError{}
l := len(clients)
done := make(chan bool, l)
for k, client := range clients {
k := k
client := client
go func() {
var clonedReply interface{}
if reply != nil {
clonedReply = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()
}
e := c.wrapCall(ctx, client, serviceMethod, args, clonedReply)
defer func() {
done <- (e == nil)
}()
if e != nil {
if uncoverError(e) {
c.removeClient(k, c.servicePath, serviceMethod, client)
}
err.Append(e)
}
if e == nil && reply != nil && clonedReply != nil {
replyOnce.Do(func() {
reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(clonedReply).Elem())
})
}
}()
}
check:
for {
select {
case result := <-done:
l--
if l == 0 || !result { // all returns or some one returns an error
break check
}
}
}
select {
case <-ctx.Done():
err.Append(errors.New(("timeout")))
default:
}
return err.ErrorOrNil()
}
// Fork sends requests to all servers and Success once one server returns OK.
// FailMode and SelectMode are meanless for this method.
func (c *xClient) Fork(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) error {
if c.isShutdown {
return ErrXClientShutdown
}
if c.auth != "" {
metadata := ctx.Value(share.ReqMetaDataKey)
if metadata == nil {
metadata = map[string]string{}
ctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)
}
m := metadata.(map[string]string)
m[share.AuthKey] = c.auth
}
ctx = setServerTimeout(ctx)
// add timeout after set server timeout, only prevent client hanging
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
callPlugins := make([]RPCClient, 0, len(c.servers))
clients := make(map[string]RPCClient)
c.mu.Lock()
for k := range c.servers {
client, needCallPlugin, err := c.getCachedClientWithoutLock(k, c.servicePath, serviceMethod)
if err != nil {
continue
}
clients[k] = client
if needCallPlugin {
callPlugins = append(callPlugins, client)
}
}
c.mu.Unlock()
for i := range callPlugins {
if c.Plugins != nil {
c.Plugins.DoClientConnected(callPlugins[i].GetConn())
}
}
if len(clients) == 0 {
return ErrXClientNoServer
}
var replyOnce sync.Once
err := &ex.MultiError{}
l := len(clients)
done := make(chan bool, l)
for k, client := range clients {
k := k
client := client
go func() {
var clonedReply interface{}
if reply != nil {
clonedReply = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()
}
e := c.wrapCall(ctx, client, serviceMethod, args, clonedReply)
if e == nil && reply != nil && clonedReply != nil {
replyOnce.Do(func() {
reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(clonedReply).Elem())
})
}
defer func() {
done <- (e == nil)
}()
if e != nil {
if uncoverError(e) {
c.removeClient(k, c.servicePath, serviceMethod, client)
}
err.Append(e)
}
}()
}
check:
for {
select {
case result := <-done:
l--
if result {
return nil
}
if l == 0 { // all returns or some one returns an error
break check
}
}
}
select {
case <-ctx.Done():
err.Append(errors.New(("timeout")))
default:
}
return err.ErrorOrNil()
}
// Inform sends requests to all servers and returns all results from services.
// FailMode and SelectMode are meanless for this method.
// Please set timeout to avoid hanging.
func (c *xClient) Inform(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) ([]Receipt, error) {
if c.isShutdown {
return nil, ErrXClientShutdown
}
if c.auth != "" {
metadata := ctx.Value(share.ReqMetaDataKey)
if metadata == nil {
metadata = map[string]string{}
ctx = context.WithValue(ctx, share.ReqMetaDataKey, metadata)
}
m := metadata.(map[string]string)
m[share.AuthKey] = c.auth
}
ctx = setServerTimeout(ctx)
// add timeout after set server timeout, only prevent client hanging
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
callPlugins := make([]RPCClient, 0, len(c.servers))
clients := make(map[string]RPCClient)
c.mu.Lock()
for k := range c.servers {
client, needCallPlugin, err := c.getCachedClientWithoutLock(k, c.servicePath, serviceMethod)
if err != nil {
continue
}
clients[k] = client
if needCallPlugin {
callPlugins = append(callPlugins, client)
}
}
c.mu.Unlock()
for i := range callPlugins {
if c.Plugins != nil {
c.Plugins.DoClientConnected(callPlugins[i].GetConn())
}
}
if len(clients) == 0 {
return nil, ErrXClientNoServer
}
var receiptsLock sync.Mutex
var receipts []Receipt
var replyOnce sync.Once
err := &ex.MultiError{}
l := len(clients)
done := make(chan bool, l)
for k, client := range clients {
k := k
client := client
go func() {
var clonedReply interface{}
if reply != nil {
clonedReply = reflect.New(reflect.ValueOf(reply).Elem().Type()).Interface()
}
e := c.wrapCall(ctx, client, serviceMethod, args, clonedReply)
defer func() {
done <- (e == nil)
}()
if e != nil {
if uncoverError(e) {
c.removeClient(k, c.servicePath, serviceMethod, client)
}
err.Append(e)
}
if e == nil && reply != nil && clonedReply != nil {
replyOnce.Do(func() {
reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(clonedReply).Elem())
})
}
addr := k
ss := strings.SplitN(k, "@", 2)
if len(ss) == 2 {
addr = ss[1]
}
receiptsLock.Lock()
receipts = append(receipts, Receipt{
Address: addr,
Reply: clonedReply,
Error: err,
})
receiptsLock.Unlock()
}()
}
check:
for {
select {
case <-done:
l--
if l == 0 { // all returns or some one returns an error
break check
}
}
}
select {
case <-ctx.Done():
err.Append(errors.New(("timeout")))
default:
}
return receipts, err.ErrorOrNil()
}
// SendFile sends a local file to the server.
// fileName is the path of local file.
// rateInBytesPerSecond can limit bandwidth of sending, 0 means does not limit the bandwidth, unit is bytes / second.
func (c *xClient) SendFile(ctx context.Context, fileName string, rateInBytesPerSecond int64, meta map[string]string) error {
file, err := os.Open(fileName)
if err != nil {
return err
}
defer file.Close()
fi, err := os.Stat(fileName)
if err != nil {
return err
}
args := share.FileTransferArgs{
FileName: fi.Name(),
FileSize: fi.Size(),
Meta: meta,
}
ctx = setServerTimeout(ctx)
reply := &share.FileTransferReply{}
err = c.Call(ctx, "TransferFile", args, reply)
if err != nil {
return err
}
conn, err := net.DialTimeout("tcp", reply.Addr, c.option.ConnectTimeout)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write(reply.Token)
if err != nil {
return err
}
var tb *ratelimit.Bucket
if rateInBytesPerSecond > 0 {
tb = ratelimit.NewBucketWithRate(float64(rateInBytesPerSecond), rateInBytesPerSecond)
}
sendBuffer := make([]byte, FileTransferBufferSize)
loop:
for {
select {
case <-ctx.Done():
err = ctx.Err()
break loop
default:
if tb != nil {
tb.Wait(FileTransferBufferSize)
}
n, err := file.Read(sendBuffer)
if err != nil {
if err == io.EOF {
return nil
} else {
return err
}
}
if n == 0 {
break loop
}
_, err = conn.Write(sendBuffer[:n])
if err != nil {
if err == io.EOF {
return nil
} else {
return err
}
}
}
}
return nil
}
func (c *xClient) DownloadFile(ctx context.Context, requestFileName string, saveTo io.Writer, meta map[string]string) error {
ctx = setServerTimeout(ctx)
args := share.DownloadFileArgs{
FileName: requestFileName,
Meta: meta,
}
reply := &share.FileTransferReply{}
err := c.Call(ctx, "DownloadFile", args, reply)
if err != nil {
return err
}
conn, err := net.DialTimeout("tcp", reply.Addr, c.option.ConnectTimeout)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write(reply.Token)
if err != nil {
return err
}
buf := make([]byte, FileTransferBufferSize)
r := bufio.NewReader(conn)
loop:
for {
select {
case <-ctx.Done():
err = ctx.Err()
break loop
default:
n, er := r.Read(buf)
if n > 0 {
_, ew := saveTo.Write(buf[0:n])
if ew != nil {
err = ew
break loop
}
}
if er != nil {
if er != io.EOF {
err = er
}
break loop
}
}
}
return err
}
// Close closes this client and its underlying connections to services.
func (c *xClient) Close() error {
var errs []error
c.mu.Lock()
c.isShutdown = true
for k, v := range c.cachedClient {
e := v.Close()
if e != nil {
errs = append(errs, e)
}
delete(c.cachedClient, k)
}
c.mu.Unlock()
go func() {
defer func() {
recover()
}()
c.discovery.RemoveWatcher(c.ch)
close(c.ch)
}()
if len(errs) > 0 {
return ex.NewMultiError(errs)
}
return nil
}
func (c *xClient) Stream(ctx context.Context, meta map[string]string) (net.Conn, error) {
args := share.StreamServiceArgs{
Meta: meta,
}
ctx = setServerTimeout(ctx)
reply := &share.StreamServiceReply{}
err := c.Call(ctx, "Stream", args, reply)
if err != nil {
return nil, err
}
conn, err := net.DialTimeout("tcp", reply.Addr, c.option.ConnectTimeout)
if err != nil {
return nil, err
}
_, err = conn.Write(reply.Token)
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
================================================
FILE: client/xclient_pool.go
================================================
package client
import (
"sync"
"sync/atomic"
"github.com/smallnest/rpcx/protocol"
)
// XClientPool is a xclient pool with fixed size.
// It uses roundrobin algorithm to call its xclients.
// All xclients share the same configurations such as ServiceDiscovery and serverMessageChan.
type XClientPool struct {
count uint64
index uint64
xclients []XClient
mu sync.RWMutex
servicePath string
failMode FailMode
selectMode SelectMode
discovery ServiceDiscovery
option Option
auth string
Plugins PluginContainer
serverMessageChan chan<- *protocol.Message
}
// NewXClientPool creates a fixed size XClient pool.
func NewXClientPool(count int, servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) *XClientPool {
pool := &XClientPool{
count: uint64(count),
xclients: make([]XClient, count),
servicePath: servicePath,
failMode: failMode,
selectMode: selectMode,
discovery: discovery,
option: option,
}
for i := 0; i < count; i++ {
xclient := NewXClient(servicePath, failMode, selectMode, discovery, option)
pool.xclients[i] = xclient
}
return pool
}
// NewBidirectionalXClientPool creates a BidirectionalXClient pool with fixed size.
func NewBidirectionalXClientPool(count int, servicePath string, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option, serverMessageChan chan<- *protocol.Message) *XClientPool {
pool := &XClientPool{
count: uint64(count),
xclients: make([]XClient, count),
servicePath: servicePath,
failMode: failMode,
selectMode: selectMode,
discovery: discovery,
option: option,
serverMessageChan: serverMessageChan,
}
for i := 0; i < count; i++ {
xclient := NewBidirectionalXClient(servicePath, failMode, selectMode, discovery, option, serverMessageChan)
pool.xclients[i] = xclient
}
return pool
}
// Auth sets s token for Authentication.
func (c *XClientPool) Auth(auth string) {
c.auth = auth
c.mu.RLock()
for _, v := range c.xclients {
v.Auth(auth)
}
c.mu.RUnlock()
}
// SetPlugins sets client's plugins.
func (p *XClientPool) SetPlugins(plugins PluginContainer) {
p.Plugins = plugins
p.mu.RLock()
for _, v := range p.xclients {
v.SetPlugins(plugins)
}
p.mu.RUnlock()
}
// GetPlugins returns client's plugins.
func (p *XClientPool) GetPlugins() PluginContainer {
return p.Plugins
}
// Get returns a xclient.
// It does not remove this xclient from its cache so you don't need to put it back.
// Don't close this xclient because maybe other goroutines are using this xclient.
func (p *XClientPool) Get() XClient {
i := atomic.AddUint64(&p.index, 1)
picked := int(i % p.count)
return p.xclients[picked]
}
// Close this pool.
// Please make sure it won't be used any more.
func (p *XClientPool) Close() {
for _, c := range p.xclients {
c.Close()
}
p.xclients = nil
}
================================================
FILE: client/xclient_pool_test.go
================================================
package client
import (
"testing"
)
// testPlugin is a simple test plugin implementation
type testPlugin struct {
name string
}
func TestXClientPool_SetPlugins(t *testing.T) {
// Create a simple discovery
pairs := []*KVPair{
{Key: "tcp@127.0.0.1:8972", Value: ""},
}
discovery, err := NewMultipleServersDiscovery(pairs)
if err != nil {
t.Fatalf("failed to create discovery: %v", err)
}
defer discovery.Close()
// Create a pool
pool := NewXClientPool(3, "Arith", Failtry, RandomSelect, discovery, DefaultOption)
defer pool.Close()
// Create plugins
plugins := NewPluginContainer()
tp := &testPlugin{name: "test-plugin"}
plugins.Add(tp)
// Test SetPlugins
pool.SetPlugins(plugins)
// Verify plugins are set on pool
if pool.GetPlugins() == nil {
t.Error("plugins should not be nil after SetPlugins")
}
if pool.GetPlugins() != plugins {
t.Error("pool plugins should be the same as the set plugins")
}
// Verify plugins are set on all xclients
for i := 0; i < 3; i++ {
xclient := pool.Get()
if xclient.GetPlugins() == nil {
t.Errorf("xclient %d plugins should not be nil", i)
}
if xclient.GetPlugins() != plugins {
t.Errorf("xclient %d plugins should be the same as the set plugins", i)
}
}
}
func TestXClientPool_GetPlugins(t *testing.T) {
// Create a simple discovery
pairs := []*KVPair{
{Key: "tcp@127.0.0.1:8972", Value: ""},
}
discovery, err := NewMultipleServersDiscovery(pairs)
if err != nil {
t.Fatalf("failed to create discovery: %v", err)
}
defer discovery.Close()
// Create a pool
pool := NewXClientPool(2, "Arith", Failtry, RandomSelect, discovery, DefaultOption)
defer pool.Close()
// Initially, plugins should be nil
if pool.GetPlugins() != nil {
t.Error("plugins should be nil initially")
}
// Create and set plugins
plugins := NewPluginContainer()
tp := &testPlugin{name: "test-plugin"}
plugins.Add(tp)
pool.SetPlugins(plugins)
// Verify GetPlugins returns the correct plugins
retrievedPlugins := pool.GetPlugins()
if retrievedPlugins == nil {
t.Error("plugins should not be nil after SetPlugins")
}
if retrievedPlugins != plugins {
t.Error("GetPlugins should return the same plugins as SetPlugins")
}
// Verify plugins contain the test plugin
allPlugins := retrievedPlugins.All()
if len(allPlugins) != 1 {
t.Errorf("expected 1 plugin, got %d", len(allPlugins))
}
if p, ok := allPlugins[0].(*testPlugin); !ok || p.name != "test-plugin" {
t.Error("plugin should be the test plugin")
}
}
func TestXClientPool_SetPlugins_Concurrent(t *testing.T) {
// Create a simple discovery
pairs := []*KVPair{
{Key: "tcp@127.0.0.1:8972", Value: ""},
}
discovery, err := NewMultipleServersDiscovery(pairs)
if err != nil {
t.Fatalf("failed to create discovery: %v", err)
}
defer discovery.Close()
// Create a pool
pool := NewXClientPool(5, "Arith", Failtry, RandomSelect, discovery, DefaultOption)
defer pool.Close()
// Test concurrent SetPlugins calls
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func(id int) {
plugins := NewPluginContainer()
tp := &testPlugin{name: "test-plugin"}
plugins.Add(tp)
pool.SetPlugins(plugins)
done <- true
}(i)
}
// Wait for all goroutines to complete
for i := 0; i < 10; i++ {
<-done
}
// Verify plugins are set
if pool.GetPlugins() == nil {
t.Error("plugins should not be nil after concurrent SetPlugins")
}
}
================================================
FILE: client/xclient_test.go
================================================
package client
import (
"context"
"errors"
"testing"
"time"
"fmt"
testutils "github.com/smallnest/rpcx/_testutils"
"github.com/smallnest/rpcx/protocol"
"github.com/smallnest/rpcx/server"
"github.com/smallnest/rpcx/share"
)
func TestXClient_Thrift(t *testing.T) {
s := server.NewServer()
s.RegisterName("Arith", new(Arith), "")
go s.Serve("tcp", "127.0.0.1:0")
defer s.Close()
time.Sleep(500 * time.Millisecond)
addr := s.Address().String()
opt := Option{
Retries: 1,
RPCPath: share.DefaultRPCPath,
ConnectTimeout: 10 * time.Second,
SerializeType: protocol.Thrift,
CompressType: protocol.None,
BackupLatency: 10 * time.Millisecond,
}
d, err := NewPeer2PeerDiscovery("tcp@"+addr, "desc=a test service")
if err != nil {
t.Fatalf("failed to NewPeer2PeerDiscovery: %v", err)
}
xclient := NewXClient("Arith", Failtry, RandomSelect, d, opt)
defer xclient.Close()
args := testutils.ThriftArgs_{}
args.A = 200
args.B = 100
reply := testutils.ThriftReply{}
err = xclient.Call(context.Background(), "ThriftMul", &args, &reply)
if err != nil {
t.Fatalf("failed to call: %v", err)
}
fmt.Println(reply.C)
if reply.C != 20000 {
t.Fatalf("expect 20000 but got %d", reply.C)
}
}
func TestXClient_IT(t *testing.T) {
s := server.NewServer()
s.RegisterName("Arith", new(Arith), "")
go s.Serve("tcp", "127.0.0.1:0")
defer s.Close()
time.Sleep(500 * time.Millisecond)
addr := s.Address().String()
d, err := NewPeer2PeerDiscovery("tcp@"+addr, "desc=a test service")
if err != nil {
t.Fatalf("failed to NewPeer2PeerDiscovery: %v", err)
}
xclient := NewXClient("Arith", Failtry, RandomSelect, d, DefaultOption)
defer xclient.Close()
args := &Args{
A: 10,
B: 20,
}
reply := &Reply{}
err = xclient.Call(context.Background(), "Mul", args, reply)
if err != nil {
t.Fatalf("failed to call: %v", err)
}
if reply.C != 200 {
t.Fatalf("expect 200 but got %d", reply.C)
}
}
func TestXClient_filterByStateAndGroup(t *testing.T) {
servers := map[string]string{"a": "", "b": "state=inactive&ops=10", "c": "ops=20", "d": "group=test1&group=test&ops=20"}
filterByStateAndGroup("test", servers)
if _, ok := servers["b"]; ok {
t.Error("has not remove inactive node")
}
if _, ok := servers["a"]; ok {
t.Error("has not remove inactive node")
}
if _, ok := servers["c"]; ok {
t.Error("has not remove inactive node")
}
if _, ok := servers["d"]; !ok {
t.Error("node must be removed")
}
filterByStateAndGroup("test1", servers)
if _, ok := servers["d"]; !ok {
t.Error("node must be removed")
}
}
func TestUncoverError(t *testing.T) {
var e error = strErr("error")
if uncoverError(e) {
t.Fatalf("expect false but get true")
}
if uncoverError(context.DeadlineExceeded) {
t.Fatalf("expect false but get true")
}
if uncoverError(context.Canceled) {
t.Fatalf("expect false but get true")
}
e = errors.New("error")
if !uncoverError(e) {
t.Fatalf("expect true but get false")
}
}
================================================
FILE: clientplugin/req_rate_limiting_redis.go
================================================
package clientplugin
import (
"context"
"time"
"github.com/go-redis/redis_rate/v10"
"github.com/redis/go-redis/v9"
"github.com/smallnest/rpcx/client"
"github.com/smallnest/rpcx/server"
)
var _ client.PreCallPlugin = (*RedisRateLimitingPlugin)(nil)
// RedisRateLimitingPlugin can limit requests per unit time
type RedisRateLimitingPlugin struct {
addrs []string
limiter redis_rate.Limiter
limit redis_rate.Limit
}
// NewRedisRateLimitingPlugin creates a new RateLimitingPlugin
func NewRedisRateLimitingPlugin(addrs []string, rate int, burst int, period time.Duration) *RedisRateLimitingPlugin {
limit := redis_rate.Limit{
Rate: rate,
Burst: burst,
Period: period,
}
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: addrs,
})
limiter := redis_rate.NewLimiter(rdb)
return &RedisRateLimitingPlugin{
addrs: addrs,
limiter: *limiter,
limit: limit,
}
}
// PreCall can limit request processing.
func (plugin *RedisRateLimitingPlugin) PreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error {
res, err := plugin.limiter.Allow(ctx, servicePath+"/"+serviceMethod, plugin.limit)
if err != nil {
return err
}
if res.Allowed > 0 {
return nil
}
return server.ErrReqReachLimit
}
================================================
FILE: codec/codec.go
================================================
package codec
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"reflect"
pb "google.golang.org/protobuf/proto"
"github.com/apache/thrift/lib/go/thrift"
"github.com/gogo/protobuf/proto"
"github.com/tinylib/msgp/msgp"
"github.com/vmihailenco/msgpack/v5"
)
// Codec defines the interface that decode/encode payload.
type Codec interface {
Encode(i interface{}) ([]byte, error)
Decode(data []byte, i interface{}) error
}
// ByteCodec uses raw slice pf bytes and don't encode/decode.
type ByteCodec struct{}
// Encode returns raw slice of bytes.
func (c ByteCodec) Encode(i interface{}) ([]byte, error) {
if data, ok := i.([]byte); ok {
return data, nil
}
if data, ok := i.(*[]byte); ok {
return *data, nil
}
return nil, fmt.Errorf("%T is not a []byte", i)
}
// Decode returns raw slice of bytes.
func (c ByteCodec) Decode(data []byte, i interface{}) error {
reflect.Indirect(reflect.ValueOf(i)).SetBytes(data)
return nil
}
// JSONCodec uses json marshaler and unmarshaler.
type JSONCodec struct{}
// Encode encodes an object into slice of bytes.
func (c JSONCodec) Encode(i interface{}) ([]byte, error) {
return json.Marshal(i)
}
// Decode decodes an object from slice of bytes.
func (c JSONCodec) Decode(data []byte, i interface{}) error {
d := json.NewDecoder(bytes.NewBuffer(data))
d.UseNumber()
return d.Decode(i)
}
// PBCodec uses protobuf marshaler and unmarshaler.
type PBCodec struct{}
// Encode encodes an object into slice of bytes.
func (c PBCodec) Encode(i interface{}) ([]byte, error) {
if m, ok := i.(proto.Marshaler); ok {
return m.Marshal()
}
if m, ok := i.(pb.Message); ok {
return pb.Marshal(m)
}
return nil, fmt.Errorf("%T is not a proto.Marshaler or pb.Message", i)
}
// Decode decodes an object from slice of bytes.
func (c PBCodec) Decode(data []byte, i interface{}) error {
if m, ok := i.(proto.Unmarshaler); ok {
return m.Unmarshal(data)
}
if m, ok := i.(pb.Message); ok {
return pb.Unmarshal(data, m)
}
return fmt.Errorf("%T is not a proto.Unmarshaler or pb.Message", i)
}
// MsgpackCodec uses messagepack marshaler and unmarshaler.
type MsgpackCodec struct{}
// Encode encodes an object into slice of bytes.
func (c MsgpackCodec) Encode(i interface{}) ([]byte, error) {
if m, ok := i.(msgp.Marshaler); ok {
return m.MarshalMsg(nil)
}
var buf bytes.Buffer
enc := msgpack.NewEncoder(&buf)
// enc.UseJSONTag(true)
err := enc.Encode(i)
return buf.Bytes(), err
}
// Decode decodes an object from slice of bytes.
func (c MsgpackCodec) Decode(data []byte, i interface{}) error {
if m, ok := i.(msgp.Unmarshaler); ok {
_, err := m.UnmarshalMsg(data)
return err
}
dec := msgpack.NewDecoder(bytes.NewReader(data))
// dec.UseJSONTag(true)
err := dec.Decode(i)
return err
}
type ThriftCodec struct{}
func (c ThriftCodec) Encode(i interface{}) ([]byte, error) {
b := thrift.NewTMemoryBufferLen(1024)
p := thrift.NewTBinaryProtocolFactoryConf(&thrift.TConfiguration{}).
GetProtocol(b)
t := &thrift.TSerializer{
Transport: b,
Protocol: p,
}
t.Transport.Close()
if msg, ok := i.(thrift.TStruct); ok {
return t.Write(context.Background(), msg)
}
return nil, errors.New("type assertion failed")
}
func (c ThriftCodec) Decode(data []byte, i interface{}) error {
t := thrift.NewTMemoryBufferLen(1024)
p := thrift.NewTBinaryProtocolFactoryConf(&thrift.TConfiguration{}).
GetProtocol(t)
d := &thrift.TDeserializer{
Transport: t,
Protocol: p,
}
d.Transport.Close()
return d.Read(context.Background(), i.(thrift.TStruct), data)
}
================================================
FILE: codec/codec_test.go
================================================
package codec
import (
"testing"
"github.com/smallnest/rpcx/codec/testdata"
)
type ColorGroup struct {
Id int `json:"id" xml:"id,attr" msg:"id"`
Name string `json:"name" xml:"name" msg:"name"`
Colors []string `json:"colors" xml:"colors" msg:"colors"`
}
var group = ColorGroup{
Id: 1,
Name: "Reds",
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
func BenchmarkJSONCodec_Encode(b *testing.B) {
var raw = make([]byte, 0, 1024)
serializer := JSONCodec{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
raw, _ = serializer.Encode(group)
}
b.ReportMetric(float64(len(raw)), "bytes")
}
func BenchmarkPBCodec_Encode(b *testing.B) {
var raw = make([]byte, 0, 1024)
serializer := PBCodec{}
group := testdata.ProtoColorGroup{
Id: 1,
Name: "Reds",
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
raw, _ = serializer.Encode(&group)
}
b.ReportMetric(float64(len(raw)), "bytes")
}
func BenchmarkMsgpackCodec_Encode(b *testing.B) {
var raw = make([]byte, 0, 1024)
serializer := MsgpackCodec{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
raw, _ = serializer.Encode(group)
}
b.ReportMetric(float64(len(raw)), "bytes")
}
func BenchmarkThriftCodec_Encode(b *testing.B) {
var bb = make([]byte, 0, 1024)
serializer := ThriftCodec{}
thriftColorGroup := testdata.ThriftColorGroup{
ID: 1,
Name: "Reds",
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
bb, _ = serializer.Encode(&thriftColorGroup)
}
b.ReportMetric(float64(len(bb)), "bytes")
}
func BenchmarkJSONCodec_Decode(b *testing.B) {
serializer := JSONCodec{}
bytes, _ := serializer.Encode(group)
result := ColorGroup{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = serializer.Decode(bytes, &result)
}
}
func BenchmarkPBCodec_Decode(b *testing.B) {
serializer := PBCodec{}
bytes, _ := serializer.Encode(group)
result := ColorGroup{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = serializer.Decode(bytes, &result)
}
}
func BenchmarkMsgpackCodec_Decode(b *testing.B) {
serializer := MsgpackCodec{}
bytes, _ := serializer.Encode(group)
result := ColorGroup{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = serializer.Decode(bytes, &result)
}
}
func BenchmarkThriftCodec_Decode(b *testing.B) {
serializer := ThriftCodec{}
thriftColorGroup := testdata.ThriftColorGroup{
ID: 1,
Name: "Reds",
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
bytes, _ := serializer.Encode(&thriftColorGroup)
result := testdata.ThriftColorGroup{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = serializer.Decode(bytes, &result)
}
}
================================================
FILE: codec/testdata/GoUnusedProtection__.go
================================================
// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.
package testdata
var GoUnusedProtection__ int;
================================================
FILE: codec/testdata/gen.sh
================================================
# generate .go files from IDL
protoc -I. --go_out=. --go_opt=module="testdata" ./protobuf.proto
thrift -r -out ../ --gen go ./thrift_colorgroup.thrift
# # run benchmarks
# go test -bench=. -run=none
# # clean files
# rm -rf ./testdata/*.go
================================================
FILE: codec/testdata/protobuf.pb.go
================================================
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v5.26.0
// source: protobuf.proto
package testdata
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ProtoColorGroup struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Colors []string `protobuf:"bytes,3,rep,name=colors,proto3" json:"colors,omitempty"`
}
func (x *ProtoColorGroup) Reset() {
*x = ProtoColorGroup{}
if protoimpl.UnsafeEnabled {
mi := &file_protobuf_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ProtoColorGroup) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProtoColorGroup) ProtoMessage() {}
func (x *ProtoColorGroup) ProtoReflect() protoreflect.Message {
mi := &file_protobuf_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProtoColorGroup.ProtoReflect.Descriptor instead.
func (*ProtoColorGroup) Descriptor() ([]byte, []int) {
return file_protobuf_proto_rawDescGZIP(), []int{0}
}
func (x *ProtoColorGroup) GetId() int32 {
if x != nil {
return x.Id
}
return 0
}
func (x *ProtoColorGroup) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *ProtoColorGroup) GetColors() []string {
if x != nil {
return x.Colors
}
return nil
}
var File_protobuf_proto protoreflect.FileDescriptor
var file_protobuf_proto_rawDesc = []byte{
0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x08, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4d, 0x0a, 0x0f, 0x50, 0x72,
0x6f, 0x74, 0x6f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x0e, 0x0a,
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
0x09, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x73, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x74,
0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_protobuf_proto_rawDescOnce sync.Once
file_protobuf_proto_rawDescData = file_protobuf_proto_rawDesc
)
func file_protobuf_proto_rawDescGZIP() []byte {
file_protobuf_proto_rawDescOnce.Do(func() {
file_protobuf_proto_rawDescData = protoimpl.X.CompressGZIP(file_protobuf_proto_rawDescData)
})
return file_protobuf_proto_rawDescData
}
var file_protobuf_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_protobuf_proto_goTypes = []interface{}{
(*ProtoColorGroup)(nil), // 0: testdata.ProtoColorGroup
}
var file_protobuf_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_protobuf_proto_init() }
func file_protobuf_proto_init() {
if File_protobuf_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_protobuf_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ProtoColorGroup); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_protobuf_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_protobuf_proto_goTypes,
DependencyIndexes: file_protobuf_proto_depIdxs,
MessageInfos: file_protobuf_proto_msgTypes,
}.Build()
File_protobuf_proto = out.File
file_protobuf_proto_rawDesc = nil
file_protobuf_proto_goTypes = nil
file_protobuf_proto_depIdxs = nil
}
================================================
FILE: codec/testdata/protobuf.proto
================================================
syntax = "proto3";
package testdata;
option go_package = "./testdata";
message ProtoColorGroup {
int32 id = 1;
string name = 2;
repeated string colors = 3;
}
================================================
FILE: codec/testdata/thrift_colorgroup-consts.go
================================================
// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.
package testdata
import(
"bytes"
"context"
"fmt"
"time"
"github.com/apache/thrift/lib/go/thrift"
)
// (needed to ensure safety because of naive import list construction.)
var _ = thrift.ZERO
var _ = fmt.Printf
var _ = context.Background
var _ = time.Now
var _ = bytes.Equal
func init() {
}
================================================
FILE: codec/testdata/thrift_colorgroup.go
================================================
// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.
package testdata
import(
"bytes"
"context"
"fmt"
"time"
"github.com/apache/thrift/lib/go/thrift"
)
// (needed to ensure safety because of naive import list construction.)
var _ = thrift.ZERO
var _ = fmt.Printf
var _ = context.Background
var _ = time.Now
var _ = bytes.Equal
// Attributes:
// - ID
// - Name
// - Colors
type ThriftColorGroup struct {
ID int32 `thrift:"id,1" db:"id" json:"id"`
Name string `thrift:"name,2" db:"name" json:"name"`
Colors []string `thrift:"colors,3" db:"colors" json:"colors"`
}
func NewThriftColorGroup() *ThriftColorGroup {
return &ThriftColorGroup{}
}
func (p *ThriftColorGroup) GetID() int32 {
return p.ID
}
func (p *ThriftColorGroup) GetName() string {
return p.Name
}
func (p *ThriftColorGroup) GetColors() []string {
return p.Colors
}
func (p *ThriftColorGroup) Read(ctx context.Context, iprot thrift.TProtocol) error {
if _, err := iprot.ReadStructBegin(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read error: ", p), err)
}
for {
_, fieldTypeId, fieldId, err := iprot.ReadFieldBegin(ctx)
if err != nil {
return thrift.PrependError(fmt.Sprintf("%T field %d read error: ", p, fieldId), err)
}
if fieldTypeId == thrift.STOP { break; }
switch fieldId {
case 1:
if fieldTypeId == thrift.I32 {
if err := p.ReadField1(ctx, iprot); err != nil {
return err
}
} else {
if err := iprot.Skip(ctx, fieldTypeId); err != nil {
return err
}
}
case 2:
if fieldTypeId == thrift.STRING {
if err := p.ReadField2(ctx, iprot); err != nil {
return err
}
} else {
if err := iprot.Skip(ctx, fieldTypeId); err != nil {
return err
}
}
case 3:
if fieldTypeId == thrift.LIST {
if err := p.ReadField3(ctx, iprot); err != nil {
return err
}
} else {
if err := iprot.Skip(ctx, fieldTypeId); err != nil {
return err
}
}
default:
if err := iprot.Skip(ctx, fieldTypeId); err != nil {
return err
}
}
if err := iprot.ReadFieldEnd(ctx); err != nil {
return err
}
}
if err := iprot.ReadStructEnd(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
}
return nil
}
func (p *ThriftColorGroup) ReadField1(ctx context.Context, iprot thrift.TProtocol) error {
if v, err := iprot.ReadI32(ctx); err != nil {
return thrift.PrependError("error reading field 1: ", err)
} else {
p.ID = v
}
return nil
}
func (p *ThriftColorGroup) ReadField2(ctx context.Context, iprot thrift.TProtocol) error {
if v, err := iprot.ReadString(ctx); err != nil {
return thrift.PrependError("error reading field 2: ", err)
} else {
p.Name = v
}
return nil
}
func (p *ThriftColorGroup) ReadField3(ctx context.Context, iprot thrift.TProtocol) error {
_, size, err := iprot.ReadListBegin(ctx)
if err != nil {
return thrift.PrependError("error reading list begin: ", err)
}
tSlice := make([]string, 0, size)
p.Colors = tSlice
for i := 0; i < size; i ++ {
var _elem0 string
if v, err := iprot.ReadString(ctx); err != nil {
return thrift.PrependError("error reading field 0: ", err)
} else {
_elem0 = v
}
p.Colors = append(p.Colors, _elem0)
}
if err := iprot.ReadListEnd(ctx); err != nil {
return thrift.PrependError("error reading list end: ", err)
}
return nil
}
func (p *ThriftColorGroup) Write(ctx context.Context, oprot thrift.TProtocol) error {
if err := oprot.WriteStructBegin(ctx, "ThriftColorGroup"); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) }
if p != nil {
if err := p.writeField1(ctx, oprot); err != nil { return err }
if err := p.writeField2(ctx, oprot); err != nil { return err }
if err := p.writeField3(ctx, oprot); err != nil { return err }
}
if err := oprot.WriteFieldStop(ctx); err != nil {
return thrift.PrependError("write field stop error: ", err) }
if err := oprot.WriteStructEnd(ctx); err != nil {
return thrift.PrependError("write struct stop error: ", err) }
return nil
}
func (p *ThriftColorGroup) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin(ctx, "id", thrift.I32, 1); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:id: ", p), err) }
if err := oprot.WriteI32(ctx, int32(p.ID)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.id (1) field write error: ", p), err) }
if err := oprot.WriteFieldEnd(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 1:id: ", p), err) }
return err
}
func (p *ThriftColorGroup) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin(ctx, "name", thrift.STRING, 2); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:name: ", p), err) }
if err := oprot.WriteString(ctx, string(p.Name)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T.name (2) field write error: ", p), err) }
if err := oprot.WriteFieldEnd(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 2:name: ", p), err) }
return err
}
func (p *ThriftColorGroup) writeField3(ctx context.Context, oprot thrift.TProtocol) (err error) {
if err := oprot.WriteFieldBegin(ctx, "colors", thrift.LIST, 3); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field begin error 3:colors: ", p), err) }
if err := oprot.WriteListBegin(ctx, thrift.STRING, len(p.Colors)); err != nil {
return thrift.PrependError("error writing list begin: ", err)
}
for _, v := range p.Colors {
if err := oprot.WriteString(ctx, string(v)); err != nil {
return thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err) }
}
if err := oprot.WriteListEnd(ctx); err != nil {
return thrift.PrependError("error writing list end: ", err)
}
if err := oprot.WriteFieldEnd(ctx); err != nil {
return thrift.PrependError(fmt.Sprintf("%T write field end error 3:colors: ", p), err) }
return err
}
func (p *ThriftColorGroup) Equals(other *ThriftColorGroup) bool {
if p == other {
return true
} else if p == nil || other == nil {
return false
}
if p.ID != other.ID { return false }
if p.Name != other.Name { return false }
if len(p.Colors) != len(other.Colors) { return false }
for i, _tgt := range p.Colors {
_src1 := other.Colors[i]
if _tgt != _src1 { return false }
}
return true
}
func (p *ThriftColorGroup) String() string {
if p == nil {
return "<nil>"
}
return fmt.Sprintf("ThriftColorGroup(%+v)", *p)
}
================================================
FILE: codec/testdata/thrift_colorgroup.thrift
================================================
namespace go testdata
struct ThriftColorGroup {
1: i32 id = 0,
2: string name,
3: list<string> colors,
}
================================================
FILE: errors/error.go
================================================
package errors
import (
"fmt"
"sync"
)
// MultiError holds multiple errors
type MultiError struct {
Errors []error
mu sync.Mutex
}
// Error returns the message of the actual error
func (e *MultiError) Error() string {
return fmt.Sprin
gitextract_r3mw_qpx/
├── .github/
│ └── workflows/
│ └── go.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── _testutils/
│ ├── GoUnusedProtection__.go
│ ├── arith_service.go
│ ├── arith_service.proto
│ ├── thrift_arith_service-consts.go
│ ├── thrift_arith_service.go
│ └── thrift_arith_service.thrift
├── client/
│ ├── cache_client_builder.go
│ ├── circuit_breaker.go
│ ├── circuit_breaker_test.go
│ ├── client.go
│ ├── client_test.go
│ ├── connection.go
│ ├── connection_iouring.go
│ ├── connection_iouring_test.go
│ ├── connection_kcp.go
│ ├── connection_memu.go
│ ├── connection_nonkcp.go
│ ├── connection_nonquic.go
│ ├── connection_quic.go
│ ├── connection_rdma.go
│ ├── discovery.go
│ ├── dns_discovery.go
│ ├── failmode_enumer.go
│ ├── geo_utils.go
│ ├── hash_utils.go
│ ├── mdns_discovery.go
│ ├── mode.go
│ ├── multiple_servers_discovery.go
│ ├── oneclient.go
│ ├── oneclient_pool.go
│ ├── oneclient_pool_test.go
│ ├── peer2peer_discovery.go
│ ├── ping_utils.go
│ ├── plugin.go
│ ├── selectmode_enumer.go
│ ├── selector.go
│ ├── selector_test.go
│ ├── smooth_weighted_round_robin.go
│ ├── xclient.go
│ ├── xclient_pool.go
│ ├── xclient_pool_test.go
│ └── xclient_test.go
├── clientplugin/
│ └── req_rate_limiting_redis.go
├── codec/
│ ├── codec.go
│ ├── codec_test.go
│ └── testdata/
│ ├── GoUnusedProtection__.go
│ ├── gen.sh
│ ├── protobuf.pb.go
│ ├── protobuf.proto
│ ├── thrift_colorgroup-consts.go
│ ├── thrift_colorgroup.go
│ └── thrift_colorgroup.thrift
├── errors/
│ ├── error.go
│ └── error_test.go
├── go.mod
├── go.sum
├── log/
│ ├── default_logger.go
│ ├── dummy_logger.go
│ └── logger.go
├── protocol/
│ ├── compressor.go
│ ├── compressor_test.go
│ ├── message.go
│ ├── message_chan_test.go
│ ├── message_test.go
│ └── testdata/
│ ├── benchmark.pb.go
│ ├── benchmark.proto
│ └── gen.sh
├── reflection/
│ ├── server_reflection.go
│ └── server_reflection_test.go
├── server/
│ ├── context.go
│ ├── converter.go
│ ├── converter_test.go
│ ├── file_transfer.go
│ ├── gateway.go
│ ├── jsonrpc2.go
│ ├── jsonrpc2_wire.go
│ ├── kcp.go
│ ├── listener.go
│ ├── listener_linux.go
│ ├── listener_rdma.go
│ ├── listener_unix.go
│ ├── memconn.go
│ ├── option.go
│ ├── option_test.go
│ ├── options.go
│ ├── plugin.go
│ ├── plugin_test.go
│ ├── pool.go
│ ├── pool_test.go
│ ├── quic.go
│ ├── router.go
│ ├── server.go
│ ├── server_test.go
│ ├── service.go
│ ├── service_test.go
│ └── stream.go
├── serverplugin/
│ ├── alias.go
│ ├── blacklist.go
│ ├── mdns.go
│ ├── metrics.go
│ ├── rate_limiting.go
│ ├── redis.go
│ ├── registry_test.go
│ ├── req_rate_limiting.go
│ ├── req_rate_limiting_redis.go
│ ├── tee.go
│ └── whitelist.go
├── share/
│ ├── context.go
│ ├── context_test.go
│ ├── share.go
│ └── share_test.go
├── tool/
│ └── xgen/
│ ├── README.md
│ ├── parser/
│ │ └── parser.go
│ └── xgen.go
└── util/
├── buffer_pool.go
├── buffer_pool_test.go
├── compress.go
├── compress_test.go
├── converter.go
├── net.go
└── net_test.go
SYMBOL INDEX (1003 symbols across 108 files)
FILE: _testutils/arith_service.go
constant _ (line 32) | _ = proto.GoGoProtoPackageIsVersion2
type ProtoArgs (line 34) | type ProtoArgs struct
method Reset (line 39) | func (m *ProtoArgs) Reset() { *m = ProtoArgs{} }
method String (line 40) | func (m *ProtoArgs) String() string { return proto.CompactT...
method ProtoMessage (line 41) | func (*ProtoArgs) ProtoMessage() {}
method Descriptor (line 42) | func (*ProtoArgs) Descriptor() ([]byte, []int) { return fileDescriptor...
method GetA (line 44) | func (m *ProtoArgs) GetA() int32 {
method GetB (line 51) | func (m *ProtoArgs) GetB() int32 {
method Marshal (line 78) | func (m *ProtoArgs) Marshal() (dAtA []byte, err error) {
method MarshalTo (line 88) | func (m *ProtoArgs) MarshalTo(dAtA []byte) (int, error) {
method Size (line 156) | func (m *ProtoArgs) Size() (n int) {
method Unmarshal (line 190) | func (m *ProtoArgs) Unmarshal(dAtA []byte) error {
type ProtoReply (line 58) | type ProtoReply struct
method Reset (line 62) | func (m *ProtoReply) Reset() { *m = ProtoReply{} }
method String (line 63) | func (m *ProtoReply) String() string { return proto.Compact...
method ProtoMessage (line 64) | func (*ProtoReply) ProtoMessage() {}
method Descriptor (line 65) | func (*ProtoReply) Descriptor() ([]byte, []int) { return fileDescripto...
method GetC (line 67) | func (m *ProtoReply) GetC() int32 {
method Marshal (line 106) | func (m *ProtoReply) Marshal() (dAtA []byte, err error) {
method MarshalTo (line 116) | func (m *ProtoReply) MarshalTo(dAtA []byte) (int, error) {
method Size (line 168) | func (m *ProtoReply) Size() (n int) {
method Unmarshal (line 278) | func (m *ProtoReply) Unmarshal(dAtA []byte) error {
function init (line 74) | func init() {
function encodeFixed64ArithService (line 129) | func encodeFixed64ArithService(dAtA []byte, offset int, v uint64) int {
function encodeFixed32ArithService (line 140) | func encodeFixed32ArithService(dAtA []byte, offset int, v uint32) int {
function encodeVarintArithService (line 147) | func encodeVarintArithService(dAtA []byte, offset int, v uint64) int {
function sovArithService (line 177) | func sovArithService(x uint64) (n int) {
function sozArithService (line 187) | func sozArithService(x uint64) (n int) {
function skipArithService (line 347) | func skipArithService(dAtA []byte) (n int, err error) {
function init (line 452) | func init() { proto.RegisterFile("arith_service.proto", fileDescriptorAr...
FILE: _testutils/thrift_arith_service-consts.go
function init (line 20) | func init() {
FILE: _testutils/thrift_arith_service.go
type ThriftArgs_ (line 23) | type ThriftArgs_ struct
method GetA (line 32) | func (p *ThriftArgs_) GetA() int32 {
method GetB (line 36) | func (p *ThriftArgs_) GetB() int32 {
method Read (line 39) | func (p *ThriftArgs_) Read(ctx context.Context, iprot thrift.TProtocol...
method ReadField1 (line 88) | func (p *ThriftArgs_) ReadField1(ctx context.Context, iprot thrift.TPr...
method ReadField2 (line 97) | func (p *ThriftArgs_) ReadField2(ctx context.Context, iprot thrift.TPr...
method Write (line 106) | func (p *ThriftArgs_) Write(ctx context.Context, oprot thrift.TProtoco...
method writeField1 (line 127) | func (p *ThriftArgs_) writeField1(ctx context.Context, oprot thrift.TP...
method writeField2 (line 140) | func (p *ThriftArgs_) writeField2(ctx context.Context, oprot thrift.TP...
method Equals (line 153) | func (p *ThriftArgs_) Equals(other *ThriftArgs_) bool {
method String (line 168) | func (p *ThriftArgs_) String() string {
function NewThriftArgs_ (line 28) | func NewThriftArgs_() *ThriftArgs_ {
type ThriftReply (line 177) | type ThriftReply struct
method GetC (line 185) | func (p *ThriftReply) GetC() int32 {
method Read (line 188) | func (p *ThriftReply) Read(ctx context.Context, iprot thrift.TProtocol...
method ReadField1 (line 227) | func (p *ThriftReply) ReadField1(ctx context.Context, iprot thrift.TPr...
method Write (line 236) | func (p *ThriftReply) Write(ctx context.Context, oprot thrift.TProtoco...
method writeField1 (line 254) | func (p *ThriftReply) writeField1(ctx context.Context, oprot thrift.TP...
method Equals (line 267) | func (p *ThriftReply) Equals(other *ThriftReply) bool {
method String (line 279) | func (p *ThriftReply) String() string {
function NewThriftReply (line 181) | func NewThriftReply() *ThriftReply {
FILE: client/cache_client_builder.go
type CacheClientBuilder (line 6) | type CacheClientBuilder interface
function RegisterCacheClientBuilder (line 18) | func RegisterCacheClientBuilder(network string, builder CacheClientBuild...
function getCacheClientBuilder (line 25) | func getCacheClientBuilder(network string) (CacheClientBuilder, bool) {
FILE: client/circuit_breaker.go
type ConsecCircuitBreaker (line 15) | type ConsecCircuitBreaker struct
method Call (line 32) | func (cb *ConsecCircuitBreaker) Call(fn func() error, d time.Duration)...
method ready (line 67) | func (cb *ConsecCircuitBreaker) ready() bool {
method success (line 78) | func (cb *ConsecCircuitBreaker) success() {
method fail (line 82) | func (cb *ConsecCircuitBreaker) fail() {
method Success (line 87) | func (cb *ConsecCircuitBreaker) Success() {
method Fail (line 91) | func (cb *ConsecCircuitBreaker) Fail() {
method Ready (line 95) | func (cb *ConsecCircuitBreaker) Ready() bool {
method reset (line 99) | func (cb *ConsecCircuitBreaker) reset() {
function NewConsecCircuitBreaker (line 24) | func NewConsecCircuitBreaker(failureThreshold uint64, window time.Durati...
FILE: client/circuit_breaker_test.go
function TestConsecCircuitBreaker (line 10) | func TestConsecCircuitBreaker(t *testing.T) {
function TestCircuitBreakerRace (line 56) | func TestCircuitBreakerRace(t *testing.T) {
FILE: client/client.go
constant XVersion (line 24) | XVersion = "X-RPCX-Version"
constant XMessageType (line 25) | XMessageType = "X-RPCX-MesssageType"
constant XHeartbeat (line 26) | XHeartbeat = "X-RPCX-Heartbeat"
constant XOneway (line 27) | XOneway = "X-RPCX-Oneway"
constant XMessageStatusType (line 28) | XMessageStatusType = "X-RPCX-MessageStatusType"
constant XSerializeType (line 29) | XSerializeType = "X-RPCX-SerializeType"
constant XMessageID (line 30) | XMessageID = "X-RPCX-MessageID"
constant XServicePath (line 31) | XServicePath = "X-RPCX-ServicePath"
constant XServiceMethod (line 32) | XServiceMethod = "X-RPCX-ServiceMethod"
constant XMeta (line 33) | XMeta = "X-RPCX-Meta"
constant XErrorMessage (line 34) | XErrorMessage = "X-RPCX-ErrorMessage"
type ServiceError (line 38) | type ServiceError interface
function NewServiceError (line 44) | func NewServiceError(s string) ServiceError {
type strErr (line 51) | type strErr
method Error (line 53) | func (s strErr) Error() string {
method IsServiceError (line 57) | func (s strErr) IsServiceError() bool {
type Breaker (line 76) | type Breaker interface
constant ReaderBuffsize (line 94) | ReaderBuffsize = 16 * 1024
constant WriterBuffsize (line 96) | WriterBuffsize = 16 * 1024
type seqKey (line 99) | type seqKey struct
type RPCClient (line 102) | type RPCClient interface
type Client (line 120) | type Client struct
method RemoteAddr (line 147) | func (client *Client) RemoteAddr() string {
method GetConn (line 152) | func (client *Client) GetConn() net.Conn {
method RegisterServerMessageChan (line 235) | func (client *Client) RegisterServerMessageChan(ch chan<- *protocol.Me...
method UnregisterServerMessageChan (line 240) | func (client *Client) UnregisterServerMessageChan() {
method IsClosing (line 245) | func (client *Client) IsClosing() bool {
method IsShutdown (line 252) | func (client *Client) IsShutdown() bool {
method Go (line 262) | func (client *Client) Go(ctx context.Context, servicePath, serviceMeth...
method Call (line 303) | func (client *Client) Call(ctx context.Context, servicePath, serviceMe...
method call (line 307) | func (client *Client) call(ctx context.Context, servicePath, serviceMe...
method SendRaw (line 366) | func (client *Client) SendRaw(ctx context.Context, r *protocol.Message...
method send (line 512) | func (client *Client) send(ctx context.Context, call *Call) {
method input (line 638) | func (client *Client) input() {
method handleServerRequest (line 784) | func (client *Client) handleServerRequest(msg *protocol.Message) {
method heartbeat (line 806) | func (client *Client) heartbeat() {
method Close (line 846) | func (client *Client) Close() error {
function NewClient (line 140) | func NewClient(option Option) *Client {
type Option (line 157) | type Option struct
type Call (line 208) | type Call struct
method done (line 220) | func (call *Call) done() {
function convertRes2Raw (line 466) | func convertRes2Raw(res *protocol.Message) (map[string]string, []byte, e...
function urlencode (line 494) | func urlencode(data map[string]string) string {
FILE: client/client_test.go
type Args (line 17) | type Args struct
type Reply (line 22) | type Reply struct
type Arith (line 26) | type Arith
method Mul (line 28) | func (t *Arith) Mul(ctx context.Context, args *Args, reply *Reply) err...
method ThriftMul (line 40) | func (t *Arith) ThriftMul(ctx context.Context, args *testutils.ThriftA...
type PBArith (line 33) | type PBArith
method Mul (line 35) | func (t *PBArith) Mul(ctx context.Context, args *testutils.ProtoArgs, ...
type Bidirectional (line 45) | type Bidirectional struct
method Mul (line 49) | func (t *Bidirectional) Mul(ctx context.Context, args *Args, reply *Re...
function TestClient_IT (line 56) | func TestClient_IT(t *testing.T) {
function TestClient_IT_Concurrency (line 126) | func TestClient_IT_Concurrency(t *testing.T) {
function testSendRaw (line 157) | func testSendRaw(t *testing.T, client *Client, seq uint64, x, y int32, w...
function TestClient_Res_Reset (line 192) | func TestClient_Res_Reset(t *testing.T) {
function TestClient_Bidirectional (line 203) | func TestClient_Bidirectional(t *testing.T) {
FILE: client/connection.go
type ConnFactoryFn (line 18) | type ConnFactoryFn
function init (line 22) | func init() {
method Connect (line 32) | func (client *Client) Connect(network, address string) error {
function newDirectConn (line 87) | func newDirectConn(c *Client, network, address string) (net.Conn, error) {
function newDirectHTTPConn (line 118) | func newDirectHTTPConn(c *Client, network, address string) (net.Conn, er...
function newDirectWSConn (line 176) | func newDirectWSConn(c *Client, network, address string) (net.Conn, erro...
FILE: client/connection_iouring.go
function newIOUringConn (line 8) | func newIOUringConn(c *Client, network, address string) (net.Conn, error) {
FILE: client/connection_kcp.go
function newDirectKCPConn (line 11) | func newDirectKCPConn(c *Client, network, address string) (net.Conn, err...
FILE: client/connection_memu.go
function newMemuConn (line 9) | func newMemuConn(c *Client, network, address string) (net.Conn, error) {
FILE: client/connection_nonkcp.go
function newDirectKCPConn (line 10) | func newDirectKCPConn(c *Client, network, address string) (net.Conn, err...
FILE: client/connection_nonquic.go
function newDirectQuicConn (line 11) | func newDirectQuicConn(c *Client, network, address string) (net.Conn, er...
FILE: client/connection_quic.go
function newDirectQuicConn (line 14) | func newDirectQuicConn(c *Client, network, address string) (net.Conn, er...
FILE: client/connection_rdma.go
function init (line 13) | func init() {
function newRDMAConn (line 17) | func newRDMAConn(c *Client, network, address string) (net.Conn, error) {
FILE: client/discovery.go
type ServiceDiscoveryFilter (line 13) | type ServiceDiscoveryFilter
type ServiceDiscovery (line 16) | type ServiceDiscovery interface
type cachedServiceDiscovery (line 25) | type cachedServiceDiscovery struct
method GetServices (line 60) | func (cd *cachedServiceDiscovery) GetServices() []*KVPair {
method WatchService (line 80) | func (cd *cachedServiceDiscovery) WatchService() chan []*KVPair {
method RemoveWatcher (line 111) | func (cd *cachedServiceDiscovery) RemoveWatcher(ch chan []*KVPair) {
method storeCached (line 123) | func (cd *cachedServiceDiscovery) storeCached(kvPairs []*KVPair) {
method loadCached (line 128) | func (cd *cachedServiceDiscovery) loadCached() (kvPairs []*KVPair) {
function CacheDiscovery (line 38) | func CacheDiscovery(threshold int, cachedFile string, discovery ServiceD...
FILE: client/dns_discovery.go
type DNSDiscovery (line 15) | type DNSDiscovery struct
method Clone (line 41) | func (d *DNSDiscovery) Clone(servicePath string) (ServiceDiscovery, er...
method SetFilter (line 46) | func (d *DNSDiscovery) SetFilter(filter ServiceDiscoveryFilter) {
method GetServices (line 51) | func (d *DNSDiscovery) GetServices() []*KVPair {
method WatchService (line 58) | func (d *DNSDiscovery) WatchService() chan []*KVPair {
method RemoveWatcher (line 67) | func (d *DNSDiscovery) RemoveWatcher(ch chan []*KVPair) {
method lookup (line 83) | func (d *DNSDiscovery) lookup() {
method watch (line 127) | func (d *DNSDiscovery) watch() {
method Close (line 141) | func (d *DNSDiscovery) Close() {
function NewDNSDiscovery (line 33) | func NewDNSDiscovery(domain string, network string, port int, d time.Dur...
FILE: client/failmode_enumer.go
constant _FailModeName (line 9) | _FailModeName = "FailoverFailfastFailtryFailbackup"
method String (line 13) | func (i FailMode) String() string {
function FailModeString (line 31) | func FailModeString(s string) (FailMode, error) {
function FailModeValues (line 39) | func FailModeValues() []FailMode {
method IsAFailMode (line 44) | func (i FailMode) IsAFailMode() bool {
FILE: client/geo_utils.go
function getDistanceFrom (line 8) | func getDistanceFrom(lat1, lon1, lat2, lon2 float64) float64 {
function hsin (line 23) | func hsin(theta float64) float64 {
FILE: client/hash_utils.go
function Hash (line 9) | func Hash(key uint64, buckets int32) int32 {
function HashString (line 26) | func HashString(s string) uint64 {
type HashServiceAndArgs (line 33) | type HashServiceAndArgs
type ConsistentAddrStrFunction (line 37) | type ConsistentAddrStrFunction
function genKey (line 39) | func genKey(options ...interface{}) uint64 {
function JumpConsistentHash (line 49) | func JumpConsistentHash(len int, options ...interface{}) int {
function toString (line 53) | func toString(obj interface{}) string {
FILE: client/mdns_discovery.go
type serviceMeta (line 14) | type serviceMeta struct
type MDNSDiscovery (line 22) | type MDNSDiscovery struct
method Clone (line 59) | func (d *MDNSDiscovery) Clone(servicePath string) (ServiceDiscovery, e...
method SetFilter (line 64) | func (d *MDNSDiscovery) SetFilter(filter ServiceDiscoveryFilter) {
method GetServices (line 69) | func (d *MDNSDiscovery) GetServices() []*KVPair {
method WatchService (line 77) | func (d *MDNSDiscovery) WatchService() chan []*KVPair {
method RemoveWatcher (line 86) | func (d *MDNSDiscovery) RemoveWatcher(ch chan []*KVPair) {
method watch (line 102) | func (d *MDNSDiscovery) watch() {
method browse (line 138) | func (d *MDNSDiscovery) browse() ([]*KVPair, error) {
method Close (line 186) | func (d *MDNSDiscovery) Close() {
function NewMDNSDiscovery (line 40) | func NewMDNSDiscovery(service string, timeout time.Duration, watchInterv...
FILE: client/mode.go
type FailMode (line 4) | type FailMode
constant Failover (line 8) | Failover FailMode = iota
constant Failfast (line 10) | Failfast
constant Failtry (line 12) | Failtry
constant Failbackup (line 14) | Failbackup
type SelectMode (line 18) | type SelectMode
constant RandomSelect (line 22) | RandomSelect SelectMode = iota
constant RoundRobin (line 24) | RoundRobin
constant WeightedRoundRobin (line 26) | WeightedRoundRobin
constant WeightedICMP (line 28) | WeightedICMP
constant ConsistentHash (line 30) | ConsistentHash
constant Closest (line 32) | Closest
constant SelectByUser (line 35) | SelectByUser = 1000
FILE: client/multiple_servers_discovery.go
type MultipleServersDiscovery (line 12) | type MultipleServersDiscovery struct
method Clone (line 28) | func (d *MultipleServersDiscovery) Clone(servicePath string) (ServiceD...
method SetFilter (line 33) | func (d *MultipleServersDiscovery) SetFilter(filter ServiceDiscoveryFi...
method GetServices (line 37) | func (d *MultipleServersDiscovery) GetServices() []*KVPair {
method WatchService (line 45) | func (d *MultipleServersDiscovery) WatchService() chan []*KVPair {
method RemoveWatcher (line 54) | func (d *MultipleServersDiscovery) RemoveWatcher(ch chan []*KVPair) {
method Update (line 71) | func (d *MultipleServersDiscovery) Update(pairs []*KVPair) {
method Close (line 94) | func (d *MultipleServersDiscovery) Close() {
function NewMultipleServersDiscovery (line 21) | func NewMultipleServersDiscovery(pairs []*KVPair) (*MultipleServersDisco...
FILE: client/oneclient.go
type OneClient (line 18) | type OneClient struct
method SetSelector (line 62) | func (c *OneClient) SetSelector(servicePath string, s Selector) {
method SetPlugins (line 72) | func (c *OneClient) SetPlugins(plugins PluginContainer) {
method GetPlugins (line 81) | func (c *OneClient) GetPlugins() PluginContainer {
method ConfigGeoSelector (line 87) | func (c *OneClient) ConfigGeoSelector(latitude, longitude float64) {
method Auth (line 100) | func (c *OneClient) Auth(auth string) {
method Go (line 111) | func (c *OneClient) Go(ctx context.Context, servicePath string, servic...
method newXClient (line 133) | func (c *OneClient) newXClient(servicePath string) (xclient XClient, e...
method Call (line 176) | func (c *OneClient) Call(ctx context.Context, servicePath string, serv...
method SendRaw (line 198) | func (c *OneClient) SendRaw(ctx context.Context, r *protocol.Message) ...
method Broadcast (line 226) | func (c *OneClient) Broadcast(ctx context.Context, servicePath string,...
method Fork (line 250) | func (c *OneClient) Fork(ctx context.Context, servicePath string, serv...
method SendFile (line 272) | func (c *OneClient) SendFile(ctx context.Context, fileName string, rat...
method DownloadFile (line 294) | func (c *OneClient) DownloadFile(ctx context.Context, requestFileName ...
method Stream (line 316) | func (c *OneClient) Stream(ctx context.Context, meta map[string]string...
method Close (line 339) | func (c *OneClient) Close() error {
function NewOneClient (line 37) | func NewOneClient(failMode FailMode, selectMode SelectMode, discovery Se...
function NewBidirectionalOneClient (line 49) | func NewBidirectionalOneClient(failMode FailMode, selectMode SelectMode,...
FILE: client/oneclient_pool.go
type OneClientPool (line 12) | type OneClientPool struct
method Auth (line 64) | func (p *OneClientPool) Auth(auth string) {
method SetPlugins (line 73) | func (p *OneClientPool) SetPlugins(plugins PluginContainer) {
method GetPlugins (line 82) | func (p *OneClientPool) GetPlugins() PluginContainer {
method Get (line 89) | func (p *OneClientPool) Get() *OneClient {
method Close (line 97) | func (p *OneClientPool) Close() {
function NewOneClientPool (line 27) | func NewOneClientPool(count int, failMode FailMode, selectMode SelectMod...
function NewBidirectionalOneClientPool (line 45) | func NewBidirectionalOneClientPool(count int, failMode FailMode, selectM...
FILE: client/oneclient_pool_test.go
function TestOneClientPool_SetPlugins (line 7) | func TestOneClientPool_SetPlugins(t *testing.T) {
function TestOneClientPool_GetPlugins (line 50) | func TestOneClientPool_GetPlugins(t *testing.T) {
function TestOneClientPool_SetPlugins_Concurrent (line 95) | func TestOneClientPool_SetPlugins_Concurrent(t *testing.T) {
FILE: client/peer2peer_discovery.go
type Peer2PeerDiscovery (line 5) | type Peer2PeerDiscovery struct
method Clone (line 16) | func (d *Peer2PeerDiscovery) Clone(servicePath string) (ServiceDiscove...
method SetFilter (line 21) | func (d *Peer2PeerDiscovery) SetFilter(filter ServiceDiscoveryFilter) {
method GetServices (line 26) | func (d *Peer2PeerDiscovery) GetServices() []*KVPair {
method WatchService (line 31) | func (d *Peer2PeerDiscovery) WatchService() chan []*KVPair {
method RemoveWatcher (line 35) | func (d *Peer2PeerDiscovery) RemoveWatcher(ch chan []*KVPair) {}
method Close (line 37) | func (d *Peer2PeerDiscovery) Close() {
function NewPeer2PeerDiscovery (line 11) | func NewPeer2PeerDiscovery(server, metadata string) (*Peer2PeerDiscovery...
FILE: client/ping_utils.go
type weightedICMPSelector (line 13) | type weightedICMPSelector struct
method Select (line 29) | func (s *weightedICMPSelector) Select(ctx context.Context, servicePath...
method UpdateServer (line 33) | func (s *weightedICMPSelector) UpdateServer(servers map[string]string) {
function newWeightedICMPSelector (line 18) | func newWeightedICMPSelector(servers map[string]string) Selector {
function createICMPWeighted (line 40) | func createICMPWeighted(servers map[string]string) []*Weighted {
function Ping (line 56) | func Ping(host string) (rtt int, err error) {
function CalculateWeight (line 88) | func CalculateWeight(rtt int) int {
FILE: client/plugin.go
type pluginContainer (line 11) | type pluginContainer struct
method Add (line 23) | func (p *pluginContainer) Add(plugin Plugin) {
method Remove (line 28) | func (p *pluginContainer) Remove(plugin Plugin) {
method All (line 44) | func (p *pluginContainer) All() []Plugin {
method DoPreCall (line 49) | func (p *pluginContainer) DoPreCall(ctx context.Context, servicePath, ...
method DoPostCall (line 63) | func (p *pluginContainer) DoPostCall(ctx context.Context, servicePath,...
method DoConnCreated (line 76) | func (p *pluginContainer) DoConnCreated(conn net.Conn) (net.Conn, erro...
method DoConnCreateFailed (line 90) | func (p *pluginContainer) DoConnCreateFailed(network, address string) {
method DoClientConnected (line 99) | func (p *pluginContainer) DoClientConnected(conn net.Conn) (net.Conn, ...
method DoClientConnectionClose (line 113) | func (p *pluginContainer) DoClientConnectionClose(conn net.Conn) error {
method DoClientBeforeEncode (line 127) | func (p *pluginContainer) DoClientBeforeEncode(req *protocol.Message) ...
method DoClientAfterDecode (line 141) | func (p *pluginContainer) DoClientAfterDecode(req *protocol.Message) e...
method DoWrapSelect (line 155) | func (p *pluginContainer) DoWrapSelect(fn SelectFunc) SelectFunc {
function NewPluginContainer (line 15) | func NewPluginContainer() PluginContainer {
type Plugin (line 20) | type Plugin interface
type PreCallPlugin (line 168) | type PreCallPlugin interface
type PostCallPlugin (line 173) | type PostCallPlugin interface
type ConnCreatedPlugin (line 178) | type ConnCreatedPlugin interface
type ConnCreateFailedPlugin (line 182) | type ConnCreateFailedPlugin interface
type ClientConnectedPlugin (line 187) | type ClientConnectedPlugin interface
type ClientConnectionClosePlugin (line 192) | type ClientConnectionClosePlugin interface
type ClientBeforeEncodePlugin (line 197) | type ClientBeforeEncodePlugin interface
type ClientAfterDecodePlugin (line 202) | type ClientAfterDecodePlugin interface
type SelectNodePlugin (line 207) | type SelectNodePlugin interface
type PluginContainer (line 213) | type PluginContainer interface
FILE: client/selectmode_enumer.go
constant _SelectModeName (line 9) | _SelectModeName = "RandomSelectRoundRobinWeightedRoundRobinWeightedICMPC...
method String (line 13) | func (i SelectMode) String() string {
function SelectModeString (line 33) | func SelectModeString(s string) (SelectMode, error) {
function SelectModeValues (line 41) | func SelectModeValues() []SelectMode {
method IsASelectMode (line 46) | func (i SelectMode) IsASelectMode() bool {
FILE: client/selector.go
type SelectFunc (line 18) | type SelectFunc
type Selector (line 21) | type Selector interface
function newSelector (line 26) | func newSelector(selectMode SelectMode, servers map[string]string) Selec...
type randomSelector (line 46) | type randomSelector struct
method Select (line 59) | func (s *randomSelector) Select(ctx context.Context, servicePath, serv...
method UpdateServer (line 68) | func (s *randomSelector) UpdateServer(servers map[string]string) {
function newRandomSelector (line 50) | func newRandomSelector(servers map[string]string) Selector {
type roundRobinSelector (line 78) | type roundRobinSelector struct
method Select (line 92) | func (s *roundRobinSelector) Select(ctx context.Context, servicePath, ...
method UpdateServer (line 104) | func (s *roundRobinSelector) UpdateServer(servers map[string]string) {
function newRoundRobinSelector (line 83) | func newRoundRobinSelector(servers map[string]string) Selector {
type weightedRoundRobinSelector (line 114) | type weightedRoundRobinSelector struct
method Select (line 127) | func (s *weightedRoundRobinSelector) Select(ctx context.Context, servi...
method UpdateServer (line 138) | func (s *weightedRoundRobinSelector) UpdateServer(servers map[string]s...
method buildRing (line 143) | func (s *weightedRoundRobinSelector) buildRing() {
method next (line 155) | func (s *weightedRoundRobinSelector) next() *Weighted {
function newWeightedRoundRobinSelector (line 120) | func newWeightedRoundRobinSelector(servers map[string]string) Selector {
function createWeighted (line 178) | func createWeighted(servers map[string]string) []*Weighted {
type geoServer (line 198) | type geoServer struct
type geoSelector (line 205) | type geoSelector struct
method Select (line 218) | func (s *geoSelector) Select(ctx context.Context, servicePath, service...
method UpdateServer (line 242) | func (s *geoSelector) UpdateServer(servers map[string]string) {
function newGeoSelector (line 212) | func newGeoSelector(servers map[string]string, latitude, longitude float...
function createGeoServer (line 247) | func createGeoServer(servers map[string]string) []*geoServer {
type consistentHashSelector (line 277) | type consistentHashSelector struct
method Select (line 294) | func (s *consistentHashSelector) Select(ctx context.Context, servicePa...
method UpdateServer (line 305) | func (s *consistentHashSelector) UpdateServer(servers map[string]strin...
function newConsistentHashSelector (line 282) | func newConsistentHashSelector(servers map[string]string) Selector {
FILE: client/selector_test.go
function Test_consistentHashSelector_Select (line 8) | func Test_consistentHashSelector_Select(t *testing.T) {
function Test_consistentHashSelector_UpdateServer (line 26) | func Test_consistentHashSelector_UpdateServer(t *testing.T) {
function TestWeightedRoundRobinSelector_Select (line 41) | func TestWeightedRoundRobinSelector_Select(t *testing.T) {
function TestWeightedRoundRobinSelector_UpdateServer (line 67) | func TestWeightedRoundRobinSelector_UpdateServer(t *testing.T) {
function BenchmarkWeightedRoundRobinSelector_Select (line 96) | func BenchmarkWeightedRoundRobinSelector_Select(b *testing.B) {
FILE: client/smooth_weighted_round_robin.go
type Weighted (line 4) | type Weighted struct
FILE: client/xclient.go
constant FileTransferBufferSize (line 28) | FileTransferBufferSize = 1024
type Receipt (line 41) | type Receipt struct
type XClient (line 49) | type XClient interface
type KVPair (line 79) | type KVPair struct
type xClient (line 84) | type xClient struct
method SetSelector (line 70) | func (c *xClient) SetSelector(s Selector) {
method SetPlugins (line 191) | func (c *xClient) SetPlugins(plugins PluginContainer) {
method GetPlugins (line 195) | func (c *xClient) GetPlugins() PluginContainer {
method ConfigGeoSelector (line 201) | func (c *xClient) ConfigGeoSelector(latitude, longitude float64) {
method Auth (line 207) | func (c *xClient) Auth(auth string) {
method watch (line 212) | func (c *xClient) watch(ch chan []*KVPair) {
method selectClient (line 257) | func (c *xClient) selectClient(ctx context.Context, servicePath, servi...
method getCachedClient (line 308) | func (c *xClient) getCachedClient(k string, servicePath, serviceMethod...
method setCachedClient (line 365) | func (c *xClient) setCachedClient(client RPCClient, k, servicePath, se...
method findCachedClient (line 375) | func (c *xClient) findCachedClient(k, servicePath, serviceMethod strin...
method deleteCachedClient (line 384) | func (c *xClient) deleteCachedClient(client RPCClient, k, servicePath,...
method removeClient (line 398) | func (c *xClient) removeClient(k, servicePath, serviceMethod string, c...
method generateClient (line 417) | func (c *xClient) generateClient(k, servicePath, serviceMethod string)...
method getCachedClientWithoutLock (line 443) | func (c *xClient) getCachedClientWithoutLock(k, servicePath, serviceMe...
method Go (line 505) | func (c *xClient) Go(ctx context.Context, serviceMethod string, args i...
method Call (line 542) | func (c *xClient) Call(ctx context.Context, serviceMethod string, args...
method Oneshot (line 707) | func (c *xClient) Oneshot(ctx context.Context, serviceMethod string, a...
method SendRaw (line 768) | func (c *xClient) SendRaw(ctx context.Context, r *protocol.Message) (m...
method wrapCall (line 877) | func (c *xClient) wrapCall(ctx context.Context, client RPCClient, serv...
method wrapSendRaw (line 905) | func (c *xClient) wrapSendRaw(ctx context.Context, client RPCClient, r...
method Broadcast (line 933) | func (c *xClient) Broadcast(ctx context.Context, serviceMethod string,...
method Fork (line 1032) | func (c *xClient) Fork(ctx context.Context, serviceMethod string, args...
method Inform (line 1135) | func (c *xClient) Inform(ctx context.Context, serviceMethod string, ar...
method SendFile (line 1252) | func (c *xClient) SendFile(ctx context.Context, fileName string, rateI...
method DownloadFile (line 1333) | func (c *xClient) DownloadFile(ctx context.Context, requestFileName st...
method Close (line 1389) | func (c *xClient) Close() error {
method Stream (line 1419) | func (c *xClient) Stream(ctx context.Context, meta map[string]string) ...
function NewXClient (line 115) | func NewXClient(servicePath string, failMode FailMode, selectMode Select...
function NewBidirectionalXClient (line 153) | func NewBidirectionalXClient(servicePath string, failMode FailMode, sele...
function filterByStateAndGroup (line 233) | func filterByStateAndGroup(group string, servers map[string]string) {
function safeCloseClient (line 296) | func safeCloseClient(client RPCClient) {
function splitNetworkAndAddress (line 480) | func splitNetworkAndAddress(server string) (string, string) {
function setServerTimeout (line 489) | func setServerTimeout(ctx context.Context) context.Context {
function uncoverError (line 740) | func uncoverError(err error) bool {
function contextCanceled (line 756) | func contextCanceled(err error) bool {
FILE: client/xclient_pool.go
type XClientPool (line 13) | type XClientPool struct
method Auth (line 70) | func (c *XClientPool) Auth(auth string) {
method SetPlugins (line 80) | func (p *XClientPool) SetPlugins(plugins PluginContainer) {
method GetPlugins (line 90) | func (p *XClientPool) GetPlugins() PluginContainer {
method Get (line 97) | func (p *XClientPool) Get() XClient {
method Close (line 105) | func (p *XClientPool) Close() {
function NewXClientPool (line 31) | func NewXClientPool(count int, servicePath string, failMode FailMode, se...
function NewBidirectionalXClientPool (line 50) | func NewBidirectionalXClientPool(count int, servicePath string, failMode...
FILE: client/xclient_pool_test.go
type testPlugin (line 8) | type testPlugin struct
function TestXClientPool_SetPlugins (line 12) | func TestXClientPool_SetPlugins(t *testing.T) {
function TestXClientPool_GetPlugins (line 55) | func TestXClientPool_GetPlugins(t *testing.T) {
function TestXClientPool_SetPlugins_Concurrent (line 100) | func TestXClientPool_SetPlugins_Concurrent(t *testing.T) {
FILE: client/xclient_test.go
function TestXClient_Thrift (line 17) | func TestXClient_Thrift(t *testing.T) {
function TestXClient_IT (line 61) | func TestXClient_IT(t *testing.T) {
function TestXClient_filterByStateAndGroup (line 95) | func TestXClient_filterByStateAndGroup(t *testing.T) {
function TestUncoverError (line 118) | func TestUncoverError(t *testing.T) {
FILE: clientplugin/req_rate_limiting_redis.go
type RedisRateLimitingPlugin (line 16) | type RedisRateLimitingPlugin struct
method PreCall (line 43) | func (plugin *RedisRateLimitingPlugin) PreCall(ctx context.Context, se...
function NewRedisRateLimitingPlugin (line 23) | func NewRedisRateLimitingPlugin(addrs []string, rate int, burst int, per...
FILE: codec/codec.go
type Codec (line 20) | type Codec interface
type ByteCodec (line 26) | type ByteCodec struct
method Encode (line 29) | func (c ByteCodec) Encode(i interface{}) ([]byte, error) {
method Decode (line 41) | func (c ByteCodec) Decode(data []byte, i interface{}) error {
type JSONCodec (line 47) | type JSONCodec struct
method Encode (line 50) | func (c JSONCodec) Encode(i interface{}) ([]byte, error) {
method Decode (line 55) | func (c JSONCodec) Decode(data []byte, i interface{}) error {
type PBCodec (line 62) | type PBCodec struct
method Encode (line 65) | func (c PBCodec) Encode(i interface{}) ([]byte, error) {
method Decode (line 78) | func (c PBCodec) Decode(data []byte, i interface{}) error {
type MsgpackCodec (line 91) | type MsgpackCodec struct
method Encode (line 94) | func (c MsgpackCodec) Encode(i interface{}) ([]byte, error) {
method Decode (line 106) | func (c MsgpackCodec) Decode(data []byte, i interface{}) error {
type ThriftCodec (line 117) | type ThriftCodec struct
method Encode (line 119) | func (c ThriftCodec) Encode(i interface{}) ([]byte, error) {
method Decode (line 134) | func (c ThriftCodec) Decode(data []byte, i interface{}) error {
FILE: codec/codec_test.go
type ColorGroup (line 9) | type ColorGroup struct
function BenchmarkJSONCodec_Encode (line 21) | func BenchmarkJSONCodec_Encode(b *testing.B) {
function BenchmarkPBCodec_Encode (line 32) | func BenchmarkPBCodec_Encode(b *testing.B) {
function BenchmarkMsgpackCodec_Encode (line 47) | func BenchmarkMsgpackCodec_Encode(b *testing.B) {
function BenchmarkThriftCodec_Encode (line 58) | func BenchmarkThriftCodec_Encode(b *testing.B) {
function BenchmarkJSONCodec_Decode (line 75) | func BenchmarkJSONCodec_Decode(b *testing.B) {
function BenchmarkPBCodec_Decode (line 86) | func BenchmarkPBCodec_Decode(b *testing.B) {
function BenchmarkMsgpackCodec_Decode (line 97) | func BenchmarkMsgpackCodec_Decode(b *testing.B) {
function BenchmarkThriftCodec_Decode (line 108) | func BenchmarkThriftCodec_Decode(b *testing.B) {
FILE: codec/testdata/protobuf.pb.go
constant _ (line 18) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
constant _ (line 20) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
type ProtoColorGroup (line 23) | type ProtoColorGroup struct
method Reset (line 33) | func (x *ProtoColorGroup) Reset() {
method String (line 42) | func (x *ProtoColorGroup) String() string {
method ProtoMessage (line 46) | func (*ProtoColorGroup) ProtoMessage() {}
method ProtoReflect (line 48) | func (x *ProtoColorGroup) ProtoReflect() protoreflect.Message {
method Descriptor (line 61) | func (*ProtoColorGroup) Descriptor() ([]byte, []int) {
method GetId (line 65) | func (x *ProtoColorGroup) GetId() int32 {
method GetName (line 72) | func (x *ProtoColorGroup) GetName() string {
method GetColors (line 79) | func (x *ProtoColorGroup) GetColors() []string {
function file_protobuf_proto_rawDescGZIP (line 104) | func file_protobuf_proto_rawDescGZIP() []byte {
function init (line 123) | func init() { file_protobuf_proto_init() }
function file_protobuf_proto_init (line 124) | func file_protobuf_proto_init() {
FILE: codec/testdata/thrift_colorgroup-consts.go
function init (line 21) | func init() {
FILE: codec/testdata/thrift_colorgroup.go
type ThriftColorGroup (line 24) | type ThriftColorGroup struct
method GetID (line 35) | func (p *ThriftColorGroup) GetID() int32 {
method GetName (line 39) | func (p *ThriftColorGroup) GetName() string {
method GetColors (line 43) | func (p *ThriftColorGroup) GetColors() []string {
method Read (line 46) | func (p *ThriftColorGroup) Read(ctx context.Context, iprot thrift.TPro...
method ReadField1 (line 104) | func (p *ThriftColorGroup) ReadField1(ctx context.Context, iprot thri...
method ReadField2 (line 113) | func (p *ThriftColorGroup) ReadField2(ctx context.Context, iprot thri...
method ReadField3 (line 122) | func (p *ThriftColorGroup) ReadField3(ctx context.Context, iprot thri...
method Write (line 144) | func (p *ThriftColorGroup) Write(ctx context.Context, oprot thrift.TPr...
method writeField1 (line 159) | func (p *ThriftColorGroup) writeField1(ctx context.Context, oprot thri...
method writeField2 (line 169) | func (p *ThriftColorGroup) writeField2(ctx context.Context, oprot thri...
method writeField3 (line 179) | func (p *ThriftColorGroup) writeField3(ctx context.Context, oprot thri...
method Equals (line 197) | func (p *ThriftColorGroup) Equals(other *ThriftColorGroup) bool {
method String (line 213) | func (p *ThriftColorGroup) String() string {
function NewThriftColorGroup (line 30) | func NewThriftColorGroup() *ThriftColorGroup {
FILE: errors/error.go
type MultiError (line 9) | type MultiError struct
method Error (line 15) | func (e *MultiError) Error() string {
method Append (line 19) | func (e *MultiError) Append(err error) {
method ErrorOrNil (line 25) | func (e *MultiError) ErrorOrNil() error {
function NewMultiError (line 33) | func NewMultiError(errors []error) *MultiError {
FILE: errors/error_test.go
function TestNewMultiError (line 11) | func TestNewMultiError(t *testing.T) {
function TestMultiError_Append (line 20) | func TestMultiError_Append(t *testing.T) {
function TestMultiError_Error (line 28) | func TestMultiError_Error(t *testing.T) {
function TestMultiError_ErrorOrNil (line 36) | func TestMultiError_ErrorOrNil(t *testing.T) {
FILE: log/default_logger.go
type Level (line 17) | type Level
constant LvPanic (line 21) | LvPanic Level = iota
constant LvFatal (line 22) | LvFatal
constant LvError (line 23) | LvError
constant LvWarn (line 24) | LvWarn
constant LvInfo (line 25) | LvInfo
constant LvDebug (line 26) | LvDebug
constant LvMax (line 28) | LvMax
type outputFn (line 31) | type outputFn
function dropOutput (line 33) | func dropOutput(calldepth int, s string) error {
type defaultLogger (line 37) | type defaultLogger struct
method Debug (line 70) | func (l *defaultLogger) Debug(v ...interface{}) {
method Debugf (line 74) | func (l *defaultLogger) Debugf(format string, v ...interface{}) {
method Info (line 78) | func (l *defaultLogger) Info(v ...interface{}) {
method Infof (line 82) | func (l *defaultLogger) Infof(format string, v ...interface{}) {
method Warn (line 86) | func (l *defaultLogger) Warn(v ...interface{}) {
method Warnf (line 90) | func (l *defaultLogger) Warnf(format string, v ...interface{}) {
method Error (line 94) | func (l *defaultLogger) Error(v ...interface{}) {
method Errorf (line 98) | func (l *defaultLogger) Errorf(format string, v ...interface{}) {
method Fatal (line 102) | func (l *defaultLogger) Fatal(v ...interface{}) {
method Fatalf (line 107) | func (l *defaultLogger) Fatalf(format string, v ...interface{}) {
method Panic (line 112) | func (l *defaultLogger) Panic(v ...interface{}) {
method Panicf (line 116) | func (l *defaultLogger) Panicf(format string, v ...interface{}) {
function NewDefaultLogger (line 42) | func NewDefaultLogger(out io.Writer, prefix string, flag int, lv Level) ...
function header (line 120) | func header(lvl, msg string) string {
FILE: log/dummy_logger.go
type dummyLogger (line 3) | type dummyLogger struct
method Debug (line 5) | func (l *dummyLogger) Debug(v ...interface{}) {
method Debugf (line 8) | func (l *dummyLogger) Debugf(format string, v ...interface{}) {
method Info (line 11) | func (l *dummyLogger) Info(v ...interface{}) {
method Infof (line 14) | func (l *dummyLogger) Infof(format string, v ...interface{}) {
method Warn (line 17) | func (l *dummyLogger) Warn(v ...interface{}) {
method Warnf (line 20) | func (l *dummyLogger) Warnf(format string, v ...interface{}) {
method Error (line 23) | func (l *dummyLogger) Error(v ...interface{}) {
method Errorf (line 26) | func (l *dummyLogger) Errorf(format string, v ...interface{}) {
method Fatal (line 29) | func (l *dummyLogger) Fatal(v ...interface{}) {
method Fatalf (line 32) | func (l *dummyLogger) Fatalf(format string, v ...interface{}) {
method Panic (line 35) | func (l *dummyLogger) Panic(v ...interface{}) {
method Panicf (line 38) | func (l *dummyLogger) Panicf(format string, v ...interface{}) {
FILE: log/logger.go
constant calldepth (line 9) | calldepth = 3
type Logger (line 14) | type Logger interface
function SetLogger (line 34) | func SetLogger(logger Logger) {
function GetLogger (line 38) | func GetLogger() Logger {
function SetDummyLogger (line 42) | func SetDummyLogger() {
function Debug (line 46) | func Debug(v ...interface{}) {
function Debugf (line 49) | func Debugf(format string, v ...interface{}) {
function Info (line 53) | func Info(v ...interface{}) {
function Infof (line 56) | func Infof(format string, v ...interface{}) {
function Warn (line 60) | func Warn(v ...interface{}) {
function Warnf (line 63) | func Warnf(format string, v ...interface{}) {
function Error (line 67) | func Error(v ...interface{}) {
function Errorf (line 70) | func Errorf(format string, v ...interface{}) {
function Fatal (line 74) | func Fatal(v ...interface{}) {
function Fatalf (line 77) | func Fatalf(format string, v ...interface{}) {
function Panic (line 81) | func Panic(v ...interface{}) {
function Panicf (line 84) | func Panicf(format string, v ...interface{}) {
FILE: protocol/compressor.go
type Compressor (line 12) | type Compressor interface
type GzipCompressor (line 18) | type GzipCompressor struct
method Zip (line 21) | func (c GzipCompressor) Zip(data []byte) ([]byte, error) {
method Unzip (line 25) | func (c GzipCompressor) Unzip(data []byte) ([]byte, error) {
type RawDataCompressor (line 29) | type RawDataCompressor struct
method Zip (line 32) | func (c RawDataCompressor) Zip(data []byte) ([]byte, error) {
method Unzip (line 36) | func (c RawDataCompressor) Unzip(data []byte) ([]byte, error) {
type SnappyCompressor (line 41) | type SnappyCompressor struct
method Zip (line 44) | func (c *SnappyCompressor) Zip(data []byte) ([]byte, error) {
method Unzip (line 64) | func (c *SnappyCompressor) Unzip(data []byte) ([]byte, error) {
FILE: protocol/compressor_test.go
function newBenchmarkMessage (line 11) | func newBenchmarkMessage() *testdata.BenchmarkMessage {
function BenchmarkGzipCompressor_Zip (line 38) | func BenchmarkGzipCompressor_Zip(b *testing.B) {
function BenchmarkRawDataCompressor_Zip (line 50) | func BenchmarkRawDataCompressor_Zip(b *testing.B) {
function BenchmarkSnappyCompressor_Zip (line 62) | func BenchmarkSnappyCompressor_Zip(b *testing.B) {
function BenchmarkGzipCompressor_Unzip (line 74) | func BenchmarkGzipCompressor_Unzip(b *testing.B) {
function BenchmarkRawDataCompressor_Unzip (line 87) | func BenchmarkRawDataCompressor_Unzip(b *testing.B) {
function BenchmarkSnappyCompressor_Unzip (line 100) | func BenchmarkSnappyCompressor_Unzip(b *testing.B) {
FILE: protocol/message.go
constant magicNumber (line 29) | magicNumber byte = 0x08
function MagicNumber (line 32) | func MagicNumber() byte {
constant ServiceError (line 47) | ServiceError = "__rpcx_error__"
type MessageType (line 51) | type MessageType
constant Request (line 55) | Request MessageType = iota
constant Response (line 57) | Response
type MessageStatusType (line 61) | type MessageStatusType
constant Normal (line 65) | Normal MessageStatusType = iota
constant Error (line 67) | Error
type CompressType (line 71) | type CompressType
constant None (line 75) | None CompressType = iota
constant Gzip (line 77) | Gzip
type SerializeType (line 81) | type SerializeType
constant SerializeNone (line 85) | SerializeNone SerializeType = iota
constant JSON (line 87) | JSON
constant ProtoBuffer (line 89) | ProtoBuffer
constant MsgPack (line 91) | MsgPack
constant Thrift (line 94) | Thrift
type Message (line 98) | type Message struct
method Clone (line 216) | func (m Message) Clone() *Message {
method Encode (line 227) | func (m Message) Encode() []byte {
method EncodeSlicePointer (line 233) | func (m Message) EncodeSlicePointer() *[]byte {
method WriteTo (line 291) | func (m Message) WriteTo(w io.Writer) (int64, error) {
method Decode (line 416) | func (m *Message) Decode(r io.Reader) error {
method Reset (line 513) | func (m *Message) Reset() {
function NewMessage (line 108) | func NewMessage() *Message {
type Header (line 119) | type Header
method CheckMagicNumber (line 122) | func (h Header) CheckMagicNumber() bool {
method Version (line 127) | func (h Header) Version() byte {
method SetVersion (line 132) | func (h *Header) SetVersion(v byte) {
method MessageType (line 137) | func (h Header) MessageType() MessageType {
method SetMessageType (line 142) | func (h *Header) SetMessageType(mt MessageType) {
method IsHeartbeat (line 147) | func (h Header) IsHeartbeat() bool {
method SetHeartbeat (line 152) | func (h *Header) SetHeartbeat(hb bool) {
method IsOneway (line 162) | func (h Header) IsOneway() bool {
method SetOneway (line 167) | func (h *Header) SetOneway(oneway bool) {
method CompressType (line 176) | func (h Header) CompressType() CompressType {
method SetCompressType (line 181) | func (h *Header) SetCompressType(ct CompressType) {
method MessageStatusType (line 186) | func (h Header) MessageStatusType() MessageStatusType {
method SetMessageStatusType (line 191) | func (h *Header) SetMessageStatusType(mt MessageStatusType) {
method SerializeType (line 196) | func (h Header) SerializeType() SerializeType {
method SetSerializeType (line 201) | func (h *Header) SetSerializeType(st SerializeType) {
method Seq (line 206) | func (h Header) Seq() uint64 {
method SetSeq (line 211) | func (h *Header) SetSeq(seq uint64) {
function PutData (line 286) | func PutData(data *[]byte) {
function encodeMetadata (line 362) | func encodeMetadata(m map[string]string, bb *bytes.Buffer) {
function decodeMetadata (line 377) | func decodeMetadata(l uint32, data []byte) (map[string]string, error) {
function Read (line 406) | func Read(r io.Reader) (*Message, error) {
function resetHeader (line 527) | func resetHeader(h *Header) {
FILE: protocol/message_chan_test.go
function TestChanValue (line 11) | func TestChanValue(t *testing.T) {
function TestChanPtr (line 35) | func TestChanPtr(t *testing.T) {
function BenchmarkChanValue (line 60) | func BenchmarkChanValue(b *testing.B) {
function BenchmarkChanPtr (line 78) | func BenchmarkChanPtr(b *testing.B) {
FILE: protocol/message_test.go
function TestMessage (line 8) | func TestMessage(t *testing.T) {
FILE: protocol/testdata/benchmark.pb.go
constant _ (line 18) | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
constant _ (line 20) | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
type BenchmarkMessage (line 23) | type BenchmarkMessage struct
method Reset (line 70) | func (x *BenchmarkMessage) Reset() {
method String (line 79) | func (x *BenchmarkMessage) String() string {
method ProtoMessage (line 83) | func (*BenchmarkMessage) ProtoMessage() {}
method ProtoReflect (line 85) | func (x *BenchmarkMessage) ProtoReflect() protoreflect.Message {
method Descriptor (line 98) | func (*BenchmarkMessage) Descriptor() ([]byte, []int) {
method GetField1 (line 102) | func (x *BenchmarkMessage) GetField1() string {
method GetField9 (line 109) | func (x *BenchmarkMessage) GetField9() string {
method GetField18 (line 116) | func (x *BenchmarkMessage) GetField18() string {
method GetField80 (line 123) | func (x *BenchmarkMessage) GetField80() bool {
method GetField81 (line 130) | func (x *BenchmarkMessage) GetField81() bool {
method GetField2 (line 137) | func (x *BenchmarkMessage) GetField2() int32 {
method GetField3 (line 144) | func (x *BenchmarkMessage) GetField3() int32 {
method GetField280 (line 151) | func (x *BenchmarkMessage) GetField280() int32 {
method GetField6 (line 158) | func (x *BenchmarkMessage) GetField6() int32 {
method GetField22 (line 165) | func (x *BenchmarkMessage) GetField22() int64 {
method GetField4 (line 172) | func (x *BenchmarkMessage) GetField4() string {
method GetField5 (line 179) | func (x *BenchmarkMessage) GetField5() []uint64 {
method GetField59 (line 186) | func (x *BenchmarkMessage) GetField59() bool {
method GetField7 (line 193) | func (x *BenchmarkMessage) GetField7() string {
method GetField16 (line 200) | func (x *BenchmarkMessage) GetField16() int32 {
method GetField130 (line 207) | func (x *BenchmarkMessage) GetField130() int32 {
method GetField12 (line 214) | func (x *BenchmarkMessage) GetField12() bool {
method GetField17 (line 221) | func (x *BenchmarkMessage) GetField17() bool {
method GetField13 (line 228) | func (x *BenchmarkMessage) GetField13() bool {
method GetField14 (line 235) | func (x *BenchmarkMessage) GetField14() bool {
method GetField104 (line 242) | func (x *BenchmarkMessage) GetField104() int32 {
method GetField100 (line 249) | func (x *BenchmarkMessage) GetField100() int32 {
method GetField101 (line 256) | func (x *BenchmarkMessage) GetField101() int32 {
method GetField102 (line 263) | func (x *BenchmarkMessage) GetField102() string {
method GetField103 (line 270) | func (x *BenchmarkMessage) GetField103() string {
method GetField29 (line 277) | func (x *BenchmarkMessage) GetField29() int32 {
method GetField30 (line 284) | func (x *BenchmarkMessage) GetField30() bool {
method GetField60 (line 291) | func (x *BenchmarkMessage) GetField60() int32 {
method GetField271 (line 298) | func (x *BenchmarkMessage) GetField271() int32 {
method GetField272 (line 305) | func (x *BenchmarkMessage) GetField272() int32 {
method GetField150 (line 312) | func (x *BenchmarkMessage) GetField150() int32 {
method GetField23 (line 319) | func (x *BenchmarkMessage) GetField23() int32 {
method GetField24 (line 326) | func (x *BenchmarkMessage) GetField24() bool {
method GetField25 (line 333) | func (x *BenchmarkMessage) GetField25() int32 {
method GetField78 (line 340) | func (x *BenchmarkMessage) GetField78() bool {
method GetField67 (line 347) | func (x *BenchmarkMessage) GetField67() int32 {
method GetField68 (line 354) | func (x *BenchmarkMessage) GetField68() int32 {
method GetField128 (line 361) | func (x *BenchmarkMessage) GetField128() int32 {
method GetField129 (line 368) | func (x *BenchmarkMessage) GetField129() string {
method GetField131 (line 375) | func (x *BenchmarkMessage) GetField131() int32 {
function file_benchmark_proto_rawDescGZIP (line 463) | func file_benchmark_proto_rawDescGZIP() []byte {
function init (line 482) | func init() { file_benchmark_proto_init() }
function file_benchmark_proto_init (line 483) | func file_benchmark_proto_init() {
FILE: reflection/server_reflection.go
type ServiceInfo (line 24) | type ServiceInfo struct
method String (line 52) | func (si ServiceInfo) String() string {
type MethodInfo (line 31) | type MethodInfo struct
type Reflection (line 59) | type Reflection struct
method Register (line 69) | func (r *Reflection) Register(name string, rcvr interface{}, metadata ...
method Unregister (line 146) | func (r *Reflection) Unregister(name string) error {
method GetService (line 151) | func (r *Reflection) GetService(ctx context.Context, s string, reply *...
method GetServices (line 161) | func (r *Reflection) GetServices(ctx context.Context, s string, reply ...
function New (line 63) | func New() *Reflection {
function generateTypeDefination (line 182) | func generateTypeDefination(name, pkg string, jsonValue string) string {
function generateJSON (line 207) | func generateJSON(typ reflect.Type) string {
function isExported (line 214) | func isExported(name string) bool {
function isExportedOrBuiltinType (line 219) | func isExportedOrBuiltinType(t reflect.Type) bool {
FILE: reflection/server_reflection_test.go
type PBArith (line 13) | type PBArith
method Mul (line 15) | func (t *PBArith) Mul(ctx context.Context, args *testutils.ProtoArgs, ...
function TestReflection_Register (line 20) | func TestReflection_Register(t *testing.T) {
type Args (line 31) | type Args struct
function Test_generateJSON (line 37) | func Test_generateJSON(t *testing.T) {
FILE: server/context.go
type Context (line 12) | type Context struct
method Get (line 26) | func (ctx *Context) Get(key interface{}) interface{} {
method SetValue (line 31) | func (ctx *Context) SetValue(key, val interface{}) {
method DeleteKey (line 39) | func (ctx *Context) DeleteKey(key interface{}) {
method Payload (line 47) | func (ctx *Context) Payload() []byte {
method Metadata (line 52) | func (ctx *Context) Metadata() map[string]string {
method ServicePath (line 57) | func (ctx *Context) ServicePath() string {
method ServiceMethod (line 62) | func (ctx *Context) ServiceMethod() string {
method Bind (line 67) | func (ctx *Context) Bind(v interface{}) error {
method Write (line 83) | func (ctx *Context) Write(v interface{}) error {
method WriteError (line 140) | func (ctx *Context) WriteError(err error) error {
function NewContext (line 21) | func NewContext(ctx *share.Context, conn net.Conn, req *protocol.Message...
FILE: server/converter.go
constant XVersion (line 14) | XVersion = "X-RPCX-Version"
constant XMessageType (line 15) | XMessageType = "X-RPCX-MessageType"
constant XHeartbeat (line 16) | XHeartbeat = "X-RPCX-Heartbeat"
constant XOneway (line 17) | XOneway = "X-RPCX-Oneway"
constant XMessageStatusType (line 18) | XMessageStatusType = "X-RPCX-MessageStatusType"
constant XSerializeType (line 19) | XSerializeType = "X-RPCX-SerializeType"
constant XCompressType (line 20) | XCompressType = "X-RPCX-CompressType"
constant XMessageID (line 21) | XMessageID = "X-RPCX-MessageID"
constant XServicePath (line 22) | XServicePath = "X-RPCX-ServicePath"
constant XServiceMethod (line 23) | XServiceMethod = "X-RPCX-ServiceMethod"
constant XMeta (line 24) | XMeta = "X-RPCX-Meta"
constant XErrorMessage (line 25) | XErrorMessage = "X-RPCX-ErrorMessage"
function HTTPRequest2RpcxRequest (line 29) | func HTTPRequest2RpcxRequest(r *http.Request) (*protocol.Message, error) {
FILE: server/converter_test.go
function TestHTTPRequest2RpcxRequest (line 13) | func TestHTTPRequest2RpcxRequest(t *testing.T) {
FILE: server/file_transfer.go
type FileTransferHandler (line 17) | type FileTransferHandler
type DownloadFileHandler (line 20) | type DownloadFileHandler
type tokenInfo (line 22) | type tokenInfo struct
type downloadTokenInfo (line 27) | type downloadTokenInfo struct
type FileTransfer (line 35) | type FileTransfer struct
method Start (line 123) | func (s *FileTransfer) Start() error {
method start (line 131) | func (s *FileTransfer) start() error {
method Stop (line 210) | func (s *FileTransfer) Stop() error {
type FileTransferService (line 49) | type FileTransferService struct
method TransferFile (line 80) | func (s *FileTransferService) TransferFile(ctx context.Context, args *...
method DownloadFile (line 101) | func (s *FileTransferService) DownloadFile(ctx context.Context, args *...
function NewFileTransfer (line 54) | func NewFileTransfer(addr string, handler FileTransferHandler, downloadF...
method EnableFileTransfer (line 72) | func (s *Server) EnableFileTransfer(serviceName string, fileTransfer *Fi...
FILE: server/gateway.go
method startGateway (line 23) | func (s *Server) startGateway(network string, ln net.Listener) net.Liste...
function http1Path (line 53) | func http1Path(prefix string) cmux.Matcher {
function parseRequestLine (line 82) | func parseRequestLine(line string) (method, uri, proto string, ok bool) {
function http1HeaderExist (line 92) | func http1HeaderExist(name string) cmux.Matcher {
function matchHTTP1Field (line 105) | func matchHTTP1Field(r io.Reader, name string, matches func(string) bool...
function rpcxPrefixByteMatcher (line 114) | func rpcxPrefixByteMatcher() cmux.Matcher {
method startHTTP1APIGateway (line 123) | func (s *Server) startHTTP1APIGateway(ln net.Listener) {
method closeHTTP1APIGateway (line 151) | func (s *Server) closeHTTP1APIGateway(ctx context.Context) error {
method handleGatewayRequest (line 161) | func (s *Server) handleGatewayRequest(w http.ResponseWriter, r *http.Req...
FILE: server/jsonrpc2.go
method jsonrpcHandler (line 20) | func (s *Server) jsonrpcHandler(w http.ResponseWriter, r *http.Request) {
method handleJSONRPCRequest (line 54) | func (s *Server) handleJSONRPCRequest(ctx context.Context, r *jsonrpcReq...
function writeResponse (line 140) | func writeResponse(w http.ResponseWriter, res *jsonrpcRespone) {
function AllowAllCORSOptions (line 154) | func AllowAllCORSOptions() *CORSOptions {
method SetCORS (line 178) | func (s *Server) SetCORS(options *CORSOptions) {
method startJSONRPC2 (line 182) | func (s *Server) startJSONRPC2(ln net.Listener) {
method closeJSONRPC2 (line 208) | func (s *Server) closeJSONRPC2(ctx context.Context) error {
FILE: server/jsonrpc2_wire.go
constant CodeUnknownJSONRPCError (line 18) | CodeUnknownJSONRPCError = -32001
constant CodeParseJSONRPCError (line 20) | CodeParseJSONRPCError = -32700
constant CodeInvalidjsonrpcRequest (line 22) | CodeInvalidjsonrpcRequest = -32600
constant CodeMethodNotFound (line 25) | CodeMethodNotFound = -32601
constant CodeInvalidParams (line 28) | CodeInvalidParams = -32602
constant CodeInternalJSONRPCError (line 30) | CodeInternalJSONRPCError = -32603
type jsonrpcRequest (line 34) | type jsonrpcRequest struct
method IsNotify (line 87) | func (r *jsonrpcRequest) IsNotify() bool {
type jsonrpcRespone (line 51) | type jsonrpcRespone struct
type JSONRPCError (line 63) | type JSONRPCError struct
method JSONRPCError (line 91) | func (err *JSONRPCError) JSONRPCError() string {
type VersionTag (line 76) | type VersionTag struct
method MarshalJSON (line 98) | func (VersionTag) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 102) | func (VersionTag) UnmarshalJSON(data []byte) error {
type ID (line 81) | type ID struct
method String (line 116) | func (id *ID) String() string {
method MarshalJSON (line 126) | func (id *ID) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 133) | func (id *ID) UnmarshalJSON(data []byte) error {
FILE: server/kcp.go
function init (line 12) | func init() {
function kcpMakeListener (line 16) | func kcpMakeListener(s *Server, address string) (ln net.Listener, err er...
function WithBlockCrypt (line 25) | func WithBlockCrypt(bc kcp.BlockCrypt) OptionFn {
FILE: server/listener.go
function init (line 12) | func init() {
function RegisterMakeListener (line 22) | func RegisterMakeListener(network string, ml MakeListener) {
type MakeListener (line 27) | type MakeListener
method makeListener (line 31) | func (s *Server) makeListener(network, address string) (ln net.Listener,...
function tcpMakeListener (line 44) | func tcpMakeListener(network string) MakeListener {
FILE: server/listener_linux.go
function init (line 17) | func init() {
function iouringMakeListener (line 24) | func iouringMakeListener(s *Server, address string) (ln net.Listener, er...
type uringnetListener (line 48) | type uringnetListener struct
method Close (line 53) | func (cl *uringnetListener) Close() error {
type uringLogger (line 60) | type uringLogger struct
method Log (line 64) | func (l *uringLogger) Log(keyvals ...interface{}) {
FILE: server/listener_rdma.go
function init (line 14) | func init() {
function rdmaMakeListener (line 18) | func rdmaMakeListener(s *Server, address string) (ln net.Listener, err e...
FILE: server/listener_unix.go
function init (line 12) | func init() {
function reuseportMakeListener (line 17) | func reuseportMakeListener(s *Server, address string) (ln net.Listener, ...
function unixMakeListener (line 28) | func unixMakeListener(s *Server, address string) (ln net.Listener, err e...
FILE: server/memconn.go
function init (line 9) | func init() {
function memconnMakeListener (line 13) | func memconnMakeListener(s *Server, address string) (ln net.Listener, er...
FILE: server/option.go
type OptionFn (line 11) | type OptionFn
function WithTLSConfig (line 23) | func WithTLSConfig(cfg *tls.Config) OptionFn {
function WithReadTimeout (line 30) | func WithReadTimeout(readTimeout time.Duration) OptionFn {
function WithWriteTimeout (line 37) | func WithWriteTimeout(writeTimeout time.Duration) OptionFn {
function WithPool (line 44) | func WithPool(maxWorkers, maxCapacity int, options ...pond.Option) Optio...
function WithCustomPool (line 51) | func WithCustomPool(pool WorkerPool) OptionFn {
function WithAsyncWrite (line 58) | func WithAsyncWrite() OptionFn {
FILE: server/option_test.go
function TestOption (line 11) | func TestOption(t *testing.T) {
FILE: server/options.go
function WithTCPKeepAlivePeriod (line 6) | func WithTCPKeepAlivePeriod(period time.Duration) OptionFn {
FILE: server/plugin.go
type PluginContainer (line 16) | type PluginContainer interface
type Plugin (line 48) | type Plugin interface
type RegisterPlugin (line 52) | type RegisterPlugin interface
type RegisterFunctionPlugin (line 58) | type RegisterFunctionPlugin interface
type PostConnAcceptPlugin (line 65) | type PostConnAcceptPlugin interface
type PostConnClosePlugin (line 70) | type PostConnClosePlugin interface
type PreReadRequestPlugin (line 75) | type PreReadRequestPlugin interface
type PostReadRequestPlugin (line 80) | type PostReadRequestPlugin interface
type PostHTTPRequestPlugin (line 85) | type PostHTTPRequestPlugin interface
type PreHandleRequestPlugin (line 90) | type PreHandleRequestPlugin interface
type PreCallPlugin (line 94) | type PreCallPlugin interface
type PostCallPlugin (line 98) | type PostCallPlugin interface
type PreWriteResponsePlugin (line 103) | type PreWriteResponsePlugin interface
type PostWriteResponsePlugin (line 108) | type PostWriteResponsePlugin interface
type PreWriteRequestPlugin (line 113) | type PreWriteRequestPlugin interface
type PostWriteRequestPlugin (line 118) | type PostWriteRequestPlugin interface
type HeartbeatPlugin (line 123) | type HeartbeatPlugin interface
type CMuxPlugin (line 127) | type CMuxPlugin interface
type pluginContainer (line 133) | type pluginContainer struct
method Add (line 138) | func (p *pluginContainer) Add(plugin Plugin) {
method Remove (line 143) | func (p *pluginContainer) Remove(plugin Plugin) {
method All (line 158) | func (p *pluginContainer) All() []Plugin {
method DoRegister (line 163) | func (p *pluginContainer) DoRegister(name string, rcvr interface{}, me...
method DoRegisterFunction (line 181) | func (p *pluginContainer) DoRegisterFunction(serviceName, fname string...
method DoUnregister (line 199) | func (p *pluginContainer) DoUnregister(name string) error {
method DoPostConnAccept (line 217) | func (p *pluginContainer) DoPostConnAccept(conn net.Conn) (net.Conn, b...
method DoPostConnClose (line 232) | func (p *pluginContainer) DoPostConnClose(conn net.Conn) bool {
method DoPreReadRequest (line 246) | func (p *pluginContainer) DoPreReadRequest(ctx context.Context) error {
method DoPostReadRequest (line 260) | func (p *pluginContainer) DoPostReadRequest(ctx context.Context, r *pr...
method DoPostHTTPRequest (line 274) | func (p *pluginContainer) DoPostHTTPRequest(ctx context.Context, r *ht...
method DoPreHandleRequest (line 288) | func (p *pluginContainer) DoPreHandleRequest(ctx context.Context, r *p...
method DoPreCall (line 302) | func (p *pluginContainer) DoPreCall(ctx context.Context, serviceName, ...
method DoPostCall (line 317) | func (p *pluginContainer) DoPostCall(ctx context.Context, serviceName,...
method DoPreWriteResponse (line 332) | func (p *pluginContainer) DoPreWriteResponse(ctx context.Context, req ...
method DoPostWriteResponse (line 346) | func (p *pluginContainer) DoPostWriteResponse(ctx context.Context, req...
method DoPreWriteRequest (line 360) | func (p *pluginContainer) DoPreWriteRequest(ctx context.Context) error {
method DoPostWriteRequest (line 374) | func (p *pluginContainer) DoPostWriteRequest(ctx context.Context, r *p...
method DoHeartbeatRequest (line 388) | func (p *pluginContainer) DoHeartbeatRequest(ctx context.Context, r *p...
method MuxMatch (line 402) | func (p *pluginContainer) MuxMatch(m cmux.CMux) {
FILE: server/plugin_test.go
type HeartbeatHandler (line 14) | type HeartbeatHandler struct
method HeartbeatRequest (line 16) | func (h *HeartbeatHandler) HeartbeatRequest(ctx context.Context, req *...
function TestPluginHeartbeat (line 23) | func TestPluginHeartbeat(t *testing.T) {
FILE: server/pool.go
type Reset (line 9) | type Reset interface
type typePools (line 28) | type typePools struct
method Init (line 34) | func (p *typePools) Init(t reflect.Type) {
method Put (line 44) | func (p *typePools) Put(t reflect.Type, x interface{}) {
method Get (line 55) | func (p *typePools) Get(t reflect.Type) interface{} {
FILE: server/pool_test.go
type Elem (line 10) | type Elem struct
method Reset (line 15) | func (e Elem) Reset() {
function TestPool (line 19) | func TestPool(t *testing.T) {
FILE: server/quic.go
function init (line 13) | func init() {
function quicMakeListener (line 17) | func quicMakeListener(s *Server, address string) (ln net.Listener, err e...
FILE: server/router.go
method UpdateHandler (line 5) | func (s *Server) UpdateHandler(router map[string]Handler) {
FILE: server/server.go
constant ReaderBuffsize (line 38) | ReaderBuffsize = 1024
constant WriterBuffsize (line 40) | WriterBuffsize = 1024
type contextKey (line 48) | type contextKey struct
method String (line 52) | func (k *contextKey) String() string { return "rpcx context value " + ...
type Handler (line 69) | type Handler
type WorkerPool (line 71) | type WorkerPool interface
type Server (line 79) | type Server struct
method Address (line 158) | func (s *Server) Address() net.Addr {
method AddHandler (line 167) | func (s *Server) AddHandler(servicePath, serviceMethod string, handler...
method ActiveClientConn (line 172) | func (s *Server) ActiveClientConn() []net.Conn {
method SendMessage (line 189) | func (s *Server) SendMessage(conn net.Conn, servicePath, serviceMethod...
method getDoneChan (line 214) | func (s *Server) getDoneChan() <-chan struct{} {
method Serve (line 220) | func (s *Server) Serve(network, address string) (err error) {
method ServeListener (line 247) | func (s *Server) ServeListener(network string, ln net.Listener) (err e...
method serveListener (line 264) | func (s *Server) serveListener(ln net.Listener) error {
method serveByHTTP (line 332) | func (s *Server) serveByHTTP(ln net.Listener, rpcPath string) {
method serveByWS (line 345) | func (s *Server) serveByWS(ln net.Listener, rpcPath string) {
method sendResponse (line 358) | func (s *Server) sendResponse(ctx *share.Context, conn net.Conn, err e...
method serveConn (line 394) | func (s *Server) serveConn(conn net.Conn) {
method processOneRequest (line 525) | func (s *Server) processOneRequest(ctx *share.Context, req *protocol.M...
method isShutdown (line 644) | func (s *Server) isShutdown() bool {
method closeConn (line 648) | func (s *Server) closeConn(conn net.Conn) {
method readRequest (line 658) | func (s *Server) readRequest(ctx context.Context, r io.Reader) (req *p...
method auth (line 676) | func (s *Server) auth(ctx context.Context, req *protocol.Message) error {
method handleRequest (line 685) | func (s *Server) handleRequest(ctx context.Context, req *protocol.Mess...
method handleRequestForFunction (line 783) | func (s *Server) handleRequestForFunction(ctx context.Context, req *pr...
method handleError (line 855) | func (s *Server) handleError(res *protocol.Message, err error) (*proto...
method ServeHTTP (line 874) | func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
method ServeWS (line 895) | func (s *Server) ServeWS(conn *websocket.Conn) {
method Close (line 905) | func (s *Server) Close() error {
method RegisterOnShutdown (line 929) | func (s *Server) RegisterOnShutdown(f func(s *Server)) {
method RegisterOnRestart (line 936) | func (s *Server) RegisterOnRestart(f func(s *Server)) {
method Shutdown (line 951) | func (s *Server) Shutdown(ctx context.Context) error {
method Restart (line 1024) | func (s *Server) Restart(ctx context.Context) error {
method startProcess (line 1036) | func (s *Server) startProcess() (int, error) {
method checkProcessMsg (line 1059) | func (s *Server) checkProcessMsg() bool {
method closeDoneChanLocked (line 1065) | func (s *Server) closeDoneChanLocked() {
function NewServer (line 134) | func NewServer(options ...OptionFn) *Server {
function parseServerTimeout (line 624) | func parseServerTimeout(ctx *share.Context, req *protocol.Message) conte...
function validIP4 (line 1078) | func validIP4(ipAddress string) bool {
function validIP6 (line 1086) | func validIP6(ipAddress string) bool {
FILE: server/server_test.go
type Args (line 18) | type Args struct
type Reply (line 23) | type Reply struct
type Arith (line 27) | type Arith
method Mul (line 29) | func (t *Arith) Mul(ctx context.Context, args *Args, reply *Reply) err...
method ThriftMul (line 34) | func (t *Arith) ThriftMul(ctx context.Context, args *testutils.ThriftA...
method ConsumingOperation (line 39) | func (t *Arith) ConsumingOperation(ctx context.Context, args *testutil...
function TestShutdownHook (line 45) | func TestShutdownHook(t *testing.T) {
function TestHandleRequest (line 66) | func TestHandleRequest(t *testing.T) {
function TestHandler (line 122) | func TestHandler(t *testing.T) {
function Test_validIP6 (line 182) | func Test_validIP6(t *testing.T) {
FILE: server/service.go
type RpcServiceInternalError (line 19) | type RpcServiceInternalError struct
method Error (line 27) | func (e RpcServiceInternalError) Error() string {
method String (line 32) | func (e RpcServiceInternalError) String() string {
type methodType (line 43) | type methodType struct
type functionType (line 51) | type functionType struct
type service (line 58) | type service struct
method call (line 371) | func (s *service) call(ctx context.Context, mtype *methodType, argv, r...
method callForFunction (line 400) | func (s *service) callForFunction(ctx context.Context, ft *functionTyp...
function isExported (line 66) | func isExported(name string) bool {
function isExportedOrBuiltinType (line 71) | func isExportedOrBuiltinType(t reflect.Type) bool {
method ListServices (line 80) | func (s *Server) ListServices() []string {
method Register (line 101) | func (s *Server) Register(rcvr interface{}, metadata string) error {
method RegisterName (line 111) | func (s *Server) RegisterName(name string, rcvr interface{}, metadata st...
method RegisterFunction (line 128) | func (s *Server) RegisterFunction(servicePath string, fn interface{}, me...
method RegisterFunctionName (line 139) | func (s *Server) RegisterFunctionName(servicePath string, name string, f...
method register (line 148) | func (s *Server) register(rcvr interface{}, name string, useName bool) (...
method registerFunction (line 191) | func (s *Server) registerFunction(servicePath string, fn interface{}, na...
function suitableMethods (line 271) | func suitableMethods(typ reflect.Type, reportErr bool) map[string]*metho...
method UnregisterAll (line 345) | func (s *Server) UnregisterAll() error {
method unregisterAll (line 354) | func (s *Server) unregisterAll() error {
FILE: server/service_test.go
function Test_isExported (line 11) | func Test_isExported(t *testing.T) {
function Mul (line 27) | func Mul(ctx context.Context, args *Args, reply *Reply) error {
function Test_isExportedOrBuiltinType (line 32) | func Test_isExportedOrBuiltinType(t *testing.T) {
FILE: server/stream.go
type StreamHandler (line 20) | type StreamHandler
type StreamAcceptor (line 24) | type StreamAcceptor
type streamTokenInfo (line 26) | type streamTokenInfo struct
type StreamService (line 34) | type StreamService struct
method Stream (line 70) | func (s *StreamService) Stream(ctx context.Context, args *share.Stream...
method Start (line 95) | func (s *StreamService) Start() error {
method start (line 103) | func (s *StreamService) start() error {
method Stop (line 176) | func (s *StreamService) Stop() error {
function NewStreamService (line 48) | func NewStreamService(addr string, streamHandler StreamHandler, acceptor...
method EnableStreamService (line 62) | func (s *Server) EnableStreamService(serviceName string, streamService *...
FILE: serverplugin/alias.go
type aliasPair (line 11) | type aliasPair struct
type AliasPlugin (line 16) | type AliasPlugin struct
method Alias (line 23) | func (p *AliasPlugin) Alias(aliasServicePath, aliasServiceMethod strin...
method PostReadRequest (line 43) | func (p *AliasPlugin) PostReadRequest(ctx context.Context, r *protocol...
method PreWriteResponse (line 62) | func (p *AliasPlugin) PreWriteResponse(ctx context.Context, r *protoco...
function NewAliasPlugin (line 35) | func NewAliasPlugin() *AliasPlugin {
FILE: serverplugin/blacklist.go
type BlacklistPlugin (line 6) | type BlacklistPlugin struct
method HandleConnAccept (line 12) | func (plugin *BlacklistPlugin) HandleConnAccept(conn net.Conn) (net.Co...
FILE: serverplugin/mdns.go
type serviceMeta (line 19) | type serviceMeta struct
type MDNSRegisterPlugin (line 26) | type MDNSRegisterPlugin struct
method Start (line 60) | func (p *MDNSRegisterPlugin) Start() error {
method Stop (line 110) | func (p *MDNSRegisterPlugin) Stop() error {
method initMDNS (line 118) | func (p *MDNSRegisterPlugin) initMDNS() {
method HandleConnAccept (line 145) | func (p *MDNSRegisterPlugin) HandleConnAccept(conn net.Conn) (net.Conn...
method PreCall (line 153) | func (p *MDNSRegisterPlugin) PreCall(_ context.Context, _, _ string, a...
method Register (line 162) | func (p *MDNSRegisterPlugin) Register(name string, rcvr interface{}, m...
method RegisterFunction (line 187) | func (p *MDNSRegisterPlugin) RegisterFunction(serviceName, fname strin...
method Unregister (line 191) | func (p *MDNSRegisterPlugin) Unregister(name string) (err error) {
function NewMDNSRegisterPlugin (line 44) | func NewMDNSRegisterPlugin(serviceAddress string, port int, m metrics.Re...
FILE: serverplugin/metrics.go
type MetricsPlugin (line 19) | type MetricsPlugin struct
method withPrefix (line 29) | func (p *MetricsPlugin) withPrefix(m string) string {
method Register (line 34) | func (p *MetricsPlugin) Register(name string, rcvr interface{}, metada...
method HandleConnAccept (line 41) | func (p *MetricsPlugin) HandleConnAccept(conn net.Conn) (net.Conn, boo...
method PreReadRequest (line 48) | func (p *MetricsPlugin) PreReadRequest(ctx context.Context) error {
method PostReadRequest (line 53) | func (p *MetricsPlugin) PostReadRequest(ctx context.Context, r *protoc...
method PostWriteResponse (line 66) | func (p *MetricsPlugin) PostWriteResponse(ctx context.Context, req *pr...
method Log (line 94) | func (p *MetricsPlugin) Log(freq time.Duration, l metrics.Logger) {
method Graphite (line 102) | func (p *MetricsPlugin) Graphite(freq time.Duration, prefix string, ad...
method Exp (line 125) | func (p *MetricsPlugin) Exp() {
function NewMetricsPlugin (line 25) | func NewMetricsPlugin(registry metrics.Registry) *MetricsPlugin {
FILE: serverplugin/rate_limiting.go
type RateLimitingPlugin (line 11) | type RateLimitingPlugin struct
method HandleConnAccept (line 28) | func (plugin *RateLimitingPlugin) HandleConnAccept(conn net.Conn) (net...
function NewRateLimitingPlugin (line 18) | func NewRateLimitingPlugin(fillInterval time.Duration, capacity int64) *...
FILE: serverplugin/redis.go
function init (line 20) | func init() {
type RedisRegisterPlugin (line 25) | type RedisRegisterPlugin struct
method Start (line 47) | func (p *RedisRegisterPlugin) Start() error {
method Stop (line 124) | func (p *RedisRegisterPlugin) Stop() error {
method HandleConnAccept (line 154) | func (p *RedisRegisterPlugin) HandleConnAccept(conn net.Conn) (net.Con...
method PreCall (line 162) | func (p *RedisRegisterPlugin) PreCall(_ context.Context, _, _ string, ...
method Register (line 171) | func (p *RedisRegisterPlugin) Register(name string, rcvr interface{}, ...
method Unregister (line 218) | func (p *RedisRegisterPlugin) Unregister(name string) (err error) {
FILE: serverplugin/registry_test.go
type Args (line 5) | type Args struct
type Reply (line 10) | type Reply struct
type Arith (line 14) | type Arith
method Mul (line 16) | func (t *Arith) Mul(ctx context.Context, args *Args, reply *Reply) err...
FILE: serverplugin/req_rate_limiting.go
type ReqRateLimitingPlugin (line 13) | type ReqRateLimitingPlugin struct
method PostReadRequest (line 33) | func (plugin *ReqRateLimitingPlugin) PostReadRequest(ctx context.Conte...
function NewReqRateLimitingPlugin (line 21) | func NewReqRateLimitingPlugin(fillInterval time.Duration, capacity int64...
FILE: serverplugin/req_rate_limiting_redis.go
type RedisRateLimitingPlugin (line 16) | type RedisRateLimitingPlugin struct
method PostReadRequest (line 43) | func (plugin *RedisRateLimitingPlugin) PostReadRequest(ctx context.Con...
function NewRedisRateLimitingPlugin (line 23) | func NewRedisRateLimitingPlugin(addrs []string, rate int, burst int, per...
FILE: serverplugin/tee.go
type TeeConnPlugin (line 9) | type TeeConnPlugin struct
method Update (line 19) | func (plugin *TeeConnPlugin) Update(w io.Writer) {
method HandleConnAccept (line 24) | func (plugin *TeeConnPlugin) HandleConnAccept(conn net.Conn) (net.Conn...
function NewTeeConnPlugin (line 13) | func NewTeeConnPlugin(w io.Writer) *TeeConnPlugin {
type teeConn (line 29) | type teeConn struct
method Read (line 34) | func (t *teeConn) Read(p []byte) (n int, err error) {
FILE: serverplugin/whitelist.go
type WhitelistPlugin (line 6) | type WhitelistPlugin struct
method HandleConnAccept (line 12) | func (plugin *WhitelistPlugin) HandleConnAccept(conn net.Conn) (net.Co...
FILE: share/context.go
type Context (line 13) | type Context struct
method Lock (line 29) | func (c *Context) Lock() {
method Unlock (line 33) | func (c *Context) Unlock() {
method Value (line 37) | func (c *Context) Value(key interface{}) interface{} {
method SetValue (line 50) | func (c *Context) SetValue(key, val interface{}) {
method DeleteKey (line 61) | func (c *Context) DeleteKey(key interface{}) {
method String (line 71) | func (c *Context) String() string {
function NewContext (line 19) | func NewContext(ctx context.Context) *Context {
function WithValue (line 75) | func WithValue(parent context.Context, key, val interface{}) *Context {
function WithLocalValue (line 88) | func WithLocalValue(ctx *Context, key, val interface{}) *Context {
function IsShareContext (line 105) | func IsShareContext(ctx context.Context) bool {
FILE: share/context_test.go
function TestContext (line 15) | func TestContext(t *testing.T) {
function TestWithValue (line 29) | func TestWithValue(t *testing.T) {
function TestWithLocalValue (line 34) | func TestWithLocalValue(t *testing.T) {
FILE: share/share.go
constant DefaultRPCPath (line 10) | DefaultRPCPath = "/_rpcx_"
constant AuthKey (line 13) | AuthKey = "__AUTH"
constant ServerAddress (line 16) | ServerAddress = "__ServerAddress"
constant ServerTimeout (line 19) | ServerTimeout = "__ServerTimeout"
constant SendFileServiceName (line 22) | SendFileServiceName = "_filetransfer"
constant StreamServiceName (line 25) | StreamServiceName = "_streamservice"
constant ContextTagsLock (line 28) | ContextTagsLock = "_tagsLock"
constant isShareContext (line 30) | isShareContext = "_isShareContext"
function RegisterCodec (line 48) | func RegisterCodec(t protocol.SerializeType, c codec.Codec) {
type ContextKey (line 53) | type ContextKey
type FileTransferArgs (line 62) | type FileTransferArgs struct
method Clone (line 69) | func (args FileTransferArgs) Clone() *FileTransferArgs {
type FileTransferReply (line 83) | type FileTransferReply struct
type DownloadFileArgs (line 89) | type DownloadFileArgs struct
method Clone (line 95) | func (args DownloadFileArgs) Clone() *DownloadFileArgs {
type StreamServiceArgs (line 108) | type StreamServiceArgs struct
type StreamServiceReply (line 113) | type StreamServiceReply struct
FILE: share/share_test.go
type MockCodec (line 10) | type MockCodec struct
method Encode (line 12) | func (codec MockCodec) Encode(i interface{}) ([]byte, error) {
method Decode (line 16) | func (codec MockCodec) Decode(data []byte, i interface{}) error {
function TestShare (line 20) | func TestShare(t *testing.T) {
FILE: tool/xgen/parser/parser.go
type Parser (line 14) | type Parser struct
method Parse (line 80) | func (p *Parser) Parse(fname string, isDir bool) error {
type visitor (line 21) | type visitor struct
method Visit (line 33) | func (v *visitor) Visit(n ast.Node) (w ast.Visitor) {
function isExported (line 28) | func isExported(name string) bool {
FILE: tool/xgen/xgen.go
function main (line 22) | func main() {
function generate (line 77) | func generate(parsers []*parser.Parser) error {
FILE: util/buffer_pool.go
type levelPool (line 8) | type levelPool struct
function newLevelPool (line 13) | func newLevelPool(size int) *levelPool {
type LimitedPool (line 25) | type LimitedPool struct
method findPool (line 50) | func (p *LimitedPool) findPool(size int) *levelPool {
method findPutPool (line 64) | func (p *LimitedPool) findPutPool(size int) *levelPool {
method Get (line 82) | func (p *LimitedPool) Get(size int) *[]byte {
method Put (line 93) | func (p *LimitedPool) Put(b *[]byte) {
function NewLimitedPool (line 31) | func NewLimitedPool(minSize, maxSize int) *LimitedPool {
FILE: util/buffer_pool_test.go
function TestLimitedPool_findPool (line 8) | func TestLimitedPool_findPool(t *testing.T) {
function TestLimitedPool_findPutPool (line 41) | func TestLimitedPool_findPutPool(t *testing.T) {
FILE: util/compress.go
function init (line 16) | func init() {
function Unzip (line 29) | func Unzip(data []byte) ([]byte, error) {
function Zip (line 50) | func Zip(data []byte) ([]byte, error) {
FILE: util/compress_test.go
function TestZip (line 10) | func TestZip(t *testing.T) {
function BenchmarkZip (line 28) | func BenchmarkZip(b *testing.B) {
function BenchmarkUnzip (line 41) | func BenchmarkUnzip(b *testing.B) {
function oldUnzip (line 59) | func oldUnzip(data []byte) ([]byte, error) {
function BenchmarkUnzip_Old (line 88) | func BenchmarkUnzip_Old(b *testing.B) {
FILE: util/converter.go
function SliceByteToString (line 7) | func SliceByteToString(b []byte) string {
function StringToSliceByte (line 11) | func StringToSliceByte(s string) []byte {
function CopyMeta (line 17) | func CopyMeta(src, dst map[string]string) {
FILE: util/net.go
function GetFreePort (line 14) | func GetFreePort() (port int, err error) {
function ParseRpcxAddress (line 25) | func ParseRpcxAddress(addr string) (network string, ip string, port int,...
function ConvertMeta2Map (line 44) | func ConvertMeta2Map(meta string) map[string]string {
function ConvertMap2String (line 62) | func ConvertMap2String(meta map[string]string) string {
function ExternalIPV4 (line 83) | func ExternalIPV4() (string, error) {
function ExternalIPV6 (line 121) | func ExternalIPV6() (string, error) {
FILE: util/net_test.go
function TestGetFreePort (line 9) | func TestGetFreePort(t *testing.T) {
function BenchmarkGetFreePort_Old (line 38) | func BenchmarkGetFreePort_Old(b *testing.B) {
function BenchmarkGetFreePort_New (line 44) | func BenchmarkGetFreePort_New(b *testing.B) {
function TestExternalIPV4 (line 50) | func TestExternalIPV4(t *testing.T) {
function TestExternalIPV6 (line 62) | func TestExternalIPV6(t *testing.T) {
Condensed preview — 128 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (483K chars).
[
{
"path": ".github/workflows/go.yml",
"chars": 512,
"preview": "name: Go\n\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n\njobs:\n\n build:\n name: Build"
},
{
"path": ".gitignore",
"chars": 128,
"preview": "# \nvendor/\n\n# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736\n.glide/\n.idea\n\ncover.html\n\n"
},
{
"path": ".travis.yml",
"chars": 357,
"preview": "language: go\n\ngo:\n - 1.17.x\n\nenv:\n - GO111MODULE=on\n\nbefore_script:\n - rm -f go.sum\n - go get -tags \"quic kcp\" -v gi"
},
{
"path": "CHANGELOG.md",
"chars": 4092,
"preview": "# [rpcx](http://rpcx.io)\n\n## 1.9.0\n- unregister all services on close automatically\n- add PostHTTPRequestPlugin \n- suppo"
},
{
"path": "LICENSE",
"chars": 556,
"preview": "Copyright 2017 Chao yuepan\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
},
{
"path": "Makefile",
"chars": 897,
"preview": "WORKDIR=`pwd`\n\ndefault: build\n\nvet:\n\tgo vet ./...\n\ntools:\n\tgo get github.com/golangci/golangci-lint/cmd/golangci-lint\n\tg"
},
{
"path": "README.md",
"chars": 9058,
"preview": "- **stable branch**: v1.7.x\n- **development branch**: master\n\n<a href=\"https://rpcx.io/\"><img height=\"160\" src=\"http://r"
},
{
"path": "_testutils/GoUnusedProtection__.go",
"chars": 114,
"preview": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testutils\r\n\r\nvar GoUnusedProtection__ int\r\n"
},
{
"path": "_testutils/arith_service.go",
"chars": 10575,
"preview": "// Code generated by protoc-gen-gogo.\n// source: arith_service.proto\n// DO NOT EDIT!\n\n/*\n\tPackage client is a generated "
},
{
"path": "_testutils/arith_service.proto",
"chars": 234,
"preview": "// protoc --gogofaster_out=. arith_service.proto\n// mv arith_service.pb.go arith_service_test.go \nsyntax = \"proto3\";\n\npa"
},
{
"path": "_testutils/thrift_arith_service-consts.go",
"chars": 382,
"preview": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testutils\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"fm"
},
{
"path": "_testutils/thrift_arith_service.go",
"chars": 7537,
"preview": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testutils\r\n\r\nimport (\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"fm"
},
{
"path": "_testutils/thrift_arith_service.thrift",
"chars": 88,
"preview": "struct ThriftArgs\n{\n 1: i32 a,\n 2: i32 b,\n}\n\n\nstruct ThriftReply\n{\n 1: i32 c,\n}"
},
{
"path": "client/cache_client_builder.go",
"chars": 1062,
"preview": "package client\r\n\r\nimport \"sync\"\r\n\r\n// CacheClientBuilder defines builder interface to generate RPCCient.\r\ntype CacheClie"
},
{
"path": "client/circuit_breaker.go",
"chars": 1945,
"preview": "package client\n\nimport (\n\t\"errors\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nvar (\n\tErrBreakerOpen = errors.New(\"breaker open\")\n\tErrB"
},
{
"path": "client/circuit_breaker_test.go",
"chars": 1389,
"preview": "package client\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestConsecCircuitBreaker(t *testing.T) {\n\tcou"
},
{
"path": "client/client.go",
"chars": 21787,
"preview": "package client\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings"
},
{
"path": "client/client_test.go",
"chars": 5189,
"preview": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\ttestutils \"github.com/smalln"
},
{
"path": "client/connection.go",
"chars": 4910,
"preview": "package client\n\nimport (\n\t\"bufio\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/smallnes"
},
{
"path": "client/connection_iouring.go",
"chars": 170,
"preview": "package client\n\nimport (\n\t\"net\"\n)\n\n// experimental\nfunc newIOUringConn(c *Client, network, address string) (net.Conn, er"
},
{
"path": "client/connection_iouring_test.go",
"chars": 857,
"preview": "//go:build linux\n// +build linux\n\npackage client\n\n// func TestXClient_IOUring(t *testing.T) {\n// \ts := server.NewServer("
},
{
"path": "client/connection_kcp.go",
"chars": 239,
"preview": "// +build kcp\n\npackage client\n\nimport (\n\t\"net\"\n\n\tkcp \"github.com/xtaci/kcp-go\"\n)\n\nfunc newDirectKCPConn(c *Client, netwo"
},
{
"path": "client/connection_memu.go",
"chars": 178,
"preview": "package client\n\nimport (\n\t\"net\"\n\n\t\"github.com/akutz/memconn\"\n)\n\nfunc newMemuConn(c *Client, network, address string) (ne"
},
{
"path": "client/connection_nonkcp.go",
"chars": 184,
"preview": "// +build !kcp\n\npackage client\n\nimport (\n\t\"errors\"\n\t\"net\"\n)\n\nfunc newDirectKCPConn(c *Client, network, address string) ("
},
{
"path": "client/connection_nonquic.go",
"chars": 204,
"preview": "//go:build !quic\n// +build !quic\n\npackage client\n\nimport (\n\t\"errors\"\n\t\"net\"\n)\n\nfunc newDirectQuicConn(c *Client, network"
},
{
"path": "client/connection_quic.go",
"chars": 488,
"preview": "//go:build quic\n// +build quic\n\npackage client\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\n\t\"github.com/quic-go/quic-go\"\n\t\"github.co"
},
{
"path": "client/connection_rdma.go",
"chars": 348,
"preview": "//go:build rdma\n// +build rdma\n\npackage client\n\nimport (\n\t\"errors\"\n\t\"net\"\n\n\t\"github.com/smallnest/rsocket\"\n)\n\nfunc init("
},
{
"path": "client/discovery.go",
"chars": 3295,
"preview": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n)\n\n// ServiceDiscoveryFilter can be used to fil"
},
{
"path": "client/dns_discovery.go",
"chars": 2705,
"preview": "package client\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/smallnest/rpcx/log\"\n)\n\n// DNSDiscovery is b"
},
{
"path": "client/failmode_enumer.go",
"chars": 1262,
"preview": "// Code generated by \"enumer -type=FailMode\"; DO NOT EDIT.\n\npackage client\n\nimport (\n\t\"fmt\"\n)\n\nconst _FailModeName = \"Fa"
},
{
"path": "client/geo_utils.go",
"chars": 542,
"preview": "package client\n\nimport (\n\t\"math\"\n)\n\n// https://gist.github.com/cdipaolo/d3f8db3848278b49db68\nfunc getDistanceFrom(lat1, "
},
{
"path": "client/hash_utils.go",
"chars": 1284,
"preview": "package client\n\nimport (\n\t\"fmt\"\n\t\"hash/fnv\"\n)\n\n// Hash consistently chooses a hash bucket number in the range [0, numBuc"
},
{
"path": "client/mdns_discovery.go",
"chars": 3998,
"preview": "package client\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/url\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/grandcat/zeroconf\"\n\t\"githu"
},
{
"path": "client/mode.go",
"chars": 993,
"preview": "package client\n\n// FailMode decides how clients action when clients fail to invoke services\ntype FailMode int\n\nconst (\n\t"
},
{
"path": "client/multiple_servers_discovery.go",
"chars": 1971,
"preview": "package client\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/smallnest/rpcx/log\"\n)\n\n// MultipleServersDiscovery is a multiple "
},
{
"path": "client/oneclient.go",
"chars": 8787,
"preview": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/smallnest/rpcx/share\"\n\n\tmultierror \"github"
},
{
"path": "client/oneclient_pool.go",
"chars": 2841,
"preview": "package client\n\nimport (\n\t\"sync/atomic\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\n// OneClientPool is a oneclient pool w"
},
{
"path": "client/oneclient_pool_test.go",
"chars": 3313,
"preview": "package client\n\nimport (\n\t\"testing\"\n)\n\nfunc TestOneClientPool_SetPlugins(t *testing.T) {\n\t// Create a simple discovery\n\t"
},
{
"path": "client/peer2peer_discovery.go",
"chars": 1045,
"preview": "package client\n\n// Peer2PeerDiscovery is a peer-to-peer service discovery.\n// It always returns the static server.\ntype "
},
{
"path": "client/ping_utils.go",
"chars": 2286,
"preview": "package client\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\tping \"github.com/go-ping/ping\"\n)\n\n// weightedICMPSelecto"
},
{
"path": "client/plugin.go",
"chars": 6121,
"preview": "package client\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\n// pluginContainer implements Plugi"
},
{
"path": "client/selectmode_enumer.go",
"chars": 1435,
"preview": "// Code generated by \"enumer -type=SelectMode\"; DO NOT EDIT.\n\npackage client\n\nimport (\n\t\"fmt\"\n)\n\nconst _SelectModeName ="
},
{
"path": "client/selector.go",
"chars": 7202,
"preview": "package client\n\nimport (\n\t\"container/ring\"\n\t\"context\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net/url\"\n\t\"sort\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gith"
},
{
"path": "client/selector_test.go",
"chars": 4173,
"preview": "package client\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc Test_consistentHashSelector_Select(t *testing.T) {\n\tservers := ma"
},
{
"path": "client/smooth_weighted_round_robin.go",
"chars": 173,
"preview": "package client\n\n// Weighted is a wrapped server with weight\ntype Weighted struct {\n\tServer string\n\tWeight "
},
{
"path": "client/xclient.go",
"chars": 35189,
"preview": "package client\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"sort\"\n\t\"strings"
},
{
"path": "client/xclient_pool.go",
"chars": 2973,
"preview": "package client\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\n// XClientPool is a xclient po"
},
{
"path": "client/xclient_pool_test.go",
"chars": 3410,
"preview": "package client\n\nimport (\n\t\"testing\"\n)\n\n// testPlugin is a simple test plugin implementation\ntype testPlugin struct {\n\tna"
},
{
"path": "client/xclient_test.go",
"chars": 2990,
"preview": "package client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"fmt\"\n\n\ttestutils \"github.com/smallnest/rpcx/_testuti"
},
{
"path": "clientplugin/req_rate_limiting_redis.go",
"chars": 1266,
"preview": "package clientplugin\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/go-redis/redis_rate/v10\"\n\t\"github.com/redis/go-redis/v9\""
},
{
"path": "codec/codec.go",
"chars": 3557,
"preview": "package codec\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\n\tpb \"google.golang.org/protobu"
},
{
"path": "codec/codec_test.go",
"chars": 2696,
"preview": "package codec\n\nimport (\n\t\"testing\"\n\n\t\"github.com/smallnest/rpcx/codec/testdata\"\n)\n\ntype ColorGroup struct {\n\tId int "
},
{
"path": "codec/testdata/GoUnusedProtection__.go",
"chars": 116,
"preview": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testdata\r\n\r\nvar GoUnusedProtection__ int;\r\n\r\n"
},
{
"path": "codec/testdata/gen.sh",
"chars": 243,
"preview": "# generate .go files from IDL\nprotoc -I. --go_out=. --go_opt=module=\"testdata\" ./protobuf.proto\n\nthrift -r -out ../ --g"
},
{
"path": "codec/testdata/protobuf.pb.go",
"chars": 4769,
"preview": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc v5.26.0\n// sou"
},
{
"path": "codec/testdata/protobuf.proto",
"chars": 166,
"preview": "syntax = \"proto3\";\n\npackage testdata;\n\noption go_package = \"./testdata\";\n\nmessage ProtoColorGroup {\n int32 id = 1;\n st"
},
{
"path": "codec/testdata/thrift_colorgroup-consts.go",
"chars": 384,
"preview": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testdata\r\n\r\nimport(\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"fmt\""
},
{
"path": "codec/testdata/thrift_colorgroup.go",
"chars": 7087,
"preview": "// Code generated by Thrift Compiler (0.14.0). DO NOT EDIT.\r\n\r\npackage testdata\r\n\r\nimport(\r\n\t\"bytes\"\r\n\t\"context\"\r\n\t\"fmt\""
},
{
"path": "codec/testdata/thrift_colorgroup.thrift",
"chars": 112,
"preview": "namespace go testdata\n\nstruct ThriftColorGroup {\n 1: i32 id = 0,\n 2: string name,\n 3: list<string> colors,\n}\n"
},
{
"path": "errors/error.go",
"chars": 639,
"preview": "package errors\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\n// MultiError holds multiple errors\ntype MultiError struct {\n\tErrors []error\n"
},
{
"path": "errors/error_test.go",
"chars": 1339,
"preview": "package errors\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewMultiError(t "
},
{
"path": "go.mod",
"chars": 3384,
"preview": "module github.com/smallnest/rpcx\n\ngo 1.24\n\nrequire (\n\tgithub.com/akutz/memconn v0.1.0\n\tgithub.com/alitto/pond v1.9.2\n\tgi"
},
{
"path": "go.sum",
"chars": 43292,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go."
},
{
"path": "log/default_logger.go",
"chars": 2919,
"preview": "package log\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/fatih/color\"\n)\n\n// The default logger output at"
},
{
"path": "log/dummy_logger.go",
"chars": 743,
"preview": "package log\n\ntype dummyLogger struct{}\n\nfunc (l *dummyLogger) Debug(v ...interface{}) {\n}\n\nfunc (l *dummyLogger) Debugf("
},
{
"path": "log/logger.go",
"chars": 1431,
"preview": "package log\n\nimport (\n\t\"log\"\n\t\"os\"\n)\n\nconst (\n\tcalldepth = 3\n)\n\nvar l Logger = NewDefaultLogger(os.Stdout, \"\", log.LstdF"
},
{
"path": "protocol/compressor.go",
"chars": 1393,
"preview": "package protocol\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\n\t\"github.com/golang/snappy\"\n\t\"github.com/smallnest/rpcx/util\"\n)\n\n// Compresso"
},
{
"path": "protocol/compressor_test.go",
"chars": 2756,
"preview": "package protocol\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/smallnest/rpcx/codec\"\n\t\"github.com/smallnest/rpcx/protoco"
},
{
"path": "protocol/message.go",
"chars": 11662,
"preview": "package protocol\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"runtime\"\n\n\t\"github.com/smallnest/rpcx/lo"
},
{
"path": "protocol/message_chan_test.go",
"chars": 2240,
"preview": "package protocol\n\nimport (\n \"fmt\"\n \"strings\"\n \"sync/atomic\"\n \"testing\"\n \"time\"\n)\n\nfunc TestChanValue(t *t"
},
{
"path": "protocol/message_test.go",
"chars": 1168,
"preview": "package protocol\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestMessage(t *testing.T) {\n\treq := NewMessage()\n\treq.SetVersion("
},
{
"path": "protocol/testdata/benchmark.pb.go",
"chars": 18002,
"preview": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc v5.26.0\n// sou"
},
{
"path": "protocol/testdata/benchmark.proto",
"chars": 1031,
"preview": "syntax = \"proto3\";\n\npackage testdata;\n\noption optimize_for = SPEED;\noption go_package = \"./testdata\";\n\n\nmessage Benchmar"
},
{
"path": "protocol/testdata/gen.sh",
"chars": 196,
"preview": "# curl -O https://raw.githubusercontent.com/rpcxio/rpcx-benchmark/master/proto/benchmark.proto\n\n# generate .go files fro"
},
{
"path": "reflection/server_reflection.go",
"chars": 4749,
"preview": "package reflection\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strings\"\n\t\"text/template\"\n"
},
{
"path": "reflection/server_reflection_test.go",
"chars": 943,
"preview": "package reflection\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/kr/pretty\"\n\ttestutils \"github.com/smallnest/"
},
{
"path": "server/context.go",
"chars": 3663,
"preview": "package server\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/smallnest/rpcx/share\"\n)\n\n// C"
},
{
"path": "server/converter.go",
"chars": 2958,
"preview": "package server\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/sma"
},
{
"path": "server/converter_test.go",
"chars": 1080,
"preview": "package server\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/smallnest/rpcx/codec\"\n\t\"github.com/smallnest/rpcx"
},
{
"path": "server/file_transfer.go",
"chars": 4894,
"preview": "package server\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\tlru \"github.com/hashicorp/golang-lru\"\n"
},
{
"path": "server/gateway.go",
"chars": 6860,
"preview": "package server\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\""
},
{
"path": "server/jsonrpc2.go",
"chars": 4756,
"preview": "package server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github."
},
{
"path": "server/jsonrpc2_wire.go",
"chars": 4412,
"preview": "// Copyright 2018 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license "
},
{
"path": "server/kcp.go",
"chars": 611,
"preview": "// +build kcp\n\npackage server\n\nimport (\n\t\"errors\"\n\t\"net\"\n\n\tkcp \"github.com/xtaci/kcp-go\"\n)\n\nfunc init() {\n\tmakeListeners"
},
{
"path": "server/listener.go",
"chars": 1446,
"preview": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n)\n\nvar makeListeners = make(map[string]MakeListener)\n\nfun"
},
{
"path": "server/listener_linux.go",
"chars": 1407,
"preview": "//go:build linux\n// +build linux\n\npackage server\n\nimport (\n\t\"net\"\n\t\"runtime\"\n\t\"time\"\n\n\turingnet \"github.com/godzie44/go-"
},
{
"path": "server/listener_rdma.go",
"chars": 602,
"preview": "//go:build rdma\n// +build rdma\n\npackage server\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/smallnest/rsocket\"\n)\n\nfun"
},
{
"path": "server/listener_unix.go",
"chars": 683,
"preview": "//go:build !windows\n// +build !windows\n\npackage server\n\nimport (\n\t\"net\"\n\n\treuseport \"github.com/kavu/go_reuseport\"\n)\n\nfu"
},
{
"path": "server/memconn.go",
"chars": 251,
"preview": "package server\n\nimport (\n\t\"net\"\n\n\t\"github.com/akutz/memconn\"\n)\n\nfunc init() {\n\tmakeListeners[\"memu\"] = memconnMakeListen"
},
{
"path": "server/option.go",
"chars": 1276,
"preview": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"time\"\n\n\t\"github.com/alitto/pond\"\n)\n\n// OptionFn configures options of server.\nt"
},
{
"path": "server/option_test.go",
"chars": 558,
"preview": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestOption(t *t"
},
{
"path": "server/options.go",
"chars": 219,
"preview": "package server\n\nimport \"time\"\n\n// WithTCPKeepAlivePeriod sets tcp keepalive period.\nfunc WithTCPKeepAlivePeriod(period t"
},
{
"path": "server/plugin.go",
"chars": 10758,
"preview": "package server\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/smallnest/rp"
},
{
"path": "server/plugin_test.go",
"chars": 1767,
"preview": "package server\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/smallnest/rpcx/client\"\n\t\"github.com/"
},
{
"path": "server/pool.go",
"chars": 1023,
"preview": "package server\n\nimport (\n\t\"reflect\"\n\t\"sync\"\n)\n\n// Reset defines Reset method for pooled object.\ntype Reset interface {\n\t"
},
{
"path": "server/pool_test.go",
"chars": 489,
"preview": "package server\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype Elem struct {\n\t// magicNum"
},
{
"path": "server/quic.go",
"chars": 495,
"preview": "//go:build quic\n// +build quic\n\npackage server\n\nimport (\n\t\"errors\"\n\t\"net\"\n\n\t\"github.com/smallnest/quick\"\n)\n\nfunc init() "
},
{
"path": "server/router.go",
"chars": 296,
"preview": "package server\n\n// UpdateHandler 批量更新router\n// 服务器使用plugin热更时,批量替换特定接口\nfunc (s *Server) UpdateHandler(router map[string]"
},
{
"path": "server/server.go",
"chars": 26979,
"preview": "package server\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\""
},
{
"path": "server/server_test.go",
"chars": 4284,
"preview": "package server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\ttestutils \"github.com/sm"
},
{
"path": "server/service.go",
"chars": 12142,
"preview": "package server\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"unicode\"\n\t\"unicode/utf8\""
},
{
"path": "server/service_test.go",
"chars": 706,
"preview": "package server\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_isExported"
},
{
"path": "server/stream.go",
"chars": 4005,
"preview": "package server\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\tlru \"github.com/hashicorp/go"
},
{
"path": "serverplugin/alias.go",
"chars": 2148,
"preview": "package serverplugin\n\nimport (\n\t\"context\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\nvar aliasAppliedKey = \"__aliasApplie"
},
{
"path": "serverplugin/blacklist.go",
"chars": 705,
"preview": "package serverplugin\n\nimport \"net\"\n\n// BlacklistPlugin is a plugin that control only ip addresses in blacklist can **NOT"
},
{
"path": "serverplugin/mdns.go",
"chars": 5063,
"preview": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings"
},
{
"path": "serverplugin/metrics.go",
"chars": 4087,
"preview": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/rcrowley/go-met"
},
{
"path": "serverplugin/rate_limiting.go",
"chars": 745,
"preview": "package serverplugin\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/juju/ratelimit\"\n)\n\n// RateLimitingPlugin can limit connectin"
},
{
"path": "serverplugin/redis.go",
"chars": 7026,
"preview": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tmetrics \"githu"
},
{
"path": "serverplugin/registry_test.go",
"chars": 238,
"preview": "package serverplugin\n\nimport \"context\"\n\ntype Args struct {\n\tA int\n\tB int\n}\n\ntype Reply struct {\n\tC int\n}\n\ntype Arith int"
},
{
"path": "serverplugin/req_rate_limiting.go",
"chars": 1088,
"preview": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/juju/ratelimit\"\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\""
},
{
"path": "serverplugin/req_rate_limiting_redis.go",
"chars": 1273,
"preview": "package serverplugin\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/go-redis/redis_rate/v10\"\n\t\"github.com/redis/go-redis/v9\""
},
{
"path": "serverplugin/tee.go",
"chars": 872,
"preview": "package serverplugin\n\nimport (\n\t\"io\"\n\t\"net\"\n)\n\n// TeeConnPlugin is a plugin that copy requests from clients and send to "
},
{
"path": "serverplugin/whitelist.go",
"chars": 697,
"preview": "package serverplugin\n\nimport \"net\"\n\n// WhitelistPlugin is a plugin that control only ip addresses in whitelist can acces"
},
{
"path": "share/context.go",
"chars": 2153,
"preview": "package share\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"sync\"\n)\n\n// var _ context.Context = &Context{}\n\n// Context is a r"
},
{
"path": "share/context_test.go",
"chars": 1002,
"preview": "package share\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar (\n\tTheAnswer = \"Answer to "
},
{
"path": "share/share.go",
"chars": 3265,
"preview": "package share\n\nimport (\n\t\"github.com/smallnest/rpcx/codec\"\n\t\"github.com/smallnest/rpcx/protocol\"\n)\n\nconst (\n\t// DefaultR"
},
{
"path": "share/share_test.go",
"chars": 533,
"preview": "package share\n\nimport (\n\t\"testing\"\n\n\t\"github.com/smallnest/rpcx/protocol\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype "
},
{
"path": "tool/xgen/README.md",
"chars": 1044,
"preview": "# xgen\n\n`xgen` isn a tool that can help you generate a server stub for rpcx services.\n\nIt search structs in your specifi"
},
{
"path": "tool/xgen/parser/parser.go",
"chars": 1965,
"preview": "// Package parser parses Go code and keeps track of all the types defined\n// and provides access to all the constants de"
},
{
"path": "tool/xgen/xgen.go",
"chars": 4743,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"go/build\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/smallnest/rpcx/tool/"
},
{
"path": "util/buffer_pool.go",
"chars": 1683,
"preview": "package util\n\nimport (\n\t\"math\"\n\t\"sync\"\n)\n\ntype levelPool struct {\n\tsize int\n\tpool sync.Pool\n}\n\nfunc newLevelPool(size in"
},
{
"path": "util/buffer_pool_test.go",
"chars": 1320,
"preview": "package util\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestLimitedPool_findPool(t *testing.T) {\n\tpool := NewLimitedPool(512, 4"
},
{
"path": "util/compress.go",
"chars": 1176,
"preview": "package util\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"io\"\n\t\"sync\"\n)\n\nvar (\n\tspWriter sync.Pool\n\tspReader sync.Pool\n\tspBuffe"
},
{
"path": "util/compress_test.go",
"chars": 2440,
"preview": "package util\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestZip(t *testing.T) {\n\ts := \"%5B%7B%22servic"
},
{
"path": "util/converter.go",
"chars": 395,
"preview": "package util\n\nimport (\n\t\"unsafe\"\n)\n\nfunc SliceByteToString(b []byte) string {\n\treturn *(*string)(unsafe.Pointer(&b))\n}\n\n"
},
{
"path": "util/net.go",
"chars": 3118,
"preview": "package util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// GetFreePort gets a free po"
},
{
"path": "util/net_test.go",
"chars": 1111,
"preview": "package util\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestGetFreePort(t *testing.T) {\n\tfor i := 0; i < 1000; i++ {"
}
]
About this extraction
This page contains the full source code of the smallnest/rpcx GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 128 files (428.0 KB), approximately 142.6k tokens, and a symbol index with 1003 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.