예제 #1
0
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)
	}
}
예제 #2
0
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)
}
예제 #3
0
// - 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)
		}
	}
}
예제 #4
0
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]
}
예제 #5
0
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")))
			}
		}
	}
}