diff --git a/jsonrpc/README.md b/jsonrpc/README.md new file mode 100644 index 0000000..ea7f06b --- /dev/null +++ b/jsonrpc/README.md @@ -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). \ No newline at end of file diff --git a/jsonrpc/jsonrpc.go b/jsonrpc/jsonrpc.go new file mode 100644 index 0000000..2a52257 --- /dev/null +++ b/jsonrpc/jsonrpc.go @@ -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") +} diff --git a/jsonrpc/jsonrpc_test.go b/jsonrpc/jsonrpc_test.go new file mode 100644 index 0000000..df7c74e --- /dev/null +++ b/jsonrpc/jsonrpc_test.go @@ -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") + }) +}