diff --git a/clienterror.go b/clienterror.go index 732fd0f..4610468 100644 --- a/clienterror.go +++ b/clienterror.go @@ -9,3 +9,11 @@ func NewClientError(err error) *ClientError { error: err, } } + +// Unwrap interface +func (e *ClientError) Unwrap() error { + if e != nil && e.error != nil { + return e.error + } + return nil +} diff --git a/example/basic/client/src/service-client.ts b/example/basic/client/src/service-client.ts index 6ae6605..89b8881 100644 --- a/example/basic/client/src/service-client.ts +++ b/example/basic/client/src/service-client.ts @@ -16,6 +16,9 @@ export class ServiceClient { async boolSlice(v:Array|null):Promise|null> { return (await this.transport<{0:Array|null}>("BoolSlice", [v]))[0] } + async context():Promise { + await this.transport("Context", []) + } async empty():Promise { await this.transport("Empty", []) } diff --git a/example/basic/main_test.go b/example/basic/main_test.go new file mode 100644 index 0000000..9c8157b --- /dev/null +++ b/example/basic/main_test.go @@ -0,0 +1,27 @@ +package main_test + +import ( + "context" + "net/http/httptest" + "testing" + "time" + + "github.com/foomo/gotsrpc/v2/example/basic/service" + "github.com/stretchr/testify/assert" +) + +func TestContextCanceled(t *testing.T) { + ctx, cancel := context.WithCancel(context.TODO()) + go func() { + time.Sleep(time.Second) + cancel() + }() + + svr := httptest.NewServer(service.NewDefaultServiceGoTSRPCProxy(&service.Handler{})) + defer svr.Close() + + client := service.NewDefaultServiceGoTSRPCClient(svr.URL) + + clientErr := client.Context(ctx) + assert.ErrorIs(t, clientErr, context.Canceled) +} diff --git a/example/basic/service/gorpc_gen.go b/example/basic/service/gorpc_gen.go index 806c80d..16f1350 100644 --- a/example/basic/service/gorpc_gen.go +++ b/example/basic/service/gorpc_gen.go @@ -42,6 +42,11 @@ type ( RetBoolSlice_0 []bool } + ServiceContextRequest struct { + } + ServiceContextResponse struct { + } + ServiceEmptyRequest struct { } ServiceEmptyResponse struct { @@ -461,6 +466,8 @@ func init() { gob.Register(ServiceBoolPtrResponse{}) gob.Register(ServiceBoolSliceRequest{}) gob.Register(ServiceBoolSliceResponse{}) + gob.Register(ServiceContextRequest{}) + gob.Register(ServiceContextResponse{}) gob.Register(ServiceEmptyRequest{}) gob.Register(ServiceEmptyResponse{}) gob.Register(ServiceFloat32Request{}) @@ -631,6 +638,9 @@ func (p *ServiceGoRPCProxy) handler(clientAddr string, request interface{}) (res req := request.(ServiceBoolSliceRequest) retBoolSlice_0 := p.service.BoolSlice(req.V) response = ServiceBoolSliceResponse{RetBoolSlice_0: retBoolSlice_0} + case "ServiceContextRequest": + p.service.Context(nil, nil) + response = ServiceContextResponse{} case "ServiceEmptyRequest": p.service.Empty() response = ServiceEmptyResponse{} diff --git a/example/basic/service/gorpcclient_gen.go b/example/basic/service/gorpcclient_gen.go index 84846ed..3cb8f72 100644 --- a/example/basic/service/gorpcclient_gen.go +++ b/example/basic/service/gorpcclient_gen.go @@ -63,6 +63,16 @@ func (tsc *ServiceGoRPCClient) BoolSlice(v []bool) (retBoolSlice_0 []bool, clien return response.RetBoolSlice_0, nil } +func (tsc *ServiceGoRPCClient) Context() (clientErr error) { + req := ServiceContextRequest{} + _, rpcCallErr := tsc.Client.Call(req) + if rpcCallErr != nil { + clientErr = rpcCallErr + return + } + return nil +} + func (tsc *ServiceGoRPCClient) Empty() (clientErr error) { req := ServiceEmptyRequest{} _, rpcCallErr := tsc.Client.Call(req) diff --git a/example/basic/service/gotsrpc_gen.go b/example/basic/service/gotsrpc_gen.go index ba4b679..8841695 100644 --- a/example/basic/service/gotsrpc_gen.go +++ b/example/basic/service/gotsrpc_gen.go @@ -14,6 +14,7 @@ const ( ServiceGoTSRPCProxyBool = "Bool" ServiceGoTSRPCProxyBoolPtr = "BoolPtr" ServiceGoTSRPCProxyBoolSlice = "BoolSlice" + ServiceGoTSRPCProxyContext = "Context" ServiceGoTSRPCProxyEmpty = "Empty" ServiceGoTSRPCProxyFloat32 = "Float32" ServiceGoTSRPCProxyFloat32Map = "Float32Map" @@ -176,6 +177,24 @@ func (p *ServiceGoTSRPCProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) } gotsrpc.Monitor(w, r, args, rets, callStats) return + case ServiceGoTSRPCProxyContext: + var ( + args []interface{} + rets []interface{} + ) + executionStart := time.Now() + rw := gotsrpc.ResponseWriter{ResponseWriter: w} + p.service.Context(&rw, r) + callStats.Execution = time.Since(executionStart) + if rw.Status() == http.StatusOK { + rets = []interface{}{} + if err := gotsrpc.Reply(rets, callStats, r, w); err != nil { + gotsrpc.ErrorCouldNotReply(w) + return + } + } + gotsrpc.Monitor(w, r, args, rets, callStats) + return case ServiceGoTSRPCProxyEmpty: var ( args []interface{} diff --git a/example/basic/service/gotsrpcclient_gen.go b/example/basic/service/gotsrpcclient_gen.go index 9228474..2bdc2a5 100644 --- a/example/basic/service/gotsrpcclient_gen.go +++ b/example/basic/service/gotsrpcclient_gen.go @@ -14,6 +14,7 @@ type ServiceGoTSRPCClient interface { Bool(ctx go_context.Context, v bool) (retBool_0 bool, clientErr error) BoolPtr(ctx go_context.Context, v bool) (retBoolPtr_0 *bool, clientErr error) BoolSlice(ctx go_context.Context, v []bool) (retBoolSlice_0 []bool, clientErr error) + Context(ctx go_context.Context) (clientErr error) Empty(ctx go_context.Context) (clientErr error) Float32(ctx go_context.Context, v float32) (retFloat32_0 float32, clientErr error) Float32Map(ctx go_context.Context, v map[float32]interface{}) (retFloat32Map_0 map[float32]interface{}, clientErr error) @@ -126,6 +127,16 @@ func (tsc *HTTPServiceGoTSRPCClient) BoolSlice(ctx go_context.Context, v []bool) return } +func (tsc *HTTPServiceGoTSRPCClient) Context(ctx go_context.Context) (clientErr error) { + args := []interface{}{} + reply := []interface{}{} + clientErr = tsc.Client.Call(ctx, tsc.URL, tsc.EndPoint, "Context", args, reply) + if clientErr != nil { + clientErr = pkg_errors.WithMessage(clientErr, "failed to call service.ServiceGoTSRPCProxy Context") + } + return +} + func (tsc *HTTPServiceGoTSRPCClient) Empty(ctx go_context.Context) (clientErr error) { args := []interface{}{} reply := []interface{}{} diff --git a/example/basic/service/handler.go b/example/basic/service/handler.go index ca5a747..075f948 100644 --- a/example/basic/service/handler.go +++ b/example/basic/service/handler.go @@ -1,10 +1,20 @@ package service +import ( + "net/http" + "time" +) + type Handler struct{} -func (h *Handler) Empty() { +func (h *Handler) Context(w http.ResponseWriter, r *http.Request) { + for r.Context().Err() == nil { + time.Sleep(100 * time.Millisecond) + } } +func (h *Handler) Empty() {} + func (h *Handler) Bool(v bool) bool { return v } diff --git a/example/basic/service/service.go b/example/basic/service/service.go index 3adcc05..b651228 100644 --- a/example/basic/service/service.go +++ b/example/basic/service/service.go @@ -1,6 +1,11 @@ package service +import ( + "net/http" +) + type Service interface { + Context(w http.ResponseWriter, r *http.Request) Empty() Bool(v bool) bool BoolPtr(v bool) *bool diff --git a/example/errors/handler/frontend/handler.go b/example/errors/handler/frontend/handler.go index e6ce3b9..c86be1d 100644 --- a/example/errors/handler/frontend/handler.go +++ b/example/errors/handler/frontend/handler.go @@ -1,7 +1,9 @@ package frontend import ( + "fmt" "net/http" + "time" "github.com/foomo/gotsrpc/v2/example/errors/service/backend" "github.com/foomo/gotsrpc/v2/example/errors/service/frontend" @@ -18,6 +20,12 @@ func New(client backend.ServiceGoTSRPCClient) *Handler { } func (h *Handler) Simple(w http.ResponseWriter, r *http.Request) (e *frontend.ErrSimple) { + fmt.Println("==========> incoming") + time.Sleep(time.Second) + if r.Context().Err() != nil { + fmt.Println("==========>" + r.Context().Err().Error()) + } + fmt.Println("<========== outgoing") return } diff --git a/example/errors/main.go b/example/errors/main.go index 3f8174b..5ffdb24 100644 --- a/example/errors/main.go +++ b/example/errors/main.go @@ -8,7 +8,6 @@ import ( "context" "fmt" "net/http" - "os/exec" "strings" "time" @@ -40,7 +39,7 @@ func main() { go func() { time.Sleep(time.Second) - _ = exec.Command("open", "http://127.0.0.1:3000").Run() + // _ = exec.Command("open", "http://127.0.0.1:3000").Run() call() }() @@ -49,12 +48,30 @@ func main() { func call() { ctx := context.Background() + time.Sleep(time.Second) + + ctx2, cancel := context.WithCancel(ctx) + req, _ := http.NewRequestWithContext(ctx2, "POST", "http://127.0.0.1:3000/service/frontend/Simple", nil) + go func() { + time.Sleep(time.Millisecond * 100) + fmt.Println("cancel") + cancel() + }() + fmt.Println("sending request") + res, err := http.DefaultClient.Do(req) + time.Sleep(time.Second) + fmt.Printf("%v\n", err) + fmt.Printf("%v\n", res.StatusCode) + c := backendsvs.NewDefaultServiceGoTSRPCClient("http://localhost:3000") { fmt.Println("--- Error ----------------------") var gotsrpcErr *gotsrpc.Error - serviceErr, err := c.Error(ctx) + + ctx2, cancel2 := context.WithCancel(ctx) + cancel2() + serviceErr, err := c.Error(ctx2) if err != nil { panic("client error should be nil") } else if serviceErr == nil {