func TestHTTPClient(t *testing.T) { ts := httptest.NewServer(jsonrpc2.HTTPHandler(nil)) // Don't close because of https://github.com/golang/go/issues/12262 // defer ts.Close() client := jsonrpc2.NewHTTPClient(ts.URL) defer client.Close() var in [2]int var got, want int in, want = [2]int{1, 2}, 3 err := client.Call("Svc.Sum", in, &got) if err != nil { t.Errorf("Call(%v), err = %v", in, err) } if got != want { t.Errorf("Call(%v) = %v, want = %v", in, got, want) } in = [2]int{2, 3} err = client.Notify("Svc.Sum", in) if err != nil { t.Errorf("Notify(%v), err = %v", in, err) } in, want = [2]int{3, 4}, 7 err = client.Call("Svc.Sum", in, &got) if err != nil { t.Errorf("Call(%v), err = %v", in, err) } if got != want { t.Errorf("Call(%v) = %v, want = %v", in, got, want) } }
func BenchmarkJSONRPC2_http(b *testing.B) { ts := httptest.NewServer(jsonrpc2.HTTPHandler(nil)) // Don't close because of https://github.com/golang/go/issues/12262 // defer ts.Close() client := jsonrpc2.NewHTTPClient(ts.URL) defer client.Close() benchmarkRPC(b, client) }
// - for each of these servers: // * TCP server without context // * TCP server with context // * HTTP server with context // - call these methods: // * Sum() // * Name() // * NameCtx() // * TODO batch call all func TestContext(t *testing.T) { // Server provide a TCP transport without context. serverTCPNoCtx, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer serverTCPNoCtx.Close() go func() { for { conn, err := serverTCPNoCtx.Accept() if err != nil { return } go jsonrpc2.ServeConn(conn) } }() // Server provide a TCP transport with context. serverTCP, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer serverTCP.Close() go func() { for { conn, err := serverTCP.Accept() if err != nil { return } ctx := context.WithValue(context.Background(), remoteAddrContextKey, conn.RemoteAddr()) go jsonrpc2.ServeConnContext(ctx, conn) } }() // Server provide a HTTP transport with context. http.Handle("/", jsonrpc2.HTTPHandler(nil)) serverHTTP, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer serverHTTP.Close() go http.Serve(serverHTTP, nil) // Client use TCP transport without context. clientTCPNoCtx, err := jsonrpc2.Dial("tcp", serverTCPNoCtx.Addr().String()) if err != nil { t.Fatal(err) } defer clientTCPNoCtx.Close() // Client use TCP transport with context. clientTCP, err := jsonrpc2.Dial("tcp", serverTCP.Addr().String()) if err != nil { t.Fatal(err) } defer clientTCP.Close() // Client use HTTP transport with context. clientHTTP := jsonrpc2.NewHTTPClient("http://" + serverHTTP.Addr().String() + "/") defer clientHTTP.Close() cases := []struct { client *jsonrpc2.Client clientName string method string arg interface{} want interface{} }{ {clientTCPNoCtx, "clientTCPNoCtx", "CtxSvc.Sum", [2]int{3, 5}, 8.0}, {clientTCP, "clientTCP", "CtxSvc.Sum", [2]int{3, 5}, 8.0}, {clientHTTP, "clientHTTP", "CtxSvc.Sum", [2]int{3, 5}, 8.0}, {clientTCPNoCtx, "clientTCPNoCtx", "CtxSvc.Name", NameArg{"First", "Last"}, map[string]interface{}{ "Name": "First Last", }}, {clientTCP, "clientTCP", "CtxSvc.Name", NameArg{"First", "Last"}, map[string]interface{}{ "Name": "First Last", }}, {clientHTTP, "clientHTTP", "CtxSvc.Name", NameArg{"First", "Last"}, map[string]interface{}{ "Name": "First Last", }}, {clientTCPNoCtx, "clientTCPNoCtx", "CtxSvc.NameCtx", NameArg{"First", "Last"}, map[string]interface{}{ "Name": "First Last", "TCPRemoteAddr": "", "HTTPRemoteAddr": "", }}, {clientTCP, "clientTCP", "CtxSvc.NameCtx", NameArg{"First", "Last"}, map[string]interface{}{ "Name": "First Last", "TCPRemoteAddr": "127.0.0.1", "HTTPRemoteAddr": "", }}, {clientHTTP, "clientHTTP", "CtxSvc.NameCtx", NameArg{"First", "Last"}, map[string]interface{}{ "Name": "First Last", "TCPRemoteAddr": "", "HTTPRemoteAddr": "127.0.0.1", }}, } for _, v := range cases { var res interface{} err := v.client.Call(v.method, v.arg, &res) if err != nil { t.Errorf("%s.Call(%q) = %v", v.clientName, v.method, err) } if !reflect.DeepEqual(v.want, res) { t.Errorf("%s.Call(%q):\n\n\texp: %#v\n\n\tgot: %#v\n\n", v.clientName, v.method, v.want, res) } } }
func Example() { // Server export an object of type ExampleSvc. rpc.Register(&ExampleSvc{}) // Server provide a TCP transport. lnTCP, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { panic(err) } defer lnTCP.Close() go func() { for { conn, err := lnTCP.Accept() if err != nil { return } ctx := context.WithValue(context.Background(), RemoteAddrContextKey, conn.RemoteAddr()) go jsonrpc2.ServeConnContext(ctx, conn) } }() // Server provide a HTTP transport on /rpc endpoint. http.Handle("/rpc", jsonrpc2.HTTPHandler(nil)) lnHTTP, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { panic(err) } defer lnHTTP.Close() go http.Serve(lnHTTP, nil) // Client use TCP transport. clientTCP, err := jsonrpc2.Dial("tcp", lnTCP.Addr().String()) if err != nil { panic(err) } defer clientTCP.Close() // Client use HTTP transport. clientHTTP := jsonrpc2.NewHTTPClient("http://" + lnHTTP.Addr().String() + "/rpc") defer clientHTTP.Close() // Custom client use HTTP transport. clientCustomHTTP := jsonrpc2.NewCustomHTTPClient( "http://"+lnHTTP.Addr().String()+"/rpc", jsonrpc2.DoerFunc(func(req *http.Request) (*http.Response, error) { // Setup custom HTTP client. client := &http.Client{} // Modify request as needed. req.Header.Set("Content-Type", "application/json-rpc") return client.Do(req) }), ) defer clientCustomHTTP.Close() var reply int // Synchronous call using positional params and TCP. err = clientTCP.Call("ExampleSvc.Sum", [2]int{3, 5}, &reply) fmt.Printf("Sum(3,5)=%d\n", reply) // Synchronous call using positional params and HTTP. err = clientHTTP.Call("ExampleSvc.SumAll", []int{3, 5, -2}, &reply) fmt.Printf("SumAll(3,5,-2)=%d\n", reply) // Asynchronous call using named params and TCP. startCall := clientTCP.Go("ExampleSvc.MapLen", map[string]int{"a": 10, "b": 20, "c": 30}, &reply, nil) replyCall := <-startCall.Done fmt.Printf("MapLen({a:10,b:20,c:30})=%d\n", *replyCall.Reply.(*int)) // Notification using named params and HTTP. clientHTTP.Notify("ExampleSvc.FullName", NameArg{"First", "Last"}) // Synchronous call using named params and TCP with context. clientTCP.Call("ExampleSvc.FullName2", NameArg{"First", "Last"}, nil) // Synchronous call using named params and HTTP with context. clientHTTP.Call("ExampleSvc.FullName3", NameArg{"First", "Last"}, nil) // Correct error handling. err = clientTCP.Call("ExampleSvc.Err1", nil, nil) if err == rpc.ErrShutdown || err == io.ErrUnexpectedEOF { fmt.Printf("Err1(): %q\n", err) } else if err != nil { rpcerr := jsonrpc2.ServerError(err) fmt.Printf("Err1(): code=%d msg=%q data=%v\n", rpcerr.Code, rpcerr.Message, rpcerr.Data) } err = clientCustomHTTP.Call("ExampleSvc.Err2", nil, nil) if err == rpc.ErrShutdown || err == io.ErrUnexpectedEOF { fmt.Printf("Err2(): %q\n", err) } else if err != nil { rpcerr := jsonrpc2.ServerError(err) fmt.Printf("Err2(): code=%d msg=%q data=%v\n", rpcerr.Code, rpcerr.Message, rpcerr.Data) } err = clientHTTP.Call("ExampleSvc.Err3", nil, nil) if err == rpc.ErrShutdown || err == io.ErrUnexpectedEOF { fmt.Printf("Err3(): %q\n", err) } else if err != nil { rpcerr := jsonrpc2.ServerError(err) fmt.Printf("Err3(): code=%d msg=%q data=%v\n", rpcerr.Code, rpcerr.Message, rpcerr.Data) } // Output: // Sum(3,5)=8 // SumAll(3,5,-2)=6 // MapLen({a:10,b:20,c:30})=3 // FullName2(): Remote IP is 127.0.0.1 // FullName3(): Remote IP is 127.0.0.1 // Err1(): code=-32000 msg="some issue" data=<nil> // Err2(): code=-32603 msg="bad HTTP Status: 415 Unsupported Media Type" data=<nil> // Err3(): code=42 msg="some issue" data=[one two] }
func TestHTTPServer(t *testing.T) { const jBad = `{}` const jSum = `{"jsonrpc":"2.0","id":0,"method":"Svc.Sum","params":[3,5]}` const jNotify = `{"jsonrpc":"2.0","method":"Svc.Sum","params":[3,5]}` const jRes = `{"jsonrpc":"2.0","id":0,"result":8}` const jErr = `{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid request"}}` const jParse = `{"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error"}}` const contentType = "application/json" cases := []struct { method string contentType string accept string body string code int reply string }{ {"GET", "", "", "", http.StatusMethodNotAllowed, ""}, {"POST", contentType, "", jSum, http.StatusUnsupportedMediaType, ""}, {"POST", "text/json", contentType, jSum, http.StatusUnsupportedMediaType, ""}, {"PUT", contentType, contentType, jSum, http.StatusMethodNotAllowed, ""}, {"POST", contentType, contentType, jNotify, http.StatusNoContent, ""}, {"POST", contentType, contentType, jSum, http.StatusOK, jRes}, {"POST", contentType, contentType, jBad, http.StatusOK, jErr}, {"POST", contentType, contentType, "", http.StatusOK, jParse}, {"POST", contentType, contentType, " ", http.StatusOK, jParse}, {"POST", contentType, contentType, "{", http.StatusOK, jParse}, {"POST", contentType, contentType, `{"jsonrpc":"2.0",`, http.StatusOK, jParse}, } ts := httptest.NewServer(jsonrpc2.HTTPHandler(nil)) // Don't close because of https://github.com/golang/go/issues/12262 // defer ts.Close() for _, c := range cases { req, err := http.NewRequest(c.method, ts.URL, strings.NewReader(c.body)) if err != nil { t.Errorf("NewRequest(%s %s), err = %v", c.method, ts.URL, err) } if c.contentType != "" { req.Header.Add("Content-Type", c.contentType) } if c.accept != "" { req.Header.Add("Accept", c.accept) } resp, err := (&http.Client{}).Do(req) if err != nil { t.Errorf("Do(%s %s), err = %v", c.method, ts.URL, err) } if resp.StatusCode != c.code { t.Errorf("Do(%s %s), status = %v, want = %v", c.method, ts.URL, resp.StatusCode, c.code) } if resp.Header.Get("Content-Type") != contentType { t.Errorf("Do(%s %s), Content-Type = %q, want = %q", c.method, ts.URL, resp.Header.Get("Content-Type"), contentType) } got, err := ioutil.ReadAll(resp.Body) if err != nil { t.Errorf("ReadAll(), err = %v", err) } if c.reply == "" { if len(got) != 0 { t.Errorf("Do(%s %s)\nexp: %#q\ngot: %#q", c.method, ts.URL, c.reply, string(bytes.TrimRight(got, "\n"))) } } else { var jgot, jwant interface{} if err := json.Unmarshal(got, &jgot); err != nil { t.Errorf("Do(%s %s), output err = %v\ngot: %#q", c.method, ts.URL, err, string(bytes.TrimRight(got, "\n"))) } if err := json.Unmarshal([]byte(c.reply), &jwant); err != nil { t.Errorf("Do(%s %s), expect err = %v\nexp: %#q", c.method, ts.URL, err, c.reply) } if !reflect.DeepEqual(jgot, jwant) { t.Errorf("Do(%s %s)\nexp: %#q\ngot: %#q", c.method, ts.URL, c.reply, string(bytes.TrimRight(got, "\n"))) } } } }