From 520888a9b61ee5b8d6c2a559d7334b3f75430137 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Sat, 26 Feb 2022 09:19:30 -0800 Subject: [PATCH] Remove support for parameter injection. (#82) This feature adds a lot of code for relatively little benefit. In practice, a service can achieve the same effect by embedding common headers in its argument types, which gives more control to both client and server without the overhead of an additional encoding step. - Remove the EncodeContext and DecodeContext options. - Remove the plumbing to encode (client) and decode (server) contexts. - Remove the jctx package. - Update usage in the jcall command-line tool. - Clean up tests. --- bench_test.go | 33 ++------- client.go | 10 +-- jctx/example_test.go | 121 --------------------------------- jctx/jctx.go | 149 ----------------------------------------- jctx/jctx_test.go | 155 ------------------------------------------- jhttp/getter_test.go | 5 +- jhttp/jhttp_test.go | 11 +-- jrpc2_test.go | 31 --------- opts.go | 36 ---------- server.go | 17 ++--- tools/jcall/jcall.go | 21 +----- 11 files changed, 15 insertions(+), 574 deletions(-) delete mode 100644 jctx/example_test.go delete mode 100644 jctx/jctx.go delete mode 100644 jctx/jctx_test.go diff --git a/bench_test.go b/bench_test.go index c1e4c1ba..b16a5004 100644 --- a/bench_test.go +++ b/bench_test.go @@ -10,7 +10,6 @@ import ( "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/handler" - "github.com/creachadair/jrpc2/jctx" "github.com/creachadair/jrpc2/server" "github.com/fortytw2/leaktest" ) @@ -23,37 +22,17 @@ func BenchmarkRoundTrip(b *testing.B) { return nil, nil }), } - ctxClient := &jrpc2.ClientOptions{EncodeContext: jctx.Encode} tests := []struct { desc string cli *jrpc2.ClientOptions srv *jrpc2.ServerOptions }{ - {"C01-CTX-B", nil, &jrpc2.ServerOptions{DisableBuiltin: true, Concurrency: 1}}, - {"C01-CTX+B", nil, &jrpc2.ServerOptions{Concurrency: 1}}, - {"C04-CTX-B", nil, &jrpc2.ServerOptions{DisableBuiltin: true, Concurrency: 4}}, - {"C04-CTX+B", nil, &jrpc2.ServerOptions{Concurrency: 4}}, - {"C12-CTX-B", nil, &jrpc2.ServerOptions{DisableBuiltin: true, Concurrency: 12}}, - {"C12-CTX+B", nil, &jrpc2.ServerOptions{Concurrency: 12}}, - - {"C01+CTX-B", ctxClient, - &jrpc2.ServerOptions{DecodeContext: jctx.Decode, DisableBuiltin: true, Concurrency: 1}, - }, - {"C01+CTX+B", ctxClient, - &jrpc2.ServerOptions{DecodeContext: jctx.Decode, Concurrency: 1}, - }, - {"C04+CTX-B", ctxClient, - &jrpc2.ServerOptions{DecodeContext: jctx.Decode, DisableBuiltin: true, Concurrency: 4}, - }, - {"C04+CTX+B", ctxClient, - &jrpc2.ServerOptions{DecodeContext: jctx.Decode, Concurrency: 4}, - }, - {"C12+CTX-B", ctxClient, - &jrpc2.ServerOptions{DecodeContext: jctx.Decode, DisableBuiltin: true, Concurrency: 4}, - }, - {"C12+CTX+B", ctxClient, - &jrpc2.ServerOptions{DecodeContext: jctx.Decode, Concurrency: 12}, - }, + {"C01-B", nil, &jrpc2.ServerOptions{DisableBuiltin: true, Concurrency: 1}}, + {"C01+B", nil, &jrpc2.ServerOptions{Concurrency: 1}}, + {"C04-B", nil, &jrpc2.ServerOptions{DisableBuiltin: true, Concurrency: 4}}, + {"C04+B", nil, &jrpc2.ServerOptions{Concurrency: 4}}, + {"C12-B", nil, &jrpc2.ServerOptions{DisableBuiltin: true, Concurrency: 12}}, + {"C12+B", nil, &jrpc2.ServerOptions{Concurrency: 12}}, } for _, test := range tests { b.Run(test.desc, func(b *testing.B) { diff --git a/client.go b/client.go index b0f66557..100b133c 100644 --- a/client.go +++ b/client.go @@ -20,7 +20,6 @@ type Client struct { done *sync.WaitGroup // done when the reader is finished at shutdown time log func(string, ...interface{}) // write debug logs here - enctx encoder snote func(*jmessage) scall func(context.Context, *jmessage) []byte chook func(*Client, *Response) @@ -41,7 +40,6 @@ func NewClient(ch channel.Channel, opts *ClientOptions) *Client { c := &Client{ done: new(sync.WaitGroup), log: opts.logFunc(), - enctx: opts.encodeContext(), snote: opts.handleNotification(), scall: opts.handleCallback(), chook: opts.handleCancel(), @@ -421,7 +419,7 @@ func (c *Client) stop(err error) { // value of params must be either nil or encodable as a JSON object or array. func (c *Client) marshalParams(ctx context.Context, method string, params interface{}) (json.RawMessage, error) { if params == nil { - return c.enctx(ctx, method, nil) // no parameters, that is OK + return nil, nil // no parameters, that is OK } pbits, err := json.Marshal(params) if err != nil { @@ -432,11 +430,7 @@ func (c *Client) marshalParams(ctx context.Context, method string, params interf // an array or an object. return nil, &Error{Code: code.InvalidRequest, Message: "invalid parameters: array or object required"} } - bits, err := c.enctx(ctx, method, pbits) - if err != nil { - return nil, err - } - return bits, err + return pbits, nil } func newPending(ctx context.Context, id string) (context.Context, *Response) { diff --git a/jctx/example_test.go b/jctx/example_test.go deleted file mode 100644 index 25ff5f6d..00000000 --- a/jctx/example_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved. - -package jctx_test - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "log" - "time" - - "github.com/creachadair/jrpc2/jctx" -) - -func ExampleEncode_basic() { - ctx := context.Background() - enc, err := jctx.Encode(ctx, "methodName", json.RawMessage(`[1,2,3]`)) - if err != nil { - log.Fatalln("Encode:", err) - } - fmt.Println(string(enc)) - // Output: - // {"jctx":"1","payload":[1,2,3]} -} - -func ExampleEncode_deadline() { - deadline := time.Date(2018, 6, 9, 20, 45, 33, 1, time.UTC) - - ctx, cancel := context.WithDeadline(context.Background(), deadline) - defer cancel() - - enc, err := jctx.Encode(ctx, "methodName", json.RawMessage(`{"A":"#1"}`)) - if err != nil { - log.Fatalln("Encode:", err) - } - fmt.Println(pretty(enc)) - // Output: - // { - // "jctx": "1", - // "deadline": "2018-06-09T20:45:33.000000001Z", - // "payload": { - // "A": "#1" - // } - // } -} - -func ExampleDecode() { - const input = `{"jctx":"1","deadline":"2018-06-09T20:45:33.000000001Z","payload":["a", "b", "c"]}` - - ctx, param, err := jctx.Decode(context.Background(), "methodName", json.RawMessage(input)) - if err != nil { - log.Fatalln("Decode:", err) - } - dl, ok := ctx.Deadline() - - fmt.Println("params:", string(param)) - fmt.Println("deadline:", ok, dl) - // Output: - // params: ["a", "b", "c"] - // deadline: true 2018-06-09 20:45:33.000000001 +0000 UTC -} - -func ExampleWithMetadata() { - type Meta struct { - User string `json:"user"` - UUID string `json:"uuid"` - } - ctx, err := jctx.WithMetadata(context.Background(), &Meta{ - User: "Jon Snow", - UUID: "28EF40F5-77C9-4744-B5BD-3ADCD1C15141", - }) - if err != nil { - log.Fatalln("WithMetadata:", err) - } - - enc, err := jctx.Encode(ctx, "methodName", nil) - if err != nil { - log.Fatal("Encode:", err) - } - fmt.Println(pretty(enc)) - // Output: - // { - // "jctx": "1", - // "meta": { - // "user": "Jon Snow", - // "uuid": "28EF40F5-77C9-4744-B5BD-3ADCD1C15141" - // } - // } -} - -func ExampleUnmarshalMetadata() { - // Setup for the example... - const input = `{"user":"Jon Snow","info":"MjhFRjQwRjUtNzdDOS00NzQ0LUI1QkQtM0FEQ0QxQzE1MTQx"}` - ctx, err := jctx.WithMetadata(context.Background(), json.RawMessage(input)) - if err != nil { - log.Fatalln("Setup:", err) - } - - // Demonstrates how to decode the value back. - var meta struct { - User string `json:"user"` - Info []byte `json:"info"` - } - if err := jctx.UnmarshalMetadata(ctx, &meta); err != nil { - log.Fatalln("UnmarshalMetadata:", err) - } - fmt.Println("user:", meta.User) - fmt.Println("info:", string(meta.Info)) - // Output: - // user: Jon Snow - // info: 28EF40F5-77C9-4744-B5BD-3ADCD1C15141 -} - -func pretty(v []byte) string { - var buf bytes.Buffer - if err := json.Indent(&buf, v, "", " "); err != nil { - log.Fatal(err) - } - return buf.String() -} diff --git a/jctx/jctx.go b/jctx/jctx.go deleted file mode 100644 index e1edb220..00000000 --- a/jctx/jctx.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved. - -// Package jctx implements an encoder and decoder for request context values, -// allowing context metadata to be propagated through JSON-RPC. -// -// A context.Context value carries request-scoped values across API boundaries -// and between processes. The jrpc2 package has hooks to allow clients and -// servers to propagate context values transparently through JSON-RPC calls. -// The jctx package provides functions that implement these hooks. -// -// The jrpc2 context plumbing works by injecting a wrapper message around the -// request parameters. The client adds this wrapper during the call, and the -// server removes it. The actual client parameters are embedded inside the -// wrapper unmodified. -// -// The format of the wrapper generated by this package is: -// -// { -// "jctx": "1", -// "payload": , -// "deadline": , -// "meta": -// } -// -// Of these, only the "jctx" marker is required; the others are assumed to be -// empty if they do not appear in the message. -// -// Deadlines and Timeouts -// -// If the parent context contains a deadline, it is encoded into the wrapper as -// an RFC 3339 timestamp in UTC, for example "2009-11-10T23:00:00.00000015Z". -// -// Metadata -// -// The jctx.WithMetadata function allows the caller to attach an arbitrary -// JSON-encoded value to a context. This value will be transmitted over the -// wire during a JSON-RPC call. The recipient can decode this value from the -// context using the jctx.UnmarshalMetadata function. -// -package jctx - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "time" -) - -const wireVersion = "1" - -// wireContext is the encoded representation of a context value. It includes -// the deadline together with an underlying payload carrying the original -// request parameters. The resulting message replaces the parameters of the -// original JSON-RPC request. -type wireContext struct { - V *string `json:"jctx"` // must be wireVersion - - Deadline *time.Time `json:"deadline,omitempty"` // encoded in UTC - Payload json.RawMessage `json:"payload,omitempty"` - Metadata json.RawMessage `json:"meta,omitempty"` -} - -// Encode encodes the specified context and request parameters for transmission. -// If a deadline is set on ctx, it is converted to UTC before encoding. -// If metadata are set on ctx (see jctx.WithMetadata), they are included. -func Encode(ctx context.Context, method string, params json.RawMessage) (json.RawMessage, error) { - v := wireVersion - c := wireContext{V: &v, Payload: params} - if dl, ok := ctx.Deadline(); ok { - utcdl := dl.UTC() - c.Deadline = &utcdl - } - - // If there are metadata in the context, attach them. - if v := ctx.Value(metadataKey{}); v != nil { - c.Metadata = v.(json.RawMessage) - } - - return json.Marshal(c) -} - -// Decode decodes the specified request message as a context-wrapped request, -// and returns the updated context (based on ctx) and the embedded parameters. -// If the request does not have a context wrapper, it is returned as-is. -// -// If the encoded request specifies a deadline, that deadline is set in the -// context value returned. -// -// If the request includes context metadata, they are attached and can be -// recovered using jctx.UnmarshalMetadata. -func Decode(ctx context.Context, method string, req json.RawMessage) (context.Context, json.RawMessage, error) { - if len(req) == 0 || req[0] != '{' { - return ctx, req, nil // an empty message or non-object has no wrapper - } - var c wireContext - if err := json.Unmarshal(req, &c); err != nil || c.V == nil { - return ctx, req, nil // fall back assuming an un-wrapped message - } else if *c.V != wireVersion { - return nil, nil, fmt.Errorf("invalid context version %q", *c.V) - } - if c.Metadata != nil { - ctx = context.WithValue(ctx, metadataKey{}, c.Metadata) - } - if c.Deadline != nil && !c.Deadline.IsZero() { - var ignored context.CancelFunc - ctx, ignored = context.WithDeadline(ctx, (*c.Deadline).UTC()) - _ = ignored // the caller cannot use this value - } - - return ctx, c.Payload, nil -} - -type metadataKey struct{} - -// WithMetadata attaches the specified metadata value to the context. The meta -// value must support encoding to JSON. In case of error, the original value of -// ctx is returned along with the error. If meta == nil, the resulting context -// has no metadata attached; this can be used to remove metadata from a context -// that has it. -func WithMetadata(ctx context.Context, meta interface{}) (context.Context, error) { - if meta == nil { - // Note we explicitly attach a value even if meta == nil, since ctx might - // already have metadata so we need to mask it. - return context.WithValue(ctx, metadataKey{}, json.RawMessage(nil)), nil - } - bits, err := json.Marshal(meta) - if err != nil { - return ctx, err - } - return context.WithValue(ctx, metadataKey{}, json.RawMessage(bits)), nil -} - -// UnmarshalMetadata decodes the metadata value attached to ctx into meta, or -// returns ErrNoMetadata if ctx does not have metadata attached. -func UnmarshalMetadata(ctx context.Context, meta interface{}) error { - if v := ctx.Value(metadataKey{}); v != nil { - // If the metadata value is explicitly nil, we should report that there - // is no metadata message. - if msg := v.(json.RawMessage); msg != nil { - return json.Unmarshal(msg, meta) - } - } - return ErrNoMetadata -} - -// ErrNoMetadata is returned by the UnmarshalMetadata function if the context -// does not contain a metadata value. -var ErrNoMetadata = errors.New("context metadata not present") diff --git a/jctx/jctx_test.go b/jctx/jctx_test.go deleted file mode 100644 index 5f6161ae..00000000 --- a/jctx/jctx_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved. - -package jctx - -import ( - "context" - "encoding/json" - "testing" - "time" -) - -var bicent = time.Date(1976, 7, 4, 1, 2, 3, 4, time.UTC) - -func TestEncoding(t *testing.T) { - tests := []struct { - desc string - deadline time.Time - params, want string - }{ - {"zero-void", time.Time{}, "", `{"jctx":"1"}`}, - - {"zero-payload", time.Time{}, - "[1,2,3]", `{"jctx":"1","payload":[1,2,3]}`}, - - {"bicentennial-void", bicent.In(time.Local), - "", `{"jctx":"1","deadline":"1976-07-04T01:02:03.000000004Z"}`, - }, - - {"bicentennial-payload", bicent, - `{"apple":"pear"}`, - `{"jctx":"1","deadline":"1976-07-04T01:02:03.000000004Z","payload":{"apple":"pear"}}`, - }, - } - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - ctx := context.Background() - if !test.deadline.IsZero() { - var cancel context.CancelFunc - ctx, cancel = context.WithDeadline(ctx, test.deadline) - defer cancel() - } - raw, err := Encode(ctx, "dummy", json.RawMessage(test.params)) - if err != nil { - t.Errorf("Encoding %q failed: %v", test.params, err) - } else if got := string(raw); got != test.want { - t.Errorf("Encoding %q: got %#q, want %#q", test.params, got, test.want) - } - }) - } -} - -func TestDecoding(t *testing.T) { - tests := []struct { - desc, input string - deadline time.Time - want string - }{ - {"empty context", "", time.Time{}, ""}, - {"empty parameters", `{"jctx":"1"}`, time.Time{}, ""}, - {"non-object input", `[1, 5]`, time.Time{}, `[1, 5]`}, - {"non-context empty object", `{}`, time.Time{}, `{}`}, - {"non-context object", `{"kiss":"me"}`, time.Time{}, `{"kiss":"me"}`}, - {"invalid context", `{"jctx":2, "ok":true}`, time.Time{}, `{"jctx":2, "ok":true}`}, - - {"zero-payload", `{"jctx":"1","payload":["a","b","c"]}`, time.Time{}, `["a","b","c"]`}, - {"zero-payload-naked", `["a", "b", "c"]`, time.Time{}, `["a", "b", "c"]`}, - - {"bicentennial-void", `{"jctx":"1","deadline":"1976-07-04T01:02:03.000000004Z"}`, bicent, ""}, - - {"bicentennial-payload", `{ -"jctx":"1", -"deadline":"1976-07-04T01:02:03.000000004Z", -"payload":{"lhs":1,"rhs":2} -}`, bicent, `{"lhs":1,"rhs":2}`}, - } - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - ctx := context.Background() - gotctx, params, err := Decode(ctx, "dummy", json.RawMessage(test.input)) - if err != nil { - t.Fatalf("Decode(_, %q): error: %v", test.input, err) - } - if !test.deadline.IsZero() { - dl, ok := gotctx.Deadline() - if !ok { - t.Error("Decode: missing expected deadline") - } else if !dl.Equal(test.deadline) { - t.Errorf("Decode deadline: got %v, want %v", dl, test.deadline) - } - } - if got := string(params); got != test.want { - t.Errorf("Decode params: got %#q, want %#q", got, test.want) - } - }) - } -} - -func TestMetadata(t *testing.T) { - type value struct { - Name string `json:"name,omitempty"` - Marbles int `json:"marbles,omitempty"` - } - input := value{Name: "Hieronymus Bosch", Marbles: 3} - - base := context.Background() - ctx, err := WithMetadata(base, input) - if err != nil { - t.Fatalf("WithMetadata(base, %+v) failed: %v", input, err) - } - - var output value - - // The base value does not contain the value. - if err := UnmarshalMetadata(base, &output); err != ErrNoMetadata { - t.Logf("Base metadata decoded value: %+v", output) - t.Errorf("UnmarshalMetadata(base): got error %v, want %v", err, ErrNoMetadata) - } - - // The attached context does contain the value (prior to transmission). - output = value{} - if err := UnmarshalMetadata(ctx, &output); err != nil { - t.Errorf("UnmarshalMetadata(ctx): unexpected error: %v", err) - } else if output != input { - t.Errorf("UnmarshalMetadata(ctx): got %+v, want %+v", output, input) - } - - // Simulate transmission -- encode, then decode. - var dec context.Context - if enc, err := Encode(ctx, "dummy", nil); err != nil { - t.Fatalf("Encoding context failed: %v", err) - } else { - t.Logf("Encoded context is: %#q", string(enc)) - if dec, _, err = Decode(base, "dummy", enc); err != nil { - t.Fatalf("Decoding context failed: %v", err) - } - } - - // The decoded context does contain the value (after receipt). - output = value{} - if err := UnmarshalMetadata(dec, &output); err != nil { - t.Errorf("Metadata(dec): unexpected error: %v", err) - } else if output != input { - t.Errorf("Metadata(dec): got %+v, want %+v", output, input) - } - - // "Attaching" nil removes the metadata. - clr, err := WithMetadata(ctx, nil) - if err != nil { - t.Fatalf("WithMetadata(ctx, nil): unexpected error: %v", err) - } - var bad interface{} - if err := UnmarshalMetadata(clr, &bad); err != ErrNoMetadata { - t.Errorf("Metadata(clr): got %+v, %v; want %v", bad, err, ErrNoMetadata) - } -} diff --git a/jhttp/getter_test.go b/jhttp/getter_test.go index 98ceb7dd..29e8fca3 100644 --- a/jhttp/getter_test.go +++ b/jhttp/getter_test.go @@ -12,7 +12,6 @@ import ( "strings" "testing" - "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/handler" "github.com/creachadair/jrpc2/jhttp" "github.com/fortytw2/leaktest" @@ -28,9 +27,7 @@ func TestGetter(t *testing.T) { }, "first", "second"), } - g := jhttp.NewGetter(mux, &jhttp.GetterOptions{ - Client: &jrpc2.ClientOptions{EncodeContext: checkContext}, - }) + g := jhttp.NewGetter(mux, nil) defer checkClose(t, g) hsrv := httptest.NewServer(g) diff --git a/jhttp/jhttp_test.go b/jhttp/jhttp_test.go index ef9cbc47..e625391f 100644 --- a/jhttp/jhttp_test.go +++ b/jhttp/jhttp_test.go @@ -27,20 +27,11 @@ var testService = handler.Map{ }), } -func checkContext(ctx context.Context, _ string, p json.RawMessage) (json.RawMessage, error) { - if jhttp.HTTPRequest(ctx) == nil { - return nil, errors.New("no HTTP request in context") - } - return p, nil -} - func TestBridge(t *testing.T) { defer leaktest.Check(t)() // Set up a bridge with the test configuration. - b := jhttp.NewBridge(testService, &jhttp.BridgeOptions{ - Client: &jrpc2.ClientOptions{EncodeContext: checkContext}, - }) + b := jhttp.NewBridge(testService, nil) defer checkClose(t, b) // Create an HTTP test server to call into the bridge. diff --git a/jrpc2_test.go b/jrpc2_test.go index 061dd603..430d7ff3 100644 --- a/jrpc2_test.go +++ b/jrpc2_test.go @@ -17,7 +17,6 @@ import ( "github.com/creachadair/jrpc2/channel" "github.com/creachadair/jrpc2/code" "github.com/creachadair/jrpc2/handler" - "github.com/creachadair/jrpc2/jctx" "github.com/creachadair/jrpc2/server" "github.com/fortytw2/leaktest" "github.com/google/go-cmp/cmp" @@ -1061,36 +1060,6 @@ func TestClient_callbackUpCall(t *testing.T) { } } -// Verify that the context encoding/decoding hooks work. -func TestContextPlumbing(t *testing.T) { - defer leaktest.Check(t)() - - want := time.Now().Add(10 * time.Second) - ctx, cancel := context.WithDeadline(context.Background(), want) - defer cancel() - - loc := server.NewLocal(handler.Map{ - "X": handler.New(func(ctx context.Context) (bool, error) { - got, ok := ctx.Deadline() - if !ok { - return false, errors.New("no deadline was set") - } else if !got.Equal(want) { - return false, fmt.Errorf("deadline: got %v, want %v", got, want) - } - t.Logf("Got expected deadline: %v", got) - return true, nil - }), - }, &server.LocalOptions{ - Server: &jrpc2.ServerOptions{DecodeContext: jctx.Decode}, - Client: &jrpc2.ClientOptions{EncodeContext: jctx.Encode}, - }) - defer loc.Close() - - if _, err := loc.Client.Call(ctx, "X", nil); err != nil { - t.Errorf("Call X failed: %v", err) - } -} - // Verify that calling a wrapped method which takes no parameters, but in which // the caller provided parameters, will correctly report an error. func TestHandler_noParams(t *testing.T) { diff --git a/opts.go b/opts.go index 0793f8a8..2ec66fa5 100644 --- a/opts.go +++ b/opts.go @@ -46,13 +46,6 @@ type ServerOptions struct { // If unset, the server uses a background context. NewContext func() context.Context - // If set, this function is called with the method name and encoded request - // parameters received from the client, before they are delivered to the - // handler. Its return value replaces the context and argument values. This - // allows the server to decode context metadata sent by the client. - // If unset, context and parameters are used as given. - DecodeContext func(context.Context, string, json.RawMessage) (context.Context, json.RawMessage, error) - // If set, use this value to record server metrics. All servers created // from the same options will share the same metrics collector. If none is // set, an empty collector will be created for each new server. @@ -95,17 +88,6 @@ func (o *ServerOptions) newContext() func() context.Context { return o.NewContext } -type decoder = func(context.Context, string, json.RawMessage) (context.Context, json.RawMessage, error) - -func (s *ServerOptions) decodeContext() decoder { - if s == nil || s.DecodeContext == nil { - return func(ctx context.Context, method string, params json.RawMessage) (context.Context, json.RawMessage, error) { - return ctx, params, nil - } - } - return s.DecodeContext -} - func (s *ServerOptions) metrics() *metrics.M { if s == nil || s.Metrics == nil { return metrics.New() @@ -126,13 +108,6 @@ type ClientOptions struct { // If not nil, send debug text logs here. Logger Logger - // If set, this function is called with the context, method name, and - // encoded request parameters before the request is sent to the server. - // Its return value replaces the request parameters. This allows the client - // to send context metadata along with the request. If unset, the parameters - // are unchanged. - EncodeContext func(context.Context, string, json.RawMessage) (json.RawMessage, error) - // If set, this function is called if a notification is received from the // server. If unset, server notifications are logged and discarded. At // most one invocation of the callback will be active at a time. @@ -169,17 +144,6 @@ func (c *ClientOptions) logFunc() func(string, ...interface{}) { return c.Logger.Printf } -type encoder = func(context.Context, string, json.RawMessage) (json.RawMessage, error) - -func (c *ClientOptions) encodeContext() encoder { - if c == nil || c.EncodeContext == nil { - return func(_ context.Context, methods string, params json.RawMessage) (json.RawMessage, error) { - return params, nil - } - } - return c.EncodeContext -} - func (c *ClientOptions) handleNotification() func(*jmessage) { if c == nil || c.OnNotify == nil { return nil diff --git a/server.go b/server.go index f4d275a2..27367582 100644 --- a/server.go +++ b/server.go @@ -31,7 +31,6 @@ type Server struct { log func(string, ...interface{}) // write debug logs here rpcLog RPCLogger // log RPC requests and responses here newctx func() context.Context // create a new base request context - dectx decoder // decode context from request metrics *metrics.M // metrics collected during execution start time.Time // when Start was called builtin bool // whether built-in rpc.* methods are enabled @@ -72,7 +71,6 @@ func NewServer(mux Assigner, opts *ServerOptions) *Server { log: opts.logFunc(), rpcLog: opts.rpcLog(), newctx: opts.newContext(), - dectx: opts.decodeContext(), mu: new(sync.Mutex), metrics: opts.metrics(), start: opts.startTime(), @@ -314,7 +312,8 @@ func (s *Server) checkAndAssign(next jmessages) tasks { // deferred validation error } else if t.hreq.method == "" { t.err = errEmptyMethod - } else if s.setContext(t, id) { + } else { + s.setContext(t, id) t.m = s.assign(t.ctx, t.hreq.method) if t.m == nil { t.err = errNoSuchMethod.WithData(t.hreq.method) @@ -332,15 +331,8 @@ func (s *Server) checkAndAssign(next jmessages) tasks { // setContext constructs and attaches a request context to t, and reports // whether this succeeded. -func (s *Server) setContext(t *task, id string) bool { - base, params, err := s.dectx(s.newctx(), t.hreq.method, t.hreq.params) - t.hreq.params = params - if err != nil { - t.err = Errorf(code.InternalError, "invalid request context: %v", err) - return false - } - - t.ctx = context.WithValue(base, inboundRequestKey{}, t.hreq) +func (s *Server) setContext(t *task, id string) { + t.ctx = context.WithValue(s.newctx(), inboundRequestKey{}, t.hreq) // Store the cancellation for a request that needs a reply, so that we can // respond to cancellation requests. @@ -349,7 +341,6 @@ func (s *Server) setContext(t *task, id string) bool { s.used[id] = cancel t.ctx = ctx } - return true } // invoke invokes the handler m for the specified request type, and marshals diff --git a/tools/jcall/jcall.go b/tools/jcall/jcall.go index f4d64a78..9438d1f8 100644 --- a/tools/jcall/jcall.go +++ b/tools/jcall/jcall.go @@ -24,7 +24,6 @@ import ( "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/channel" - "github.com/creachadair/jrpc2/jctx" "github.com/creachadair/jrpc2/jhttp" "github.com/creachadair/wschannel" ) @@ -33,7 +32,6 @@ var ( dialTimeout = flag.Duration("dial", 5*time.Second, "Timeout on dialing the server (0 for no timeout)") callTimeout = flag.Duration("timeout", 0, "Timeout on each call (0 for no timeout)") doNotify = flag.Bool("notify", false, "Send a notification") - withContext = flag.Bool("c", false, "Send context with request") chanFraming = flag.String("f", envOrDefault("JCALL_FRAMING", "line"), "Channel framing") doBatch = flag.Bool("batch", false, "Issue calls as a batch rather than sequentially") doErrors = flag.Bool("e", false, "Print error values to stdout") @@ -42,7 +40,6 @@ var ( doTiming = flag.Bool("T", false, "Print call timing stats") doWaitExit = flag.Bool("W", false, "Wait for interrupt at exit") withLogging = flag.Bool("v", false, "Enable verbose logging") - withMeta = flag.String("meta", "", "Attach this JSON value as request metadata (implies -c)") ) func init() { @@ -97,21 +94,8 @@ func main() { log.Fatal("Arguments are
{ }...") } - // Set up the context for the call, including timeouts and any metadata that - // are specified on the command line. Setting -meta also implicitly sets -c. + // Set up the context for the call, including a timeouts if specified. ctx := context.Background() - if *withMeta == "" { - *withMeta = os.Getenv("JCALL_META") - } - if *withMeta != "" { - mc, err := jctx.WithMetadata(ctx, json.RawMessage(*withMeta)) - if err != nil { - log.Fatalf("Invalid request metadata: %v", err) - } - ctx = mc - *withContext = true - } - if *callTimeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, *callTimeout) @@ -180,9 +164,6 @@ func newClient(conn channel.Channel) *jrpc2.Client { fmt.Printf(`{"method":%q,"params":%s}`+"\n", req.Method(), string(p)) }, } - if *withContext { - opts.EncodeContext = jctx.Encode - } if *withLogging { opts.Logger = jrpc2.StdLogger(nil) }