Merge branch 'jsonrpc' into 'master'
jsonrpc See merge request slon/shad-go-private!30
This commit is contained in:
commit
b38930111f
3 changed files with 125 additions and 0 deletions
39
jsonrpc/README.md
Normal file
39
jsonrpc/README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
## jsonrpc
|
||||||
|
|
||||||
|
Главная идея [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) (remove procedure call) заключается в том,
|
||||||
|
чтобы вызов удаленных процедур (например, работающих на другой машине) был очень похожим на вызов функций внутри программы.
|
||||||
|
Вся механика удаленных вызовов прячется от пользователя.
|
||||||
|
|
||||||
|
Для чего это нужно? Например, для облегчения организации распределенных вычислений.
|
||||||
|
|
||||||
|
Реализация RPC включает в себя два компонента:
|
||||||
|
сетевой протокол для обмена в режиме клиент-сервер и язык сериализации объектов.
|
||||||
|
|
||||||
|
В данной задаче нужно реализовать RPC, используя http в качестве транспорта и json для сериализации запросов/ответов.
|
||||||
|
|
||||||
|
### Клиентская часть
|
||||||
|
```
|
||||||
|
Call(ctx context.Context, endpoint string, method string, req, rsp interface{}) error
|
||||||
|
```
|
||||||
|
`Call` делает http запрос на заданную ручку (endpoint+method), передавая в body сериализованный запрос и
|
||||||
|
возвращает десериализованный ответ через аргумент.
|
||||||
|
|
||||||
|
Логической частью запроса занимается сервер.
|
||||||
|
Для пользователя работа с `Call` выглядит практически также, как работа с обычной функцией:
|
||||||
|
```
|
||||||
|
method(req, &resp) error
|
||||||
|
```
|
||||||
|
|
||||||
|
### Серверная часть
|
||||||
|
```
|
||||||
|
MakeHandler(service interface{}) http.Handler
|
||||||
|
```
|
||||||
|
|
||||||
|
RPC сервис - это структура, на которой определено несколько RPC методов.
|
||||||
|
RPC метод - это функция с сигнатурой вида:
|
||||||
|
```
|
||||||
|
Method(ctx context.Context, req *Request) (*Response, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
`MakeHandler` создаёт http.Handler,
|
||||||
|
предоставляющий http ручки для всех RPC методов сервиса (http endpoint = method name).
|
16
jsonrpc/jsonrpc.go
Normal file
16
jsonrpc/jsonrpc.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// +build !solution
|
||||||
|
|
||||||
|
package jsonrpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MakeHandler(service interface{}) http.Handler {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Call(ctx context.Context, endpoint string, method string, req, rsp interface{}) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
70
jsonrpc/jsonrpc_test.go
Normal file
70
jsonrpc/jsonrpc_test.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package jsonrpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testService struct{}
|
||||||
|
|
||||||
|
type PingRequest struct{}
|
||||||
|
type PingResponse struct{}
|
||||||
|
|
||||||
|
func (*testService) Ping(ctx context.Context, req *PingRequest) (*PingResponse, error) {
|
||||||
|
return &PingResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddRequest struct{ A, B int }
|
||||||
|
type AddResponse struct{ Sum int }
|
||||||
|
|
||||||
|
func (*testService) Add(ctx context.Context, req *AddRequest) (*AddResponse, error) {
|
||||||
|
return &AddResponse{Sum: req.A + req.B}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorRequest struct{}
|
||||||
|
type ErrorResponse struct{}
|
||||||
|
|
||||||
|
func (*testService) Error(ctx context.Context, req *ErrorRequest) (*ErrorResponse, error) {
|
||||||
|
return nil, fmt.Errorf("cache is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONRPC(t *testing.T) {
|
||||||
|
server := httptest.NewServer(MakeHandler(&testService{}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("Ping", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
req PingRequest
|
||||||
|
rsp PingResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
require.NoError(t, Call(ctx, server.URL, "Ping", &req, &rsp))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Add", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
req = AddRequest{A: 1, B: 2}
|
||||||
|
rsp AddResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
require.NoError(t, Call(ctx, server.URL, "Add", &req, &rsp))
|
||||||
|
require.Equal(t, 3, rsp.Sum)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Error", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
req ErrorRequest
|
||||||
|
rsp ErrorResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
err := Call(ctx, server.URL, "Error", &req, &rsp)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "cache is empty")
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue