30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/v2/json2/error.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json2
7 |
8 | import (
9 | "errors"
10 | )
11 |
12 | type ErrorCode int
13 |
14 | const (
15 | E_PARSE ErrorCode = -32700
16 | E_INVALID_REQ ErrorCode = -32600
17 | E_NO_METHOD ErrorCode = -32601
18 | E_BAD_PARAMS ErrorCode = -32602
19 | E_INTERNAL ErrorCode = -32603
20 | E_SERVER ErrorCode = -32000
21 | )
22 |
23 | var ErrNullResult = errors.New("result is null")
24 |
25 | type Error struct {
26 | // A Number that indicates the error type that occurred.
27 | Code ErrorCode `json:"code"` /* required */
28 |
29 | // A String providing a short description of the error.
30 | // The message SHOULD be limited to a concise single sentence.
31 | Message string `json:"message"` /* required */
32 |
33 | // A Primitive or Structured value that contains additional information about the error.
34 | Data interface{} `json:"data"` /* optional */
35 | }
36 |
37 | func (e *Error) Error() string {
38 | return e.Message
39 | }
40 |
--------------------------------------------------------------------------------
/v2/encoder_selector.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package rpc
7 |
8 | import (
9 | "io"
10 | "net/http"
11 | )
12 |
13 | // Encoder interface contains the encoder for http response.
14 | // Eg. gzip, flate compressions.
15 | type Encoder interface {
16 | Encode(w http.ResponseWriter) io.Writer
17 | }
18 |
19 | type encoder struct {
20 | }
21 |
22 | func (_ *encoder) Encode(w http.ResponseWriter) io.Writer {
23 | return w
24 | }
25 |
26 | var DefaultEncoder = &encoder{}
27 |
28 | // EncoderSelector interface provides a way to select encoder using the http
29 | // request. Typically people can use this to check HEADER of the request and
30 | // figure out client capabilities.
31 | // Eg. "Accept-Encoding" tells about supported compressions.
32 | type EncoderSelector interface {
33 | Select(r *http.Request) Encoder
34 | }
35 |
36 | type encoderSelector struct {
37 | }
38 |
39 | func (_ *encoderSelector) Select(_ *http.Request) Encoder {
40 | return DefaultEncoder
41 | }
42 |
43 | var DefaultEncoderSelector = &encoderSelector{}
44 |
--------------------------------------------------------------------------------
/protorpc/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | /*
7 | Package gorilla/rpc/protorpc provides a codec for ProtoRPC over HTTP services.
8 |
9 | To register the codec in a RPC server:
10 |
11 | import (
12 | "http"
13 | "github.com/gorilla/rpc"
14 | "github.com/gorilla/rpc/protorpc"
15 | )
16 |
17 | func init() {
18 | s := rpc.NewServer()
19 | s.RegisterCodec(protorpc.NewCodec(), "application/json")
20 | // [...]
21 | http.Handle("/rpc", s)
22 | }
23 |
24 | A codec is tied to a content type. In the example above, the server
25 | will use the ProtoRPC codec for requests with "application/json" as
26 | the value for the "Content-Type" header.
27 |
28 | This package implement ProtoRPC, based on the JSON-RPC transport, it
29 | differs in that it uses HTTP as its envelope.
30 |
31 | Example:
32 | POST /Service.Method
33 | Request:
34 | {
35 | "requestField1": "value1",
36 | "requestField2": "value2",
37 | }
38 | Response:
39 | {
40 | "responseField1": "value1",
41 | "responseField2": "value2",
42 | }
43 |
44 | Check the gorilla/rpc documentation for more details:
45 |
46 | http://gorilla-web.appspot.com/pkg/rpc
47 | */
48 | package protorpc
49 |
--------------------------------------------------------------------------------
/v2/json2/testapp/counter.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2013 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package main
7 |
8 | import (
9 | "flag"
10 | "log"
11 | "net/http"
12 |
13 | "github.com/gorilla/rpc/v2"
14 | "github.com/gorilla/rpc/v2/json2"
15 | )
16 |
17 | type Counter struct {
18 | Count int
19 | }
20 |
21 | type IncrReq struct {
22 | Delta int
23 | }
24 |
25 | // Notification.
26 | func (c *Counter) Incr(r *http.Request, req *IncrReq, res *json2.EmptyResponse) error {
27 | log.Printf("<- Incr %+v", *req)
28 | c.Count += req.Delta
29 | return nil
30 | }
31 |
32 | type GetReq struct {
33 | }
34 |
35 | func (c *Counter) Get(r *http.Request, req *GetReq, res *Counter) error {
36 | log.Printf("<- Get %+v", *req)
37 | *res = *c
38 | log.Printf("-> %v", *res)
39 | return nil
40 | }
41 |
42 | func main() {
43 | address := flag.String("address", ":65534", "")
44 | s := rpc.NewServer()
45 | s.RegisterCodec(json2.NewCustomCodec(&rpc.CompressionSelector{}), "application/json")
46 | s.RegisterService(new(Counter), "")
47 | http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("./"))))
48 | http.Handle("/jsonrpc/", s)
49 | log.Fatal(http.ListenAndServe(*address, nil))
50 | }
51 |
--------------------------------------------------------------------------------
/v2/protorpc/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | /*
7 | Package gorilla/rpc/protorpc provides a codec for ProtoRPC over HTTP services.
8 |
9 | To register the codec in a RPC server:
10 |
11 | import (
12 | "http"
13 | "github.com/gorilla/rpc/v2"
14 | "github.com/gorilla/rpc/v2/protorpc"
15 | )
16 |
17 | func init() {
18 | s := rpc.NewServer()
19 | s.RegisterCodec(protorpc.NewCodec(), "application/json")
20 | // [...]
21 | http.Handle("/rpc", s)
22 | }
23 |
24 | A codec is tied to a content type. In the example above, the server
25 | will use the ProtoRPC codec for requests with "application/json" as
26 | the value for the "Content-Type" header.
27 |
28 | This package implement ProtoRPC, based on the JSON-RPC transport, it
29 | differs in that it uses HTTP as its envelope.
30 |
31 | Example:
32 | POST /Service.Method
33 | Request:
34 | {
35 | "requestField1": "value1",
36 | "requestField2": "value2",
37 | }
38 | Response:
39 | {
40 | "responseField1": "value1",
41 | "responseField2": "value2",
42 | }
43 |
44 | Check the gorilla/rpc documentation for more details:
45 |
46 | http://gorilla-web.appspot.com/pkg/rpc
47 | */
48 | package protorpc
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/v2/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/json/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | /*
7 | Package gorilla/rpc/json provides a codec for JSON-RPC over HTTP services.
8 |
9 | To register the codec in a RPC server:
10 |
11 | import (
12 | "http"
13 | "github.com/gorilla/rpc"
14 | "github.com/gorilla/rpc/json"
15 | )
16 |
17 | func init() {
18 | s := rpc.NewServer()
19 | s.RegisterCodec(json.NewCodec(), "application/json")
20 | // [...]
21 | http.Handle("/rpc", s)
22 | }
23 |
24 | A codec is tied to a content type. In the example above, the server will use
25 | the JSON codec for requests with "application/json" as the value for the
26 | "Content-Type" header.
27 |
28 | This package follows the JSON-RPC 1.0 specification:
29 |
30 | http://json-rpc.org/wiki/specification
31 |
32 | Request format is:
33 |
34 | method:
35 | The name of the method to be invoked, as a string in dotted notation
36 | as in "Service.Method".
37 | params:
38 | An array with a single object to pass as argument to the method.
39 | id:
40 | The request id, a uint. It is used to match the response with the
41 | request that it is replying to.
42 |
43 | Response format is:
44 |
45 | result:
46 | The Object that was returned by the invoked method,
47 | or null in case there was an error invoking the method.
48 | error:
49 | An Error object if there was an error invoking the method,
50 | or null if there was no error.
51 | id:
52 | The same id as the request it is responding to.
53 |
54 | Check the gorilla/rpc documentation for more details:
55 |
56 | http://gorilla-web.appspot.com/pkg/rpc
57 | */
58 | package json
59 |
--------------------------------------------------------------------------------
/v2/json/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | /*
7 | Package gorilla/rpc/json provides a codec for JSON-RPC over HTTP services.
8 |
9 | To register the codec in a RPC server:
10 |
11 | import (
12 | "http"
13 | "github.com/gorilla/rpc/v2"
14 | "github.com/gorilla/rpc/v2/json"
15 | )
16 |
17 | func init() {
18 | s := rpc.NewServer()
19 | s.RegisterCodec(json.NewCodec(), "application/json")
20 | // [...]
21 | http.Handle("/rpc", s)
22 | }
23 |
24 | A codec is tied to a content type. In the example above, the server will use
25 | the JSON codec for requests with "application/json" as the value for the
26 | "Content-Type" header.
27 |
28 | This package follows the JSON-RPC 1.0 specification:
29 |
30 | http://json-rpc.org/wiki/specification
31 |
32 | Request format is:
33 |
34 | method:
35 | The name of the method to be invoked, as a string in dotted notation
36 | as in "Service.Method".
37 | params:
38 | An array with a single object to pass as argument to the method.
39 | id:
40 | The request id, a uint. It is used to match the response with the
41 | request that it is replying to.
42 |
43 | Response format is:
44 |
45 | result:
46 | The Object that was returned by the invoked method,
47 | or null in case there was an error invoking the method.
48 | error:
49 | An Error object if there was an error invoking the method,
50 | or null if there was no error.
51 | id:
52 | The same id as the request it is responding to.
53 |
54 | Check the gorilla/rpc documentation for more details:
55 |
56 | http://gorilla-web.appspot.com/pkg/rpc
57 | */
58 | package json
59 |
--------------------------------------------------------------------------------
/v2/json2/testapp/counter.js:
--------------------------------------------------------------------------------
1 | function log(m, label) {
2 | msg = $("
" + m + "
");
3 | msg.find("span").addClass(label);
4 | out = $("#output");
5 | out.append(msg);
6 | out.animate({"scrollTop": out[0].scrollHeight}, "fast");
7 | }
8 |
9 | $(document).ready(function() {
10 | $("#incr").click(function() {
11 | req = {
12 | method : "Counter.Incr",
13 | params : {delta: 1},
14 | };
15 | log("<- " + JSON.stringify(req), "secondary label");
16 | $.jsonrpc(req);
17 | });
18 | $("#get").click(function() {
19 | req = {
20 | method : "Counter.Get",
21 | params : {},
22 | };
23 | log("<- " + JSON.stringify(req), "label");
24 | $.jsonrpc(req, {
25 | success : function(result) {
26 | $("#get").addClass("success");
27 | setTimeout(function() {
28 | $("#get").removeClass("success");
29 | }, 2000);
30 | log("-> " + JSON.stringify(result), "success label");
31 | },
32 | error : function(error) {
33 | $("#get").addClass("alert");
34 | setTimeout(function() {
35 | $("#get").removeClass("alert");
36 | }, 2000);
37 | log("-> " + JSON.stringify(error), "alert label");
38 | },
39 | });
40 | });
41 | $("#nan").click(function() {
42 | req = {
43 | method : "Counter.Nan",
44 | params : {},
45 | };
46 | log("<- " + JSON.stringify(req), "label");
47 | $.jsonrpc(req, {
48 | success : function(result) {
49 | $("#nan").addClass("success");
50 | setTimeout(function() {
51 | $("#nan").removeClass("success");
52 | }, 2000);
53 | log("-> " + JSON.stringify(result), "success label");
54 | },
55 | error : function(error) {
56 | $("#nan").addClass("alert");
57 | setTimeout(function() {
58 | $("#nan").removeClass("alert");
59 | }, 2000);
60 | log("-> " + JSON.stringify(error), "alert label");
61 | },
62 | });
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/v2/json/client.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012-2013 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json
7 |
8 | import (
9 | "encoding/json"
10 | "fmt"
11 | "io"
12 | "math/rand"
13 | )
14 |
15 | // ----------------------------------------------------------------------------
16 | // Request and Response
17 | // ----------------------------------------------------------------------------
18 |
19 | // clientRequest represents a JSON-RPC request sent by a client.
20 | type clientRequest struct {
21 | // A String containing the name of the method to be invoked.
22 | Method string `json:"method"`
23 | // Object to pass as request parameter to the method.
24 | Params [1]interface{} `json:"params"`
25 | // The request id. This can be of any type. It is used to match the
26 | // response with the request that it is replying to.
27 | Id uint64 `json:"id"`
28 | }
29 |
30 | // clientResponse represents a JSON-RPC response returned to a client.
31 | type clientResponse struct {
32 | Result *json.RawMessage `json:"result"`
33 | Error interface{} `json:"error"`
34 | Id uint64 `json:"id"`
35 | }
36 |
37 | // EncodeClientRequest encodes parameters for a JSON-RPC client request.
38 | func EncodeClientRequest(method string, args interface{}) ([]byte, error) {
39 | c := &clientRequest{
40 | Method: method,
41 | Params: [1]interface{}{args},
42 | Id: uint64(rand.Int63()),
43 | }
44 | return json.Marshal(c)
45 | }
46 |
47 | // DecodeClientResponse decodes the response body of a client request into
48 | // the interface reply.
49 | func DecodeClientResponse(r io.Reader, reply interface{}) error {
50 | var c clientResponse
51 | if err := json.NewDecoder(r).Decode(&c); err != nil {
52 | return err
53 | }
54 | if c.Error != nil {
55 | return &Error{Data: c.Error}
56 | }
57 | if c.Result == nil {
58 | return fmt.Errorf("Unexpected null result")
59 | }
60 | return json.Unmarshal(*c.Result, reply)
61 | }
62 |
--------------------------------------------------------------------------------
/json/client.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json
7 |
8 | import (
9 | "encoding/json"
10 | "errors"
11 | "fmt"
12 | "io"
13 | "math/rand"
14 | )
15 |
16 | // ----------------------------------------------------------------------------
17 | // Request and Response
18 | // ----------------------------------------------------------------------------
19 |
20 | // clientRequest represents a JSON-RPC request sent by a client.
21 | type clientRequest struct {
22 | // A String containing the name of the method to be invoked.
23 | Method string `json:"method"`
24 | // Object to pass as request parameter to the method.
25 | Params [1]interface{} `json:"params"`
26 | // The request id. This can be of any type. It is used to match the
27 | // response with the request that it is replying to.
28 | Id uint64 `json:"id"`
29 | }
30 |
31 | // clientResponse represents a JSON-RPC response returned to a client.
32 | type clientResponse struct {
33 | Result *json.RawMessage `json:"result"`
34 | Error interface{} `json:"error"`
35 | Id uint64 `json:"id"`
36 | }
37 |
38 | // EncodeClientRequest encodes parameters for a JSON-RPC client request.
39 | func EncodeClientRequest(method string, args interface{}) ([]byte, error) {
40 | c := &clientRequest{
41 | Method: method,
42 | Params: [1]interface{}{args},
43 | Id: uint64(rand.Int63()),
44 | }
45 | return json.Marshal(c)
46 | }
47 |
48 | // DecodeClientResponse decodes the response body of a client request into
49 | // the interface reply.
50 | func DecodeClientResponse(r io.Reader, reply interface{}) error {
51 | var c clientResponse
52 | if err := json.NewDecoder(r).Decode(&c); err != nil {
53 | return err
54 | }
55 | if c.Error != nil {
56 | return fmt.Errorf("%v", c.Error)
57 | }
58 | if c.Result == nil {
59 | return errors.New("result is null")
60 | }
61 | return json.Unmarshal(*c.Result, reply)
62 | }
63 |
--------------------------------------------------------------------------------
/v2/json2/client.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json2
7 |
8 | import (
9 | "encoding/json"
10 | "io"
11 | "math/rand"
12 | )
13 |
14 | // ----------------------------------------------------------------------------
15 | // Request and Response
16 | // ----------------------------------------------------------------------------
17 |
18 | // clientRequest represents a JSON-RPC request sent by a client.
19 | type clientRequest struct {
20 | // JSON-RPC protocol.
21 | Version string `json:"jsonrpc"`
22 |
23 | // A String containing the name of the method to be invoked.
24 | Method string `json:"method"`
25 |
26 | // Object to pass as request parameter to the method.
27 | Params interface{} `json:"params"`
28 |
29 | // The request id. This can be of any type. It is used to match the
30 | // response with the request that it is replying to.
31 | Id uint64 `json:"id"`
32 | }
33 |
34 | // clientResponse represents a JSON-RPC response returned to a client.
35 | type clientResponse struct {
36 | Version string `json:"jsonrpc"`
37 | Result *json.RawMessage `json:"result"`
38 | Error *json.RawMessage `json:"error"`
39 | }
40 |
41 | // EncodeClientRequest encodes parameters for a JSON-RPC client request.
42 | func EncodeClientRequest(method string, args interface{}) ([]byte, error) {
43 | c := &clientRequest{
44 | Version: "2.0",
45 | Method: method,
46 | Params: args,
47 | Id: uint64(rand.Int63()),
48 | }
49 | return json.Marshal(c)
50 | }
51 |
52 | // DecodeClientResponse decodes the response body of a client request into
53 | // the interface reply.
54 | func DecodeClientResponse(r io.Reader, reply interface{}) error {
55 | var c clientResponse
56 | if err := json.NewDecoder(r).Decode(&c); err != nil {
57 | return err
58 | }
59 | if c.Error != nil {
60 | jsonErr := &Error{}
61 | if err := json.Unmarshal(*c.Error, jsonErr); err != nil {
62 | return &Error{
63 | Code: E_SERVER,
64 | Message: string(*c.Error),
65 | }
66 | }
67 | return jsonErr
68 | }
69 |
70 | if c.Result == nil {
71 | return ErrNullResult
72 | }
73 |
74 | return json.Unmarshal(*c.Result, reply)
75 | }
76 |
--------------------------------------------------------------------------------
/v2/compression_selector.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package rpc
7 |
8 | import (
9 | "compress/flate"
10 | "compress/gzip"
11 | "io"
12 | "net/http"
13 | "strings"
14 | "unicode"
15 | )
16 |
17 | // gzipWriter writes and closes the gzip writer.
18 | type gzipWriter struct {
19 | w *gzip.Writer
20 | }
21 |
22 | func (gw *gzipWriter) Write(p []byte) (n int, err error) {
23 | defer gw.w.Close()
24 | return gw.w.Write(p)
25 | }
26 |
27 | // gzipEncoder implements the gzip compressed http encoder.
28 | type gzipEncoder struct {
29 | }
30 |
31 | func (enc *gzipEncoder) Encode(w http.ResponseWriter) io.Writer {
32 | w.Header().Set("Content-Encoding", "gzip")
33 | return &gzipWriter{gzip.NewWriter(w)}
34 | }
35 |
36 | // flateWriter writes and closes the flate writer.
37 | type flateWriter struct {
38 | w *flate.Writer
39 | }
40 |
41 | func (fw *flateWriter) Write(p []byte) (n int, err error) {
42 | defer fw.w.Close()
43 | return fw.w.Write(p)
44 | }
45 |
46 | // flateEncoder implements the flate compressed http encoder.
47 | type flateEncoder struct {
48 | }
49 |
50 | func (enc *flateEncoder) Encode(w http.ResponseWriter) io.Writer {
51 | fw, err := flate.NewWriter(w, flate.DefaultCompression)
52 | if err != nil {
53 | return w
54 | }
55 | w.Header().Set("Content-Encoding", "deflate")
56 | return &flateWriter{fw}
57 | }
58 |
59 | // CompressionSelector generates the compressed http encoder.
60 | type CompressionSelector struct {
61 | }
62 |
63 | // acceptedEnc returns the first compression type in "Accept-Encoding" header
64 | // field of the request.
65 | func acceptedEnc(req *http.Request) string {
66 | encHeader := req.Header.Get("Accept-Encoding")
67 | if encHeader == "" {
68 | return ""
69 | }
70 | encTypes := strings.FieldsFunc(encHeader, func(r rune) bool {
71 | return unicode.IsSpace(r) || r == ','
72 | })
73 | for _, enc := range encTypes {
74 | if enc == "gzip" || enc == "deflate" {
75 | return enc
76 | }
77 | }
78 | return ""
79 | }
80 |
81 | // Select method selects the correct compression encoder based on http HEADER.
82 | func (_ *CompressionSelector) Select(r *http.Request) Encoder {
83 | switch acceptedEnc(r) {
84 | case "gzip":
85 | return &gzipEncoder{}
86 | case "flate":
87 | return &flateEncoder{}
88 | }
89 | return DefaultEncoder
90 | }
91 |
--------------------------------------------------------------------------------
/protorpc/protorpc_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package protorpc
7 |
8 | import (
9 | "bytes"
10 | "encoding/json"
11 | "errors"
12 | "net/http"
13 | "net/http/httptest"
14 | "testing"
15 |
16 | "github.com/gorilla/rpc"
17 | )
18 |
19 | var ErrResponseError = errors.New("response error")
20 |
21 | type Service1Request struct {
22 | A int
23 | B int
24 | }
25 |
26 | type Service1BadRequest struct {
27 | }
28 |
29 | type Service1Response struct {
30 | Result int
31 | ErrorMessage string `json:"error_message"`
32 | }
33 |
34 | type Service1 struct {
35 | }
36 |
37 | func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
38 | res.Result = req.A * req.B
39 | return nil
40 | }
41 |
42 | func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
43 | return ErrResponseError
44 | }
45 |
46 | func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) (int, error) {
47 | if !s.HasMethod(method) {
48 | t.Fatal("Expected to be registered:", method)
49 | }
50 |
51 | buf, _ := json.Marshal(req)
52 | body := bytes.NewBuffer(buf)
53 | r, _ := http.NewRequest("POST", "http://localhost:8080/"+method, body)
54 | r.Header.Set("Content-Type", "application/json")
55 |
56 | w := httptest.NewRecorder()
57 | s.ServeHTTP(w, r)
58 |
59 | err := json.NewDecoder(w.Body).Decode(res)
60 | return w.Code, err
61 | }
62 |
63 | func TestService(t *testing.T) {
64 | s := rpc.NewServer()
65 | s.RegisterCodec(NewCodec(), "application/json")
66 | s.RegisterService(new(Service1), "")
67 |
68 | var res Service1Response
69 | if _, err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
70 | t.Error("Expected err to be nil, but got:", err)
71 | }
72 | if res.Result != 8 {
73 | t.Error("Expected res.Result to be 8, but got:", res.Result)
74 | }
75 | if res.ErrorMessage != "" {
76 | t.Error("Expected error_message to be empty, but got:", res.ErrorMessage)
77 | }
78 | if code, err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err != nil || code != 500 {
79 | t.Errorf("Expected code to be 500 and error to be nil, but got %v (%v)", code, err)
80 | }
81 | if res.ErrorMessage == "" {
82 | t.Errorf("Expected error_message to be %q, but got %q", ErrResponseError, res.ErrorMessage)
83 | }
84 | if code, _ := execute(t, s, "Service1.Multiply", nil, &res); code != 400 {
85 | t.Error("Expected http response code 400, but got", code)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | /*
7 | Package gorilla/rpc is a foundation for RPC over HTTP services, providing
8 | access to the exported methods of an object through HTTP requests.
9 |
10 | This package derives from the standard net/rpc package but uses a single HTTP
11 | request per call instead of persistent connections. Other differences
12 | compared to net/rpc:
13 |
14 | - Multiple codecs can be registered in the same server.
15 | - A codec is chosen based on the "Content-Type" header from the request.
16 | - Service methods also receive http.Request as parameter.
17 | - This package can be used on Google App Engine.
18 |
19 | Let's setup a server and register a codec and service:
20 |
21 | import (
22 | "http"
23 | "github.com/gorilla/rpc"
24 | "github.com/gorilla/rpc/json"
25 | )
26 |
27 | func init() {
28 | s := rpc.NewServer()
29 | s.RegisterCodec(json.NewCodec(), "application/json")
30 | s.RegisterService(new(HelloService), "")
31 | http.Handle("/rpc", s)
32 | }
33 |
34 | This server handles requests to the "/rpc" path using a JSON codec.
35 | A codec is tied to a content type. In the example above, the JSON codec is
36 | registered to serve requests with "application/json" as the value for the
37 | "Content-Type" header. If the header includes a charset definition, it is
38 | ignored; only the media-type part is taken into account.
39 |
40 | A service can be registered using a name. If the name is empty, like in the
41 | example above, it will be inferred from the service type.
42 |
43 | That's all about the server setup. Now let's define a simple service:
44 |
45 | type HelloArgs struct {
46 | Who string
47 | }
48 |
49 | type HelloReply struct {
50 | Message string
51 | }
52 |
53 | type HelloService struct {}
54 |
55 | func (h *HelloService) Say(r *http.Request, args *HelloArgs, reply *HelloReply) error {
56 | reply.Message = "Hello, " + args.Who + "!"
57 | return nil
58 | }
59 |
60 | The example above defines a service with a method "HelloService.Say" and
61 | the arguments and reply related to that method.
62 |
63 | The service must be exported (begin with an upper case letter) or local
64 | (defined in the package registering the service).
65 |
66 | When a service is registered, the server inspects the service methods
67 | and make available the ones that follow these rules:
68 |
69 | - The method name is exported.
70 | - The method has three arguments: *http.Request, *args, *reply.
71 | - All three arguments are pointers.
72 | - The second and third arguments are exported or local.
73 | - The method has return type error.
74 |
75 | All other methods are ignored.
76 |
77 | Gorilla has packages with common RPC codecs. Check out their documentation:
78 |
79 | JSON: http://gorilla-web.appspot.com/pkg/rpc/json
80 | */
81 | package rpc
82 |
--------------------------------------------------------------------------------
/v2/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | /*
7 | Package gorilla/rpc is a foundation for RPC over HTTP services, providing
8 | access to the exported methods of an object through HTTP requests.
9 |
10 | This package derives from the standard net/rpc package but uses a single HTTP
11 | request per call instead of persistent connections. Other differences
12 | compared to net/rpc:
13 |
14 | - Multiple codecs can be registered in the same server.
15 | - A codec is chosen based on the "Content-Type" header from the request.
16 | - Service methods also receive http.Request as parameter.
17 | - This package can be used on Google App Engine.
18 |
19 | Let's setup a server and register a codec and service:
20 |
21 | import (
22 | "http"
23 | "github.com/gorilla/rpc/v2"
24 | "github.com/gorilla/rpc/v2/json"
25 | )
26 |
27 | func init() {
28 | s := rpc.NewServer()
29 | s.RegisterCodec(json.NewCodec(), "application/json")
30 | s.RegisterService(new(HelloService), "")
31 | http.Handle("/rpc", s)
32 | }
33 |
34 | This server handles requests to the "/rpc" path using a JSON codec.
35 | A codec is tied to a content type. In the example above, the JSON codec is
36 | registered to serve requests with "application/json" as the value for the
37 | "Content-Type" header. If the header includes a charset definition, it is
38 | ignored; only the media-type part is taken into account.
39 |
40 | A service can be registered using a name. If the name is empty, like in the
41 | example above, it will be inferred from the service type.
42 |
43 | That's all about the server setup. Now let's define a simple service:
44 |
45 | type HelloArgs struct {
46 | Who string
47 | }
48 |
49 | type HelloReply struct {
50 | Message string
51 | }
52 |
53 | type HelloService struct {}
54 |
55 | func (h *HelloService) Say(r *http.Request, args *HelloArgs, reply *HelloReply) error {
56 | reply.Message = "Hello, " + args.Who + "!"
57 | return nil
58 | }
59 |
60 | The example above defines a service with a method "HelloService.Say" and
61 | the arguments and reply related to that method.
62 |
63 | The service must be exported (begin with an upper case letter) or local
64 | (defined in the package registering the service).
65 |
66 | When a service is registered, the server inspects the service methods
67 | and make available the ones that follow these rules:
68 |
69 | - The method name is exported.
70 | - The method has three arguments: *http.Request, *args, *reply.
71 | - All three arguments are pointers.
72 | - The second and third arguments are exported or local.
73 | - The method has return type error.
74 |
75 | All other methods are ignored.
76 |
77 | Gorilla has packages with common RPC codecs. Check out their documentation:
78 |
79 | JSON: http://gorilla-web.appspot.com/pkg/rpc/json
80 | */
81 | package rpc
82 |
--------------------------------------------------------------------------------
/v2/protorpc/protorpc_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package protorpc
7 |
8 | import (
9 | "bytes"
10 | "encoding/json"
11 | "errors"
12 | "net/http"
13 | "net/http/httptest"
14 | "strings"
15 | "testing"
16 |
17 | "github.com/gorilla/rpc/v2"
18 | )
19 |
20 | const jsonContentType = "application/json; charset=utf-8"
21 |
22 | var ErrResponseError = errors.New("response error")
23 |
24 | type Service1Request struct {
25 | A int
26 | B int
27 | }
28 |
29 | type Service1BadRequest struct {
30 | }
31 |
32 | type Service1Response struct {
33 | Result int
34 | ErrorMessage string `json:"error_message"`
35 | }
36 |
37 | type Service1 struct {
38 | }
39 |
40 | func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
41 | res.Result = req.A * req.B
42 | return nil
43 | }
44 |
45 | func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
46 | return ErrResponseError
47 | }
48 |
49 | func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) (*httptest.ResponseRecorder, error) {
50 | if !s.HasMethod(method) {
51 | t.Fatal("Expected to be registered:", method)
52 | }
53 |
54 | buf, _ := json.Marshal(req)
55 | body := bytes.NewBuffer(buf)
56 | r, _ := http.NewRequest("POST", "http://localhost:8080/"+method, body)
57 | r.Header.Set("Content-Type", "application/json")
58 |
59 | w := httptest.NewRecorder()
60 | s.ServeHTTP(w, r)
61 |
62 | err := json.NewDecoder(w.Body).Decode(res)
63 | return w, err
64 | }
65 |
66 | func TestService(t *testing.T) {
67 | s := rpc.NewServer()
68 | s.RegisterCodec(NewCodec(), "application/json")
69 | s.RegisterService(new(Service1), "")
70 |
71 | var res Service1Response
72 | if _, err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
73 | t.Error("Expected err to be nil, but got:", err)
74 | }
75 | if res.Result != 8 {
76 | t.Error("Expected res.Result to be 8, but got:", res.Result)
77 | }
78 | if res.ErrorMessage != "" {
79 | t.Error("Expected error_message to be empty, but got:", res.ErrorMessage)
80 | }
81 | if rec, err := execute(t, s, "Service1.ResponseError", &Service1Request{4,
82 | 2}, &res); err != nil || rec.Code != 400 {
83 | t.Errorf("Expected code to be 400 and error to be nil, but got %v (%v)",
84 | rec.Code, err)
85 | }
86 | if res.ErrorMessage == "" {
87 | t.Errorf("Expected error_message to be %q, but got %q", ErrResponseError, res.ErrorMessage)
88 | }
89 | if rec, _ := execute(t, s, "Service1.Multiply", nil, &res); rec.Code != 400 {
90 | t.Error("Expected http response code 400, but got", rec.Code)
91 | }
92 | if rec, _ := execute(t, s, "Service1.Multiply", nil, &res); !strings.Contains(rec.Header().Get("Content-Type"), jsonContentType) {
93 | t.Errorf("Expected Content-Type header %q, but got %q", jsonContentType,
94 | rec.Header().Get("Content-Type"))
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/json/json_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json
7 |
8 | import (
9 | "bytes"
10 | "encoding/json"
11 | "errors"
12 | "fmt"
13 | "net/http"
14 | "net/http/httptest"
15 | "testing"
16 |
17 | "github.com/gorilla/rpc"
18 | )
19 |
20 | var ErrResponseError = errors.New("response error")
21 |
22 | type Service1Request struct {
23 | A int
24 | B int
25 | }
26 |
27 | type Service1BadRequest struct {
28 | M string `json:"method"`
29 | }
30 |
31 | type Service1Response struct {
32 | Result int
33 | }
34 |
35 | type Service1 struct {
36 | beforeAfterContext map[string]string
37 | }
38 |
39 | func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
40 | res.Result = req.A * req.B
41 | return nil
42 | }
43 |
44 | func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
45 | return ErrResponseError
46 | }
47 |
48 | func (t *Service1) BeforeAfter(r *http.Request, req *Service1Request, res *Service1Response) error {
49 | if _, ok := t.beforeAfterContext["before"]; !ok {
50 | return fmt.Errorf("before value not found in context")
51 | }
52 | res.Result = 1
53 | return nil
54 | }
55 |
56 | func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) error {
57 | if !s.HasMethod(method) {
58 | t.Fatal("Expected to be registered:", method)
59 | }
60 |
61 | buf, _ := EncodeClientRequest(method, req)
62 | body := bytes.NewBuffer(buf)
63 | r, _ := http.NewRequest("POST", "http://localhost:8080/", body)
64 | r.Header.Set("Content-Type", "application/json")
65 |
66 | w := httptest.NewRecorder()
67 | s.ServeHTTP(w, r)
68 |
69 | return DecodeClientResponse(w.Body, res)
70 | }
71 |
72 | func executeRaw(t *testing.T, s *rpc.Server, req interface{}, res interface{}) int {
73 | j, _ := json.Marshal(req)
74 | r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(j))
75 | r.Header.Set("Content-Type", "application/json")
76 |
77 | w := httptest.NewRecorder()
78 | s.ServeHTTP(w, r)
79 |
80 | return w.Code
81 | }
82 |
83 | func TestService(t *testing.T) {
84 | s := rpc.NewServer()
85 | s.RegisterCodec(NewCodec(), "application/json")
86 | s.RegisterService(new(Service1), "")
87 |
88 | var res Service1Response
89 | if err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
90 | t.Error("Expected err to be nil, but got:", err)
91 | }
92 | if res.Result != 8 {
93 | t.Errorf("Wrong response: %v.", res.Result)
94 | }
95 |
96 | if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil {
97 | t.Errorf("Expected to get %q, but got nil", ErrResponseError)
98 | } else if err.Error() != ErrResponseError.Error() {
99 | t.Errorf("Expected to get %q, but got %q", ErrResponseError, err)
100 | }
101 |
102 | if code := executeRaw(t, s, &Service1BadRequest{"Service1.Multiply"}, &res); code != 400 {
103 | t.Errorf("Expected http response code 400, but got %v", code)
104 | }
105 | }
106 |
107 | func TestServiceBeforeAfter(t *testing.T) {
108 | s := rpc.NewServer()
109 | s.RegisterCodec(NewCodec(), "application/json")
110 | service := &Service1{}
111 | service.beforeAfterContext = map[string]string{}
112 | s.RegisterService(service, "")
113 |
114 | s.RegisterBeforeFunc(func(i *rpc.RequestInfo) {
115 | service.beforeAfterContext["before"] = "Before is true"
116 | })
117 | s.RegisterAfterFunc(func(i *rpc.RequestInfo) {
118 | service.beforeAfterContext["after"] = "After is true"
119 | })
120 |
121 | var res Service1Response
122 | if err := execute(t, s, "Service1.BeforeAfter", &Service1Request{}, &res); err != nil {
123 | t.Error("Expected err to be nil, but got:", err)
124 | }
125 |
126 | if res.Result != 1 {
127 | t.Errorf("Expected Result = 1, got %d", res.Result)
128 | }
129 |
130 | if afterValue, ok := service.beforeAfterContext["after"]; !ok || afterValue != "After is true" {
131 | t.Errorf("Expected after in context to be 'After is true', got %s", afterValue)
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/v2/server_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package rpc
7 |
8 | import (
9 | "net/http"
10 | "strconv"
11 | "testing"
12 | )
13 |
14 | type Service1Request struct {
15 | A int
16 | B int
17 | }
18 |
19 | type Service1Response struct {
20 | Result int
21 | }
22 |
23 | type Service1 struct {
24 | }
25 |
26 | func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
27 | res.Result = req.A * req.B
28 | return nil
29 | }
30 |
31 | type Service2 struct {
32 | }
33 |
34 | func TestRegisterService(t *testing.T) {
35 | var err error
36 | s := NewServer()
37 | service1 := new(Service1)
38 | service2 := new(Service2)
39 |
40 | // Inferred name.
41 | err = s.RegisterService(service1, "")
42 | if err != nil || !s.HasMethod("Service1.Multiply") {
43 | t.Errorf("Expected to be registered: Service1.Multiply")
44 | }
45 | // Provided name.
46 | err = s.RegisterService(service1, "Foo")
47 | if err != nil || !s.HasMethod("Foo.Multiply") {
48 | t.Errorf("Expected to be registered: Foo.Multiply")
49 | }
50 | // No methods.
51 | err = s.RegisterService(service2, "")
52 | if err == nil {
53 | t.Errorf("Expected error on service2")
54 | }
55 | }
56 |
57 | // MockCodec decodes to Service1.Multiply.
58 | type MockCodec struct {
59 | A, B int
60 | }
61 |
62 | func (c MockCodec) NewRequest(*http.Request) CodecRequest {
63 | return MockCodecRequest{c.A, c.B}
64 | }
65 |
66 | type MockCodecRequest struct {
67 | A, B int
68 | }
69 |
70 | func (r MockCodecRequest) Method() (string, error) {
71 | return "Service1.Multiply", nil
72 | }
73 |
74 | func (r MockCodecRequest) ReadRequest(args interface{}) error {
75 | req := args.(*Service1Request)
76 | req.A, req.B = r.A, r.B
77 | return nil
78 | }
79 |
80 | func (r MockCodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
81 | res := reply.(*Service1Response)
82 | w.Write([]byte(strconv.Itoa(res.Result)))
83 | }
84 |
85 | func (r MockCodecRequest) WriteError(w http.ResponseWriter, status int, err error) {
86 | w.WriteHeader(status)
87 | w.Write([]byte(err.Error()))
88 | }
89 |
90 | type MockResponseWriter struct {
91 | header http.Header
92 | Status int
93 | Body string
94 | }
95 |
96 | func NewMockResponseWriter() *MockResponseWriter {
97 | header := make(http.Header)
98 | return &MockResponseWriter{header: header}
99 | }
100 |
101 | func (w *MockResponseWriter) Header() http.Header {
102 | return w.header
103 | }
104 |
105 | func (w *MockResponseWriter) Write(p []byte) (int, error) {
106 | w.Body = string(p)
107 | if w.Status == 0 {
108 | w.Status = 200
109 | }
110 | return len(p), nil
111 | }
112 |
113 | func (w *MockResponseWriter) WriteHeader(status int) {
114 | w.Status = status
115 | }
116 |
117 | func TestServeHTTP(t *testing.T) {
118 | const (
119 | A = 2
120 | B = 3
121 | )
122 | expected := A * B
123 |
124 | s := NewServer()
125 | s.RegisterService(new(Service1), "")
126 | s.RegisterCodec(MockCodec{A, B}, "mock")
127 |
128 | r, err := http.NewRequest("POST", "", nil)
129 | if err != nil {
130 | t.Fatal(err)
131 | }
132 | r.Header.Set("Content-Type", "mock; dummy")
133 | w := NewMockResponseWriter()
134 | s.ServeHTTP(w, r)
135 | if w.Status != 200 {
136 | t.Errorf("Status was %d, should be 200.", w.Status)
137 | }
138 | if w.Body != strconv.Itoa(expected) {
139 | t.Errorf("Response body was %s, should be %s.", w.Body, strconv.Itoa(expected))
140 | }
141 |
142 | // Test wrong Content-Type
143 | r.Header.Set("Content-Type", "invalid")
144 | w = NewMockResponseWriter()
145 | s.ServeHTTP(w, r)
146 | if w.Status != 415 {
147 | t.Errorf("Status was %d, should be 415.", w.Status)
148 | }
149 | if w.Body != "rpc: unrecognized Content-Type: invalid" {
150 | t.Errorf("Wrong response body.")
151 | }
152 |
153 | // Test omitted Content-Type; codec should default to the sole registered one.
154 | r.Header.Del("Content-Type")
155 | w = NewMockResponseWriter()
156 | s.ServeHTTP(w, r)
157 | if w.Status != 200 {
158 | t.Errorf("Status was %d, should be 200.", w.Status)
159 | }
160 | if w.Body != strconv.Itoa(expected) {
161 | t.Errorf("Response body was %s, should be %s.", w.Body, strconv.Itoa(expected))
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/v2/json/json_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json
7 |
8 | import (
9 | "bytes"
10 | "encoding/json"
11 | "errors"
12 | "net/http"
13 | "net/http/httptest"
14 | "reflect"
15 | "testing"
16 |
17 | "github.com/gorilla/rpc/v2"
18 | )
19 |
20 | var (
21 | ErrResponseError = errors.New("response error")
22 | ErrResponseJsonError = &Error{Data: map[string]interface{}{
23 | "stackstrace": map[string]interface{}{"0": "foo()"},
24 | "error": "a message",
25 | }}
26 | )
27 |
28 | type Service1Request struct {
29 | A int
30 | B int
31 | }
32 |
33 | type Service1Response struct {
34 | Result int
35 | }
36 |
37 | type Service1 struct {
38 | }
39 |
40 | func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
41 | res.Result = req.A * req.B
42 | return nil
43 | }
44 |
45 | func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
46 | return ErrResponseError
47 | }
48 |
49 | func (t *Service1) ResponseJsonError(r *http.Request, req *Service1Request, res *Service1Response) error {
50 | return ErrResponseJsonError
51 | }
52 |
53 | func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) error {
54 | if !s.HasMethod(method) {
55 | t.Fatal("Expected to be registered:", method)
56 | }
57 |
58 | buf, _ := EncodeClientRequest(method, req)
59 | body := bytes.NewBuffer(buf)
60 | r, _ := http.NewRequest("POST", "http://localhost:8080/", body)
61 | r.Header.Set("Content-Type", "application/json")
62 |
63 | w := httptest.NewRecorder()
64 | s.ServeHTTP(w, r)
65 |
66 | return DecodeClientResponse(w.Body, res)
67 | }
68 |
69 | func executeRaw(t *testing.T, s *rpc.Server, req json.RawMessage) (int, *bytes.Buffer) {
70 | r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(req))
71 | r.Header.Set("Content-Type", "application/json")
72 |
73 | w := httptest.NewRecorder()
74 | s.ServeHTTP(w, r)
75 |
76 | return w.Code, w.Body
77 | }
78 |
79 | func field(name string, blob json.RawMessage) (v interface{}, ok bool) {
80 | var obj map[string]interface{}
81 | if err := json.Unmarshal(blob, &obj); err != nil {
82 | return nil, false
83 | }
84 | v, ok = obj[name]
85 | return
86 | }
87 |
88 | func TestService(t *testing.T) {
89 | s := rpc.NewServer()
90 | s.RegisterCodec(NewCodec(), "application/json")
91 | s.RegisterService(new(Service1), "")
92 |
93 | var res Service1Response
94 | if err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
95 | t.Error("Expected err to be nil, but got", err)
96 | }
97 | if res.Result != 8 {
98 | t.Error("Expected res.Result to be 8, but got", res.Result)
99 | }
100 | if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil {
101 | t.Errorf("Expected to get %q, but got nil", ErrResponseError)
102 | } else if err.Error() != ErrResponseError.Error() {
103 | t.Errorf("Expected to get %q, but got %q", ErrResponseError, err)
104 | }
105 | if code, res := executeRaw(t, s, json.RawMessage(`{"method":"Service1.Multiply","params":null,"id":5}`)); code != 400 {
106 | t.Error("Expected response code to be 400, but got", code)
107 | } else if v, ok := field("result", res.Bytes()); !ok || v != nil {
108 | t.Errorf("Expected ok to be true and v to be nil, but got %v and %v", ok, v)
109 | }
110 | if err := execute(t, s, "Service1.ResponseJsonError", &Service1Request{4, 2}, &res); err == nil {
111 | t.Errorf("Expected to get %q, but got nil", ErrResponseError)
112 | } else if jsonErr, ok := err.(*Error); !ok {
113 | t.Error("Expected err to be of a *json.Error type")
114 | } else if !reflect.DeepEqual(jsonErr.Data, ErrResponseJsonError.Data) {
115 | t.Errorf("Expected jsonErr to be %q, but got %q", ErrResponseJsonError, jsonErr)
116 | }
117 | }
118 |
119 | func TestClientNullResult(t *testing.T) {
120 | data := `{"jsonrpc": "2.0", "id": 8674665223082153551, "result": null}`
121 | reader := bytes.NewReader([]byte(data))
122 |
123 | var reply interface{}
124 |
125 | err := DecodeClientResponse(reader, &reply)
126 | if err == nil {
127 | t.Fatal(err)
128 | }
129 | if err.Error() != "Unexpected null result" {
130 | t.Fatalf("Unexpected error: %s", err)
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/json/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json
7 |
8 | import (
9 | "encoding/json"
10 | "errors"
11 | "net/http"
12 |
13 | "github.com/gorilla/rpc"
14 | )
15 |
16 | var null = json.RawMessage([]byte("null"))
17 |
18 | // ----------------------------------------------------------------------------
19 | // Request and Response
20 | // ----------------------------------------------------------------------------
21 |
22 | // serverRequest represents a JSON-RPC request received by the server.
23 | type serverRequest struct {
24 | // A String containing the name of the method to be invoked.
25 | Method string `json:"method"`
26 | // An Array of objects to pass as arguments to the method.
27 | Params *json.RawMessage `json:"params"`
28 | // The request id. This can be of any type. It is used to match the
29 | // response with the request that it is replying to.
30 | Id *json.RawMessage `json:"id"`
31 | }
32 |
33 | // serverResponse represents a JSON-RPC response returned by the server.
34 | type serverResponse struct {
35 | // The Object that was returned by the invoked method. This must be null
36 | // in case there was an error invoking the method.
37 | Result interface{} `json:"result"`
38 | // An Error object if there was an error invoking the method. It must be
39 | // null if there was no error.
40 | Error interface{} `json:"error"`
41 | // This must be the same id as the request it is responding to.
42 | Id *json.RawMessage `json:"id"`
43 | }
44 |
45 | // ----------------------------------------------------------------------------
46 | // Codec
47 | // ----------------------------------------------------------------------------
48 |
49 | // NewCodec returns a new JSON Codec.
50 | func NewCodec() *Codec {
51 | return &Codec{}
52 | }
53 |
54 | // Codec creates a CodecRequest to process each request.
55 | type Codec struct {
56 | }
57 |
58 | // NewRequest returns a CodecRequest.
59 | func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
60 | return newCodecRequest(r)
61 | }
62 |
63 | // ----------------------------------------------------------------------------
64 | // CodecRequest
65 | // ----------------------------------------------------------------------------
66 |
67 | // newCodecRequest returns a new CodecRequest.
68 | func newCodecRequest(r *http.Request) rpc.CodecRequest {
69 | // Decode the request body and check if RPC method is valid.
70 | req := new(serverRequest)
71 | err := json.NewDecoder(r.Body).Decode(req)
72 | r.Body.Close()
73 | return &CodecRequest{request: req, err: err}
74 | }
75 |
76 | // CodecRequest decodes and encodes a single request.
77 | type CodecRequest struct {
78 | request *serverRequest
79 | err error
80 | }
81 |
82 | // Method returns the RPC method for the current request.
83 | //
84 | // The method uses a dotted notation as in "Service.Method".
85 | func (c *CodecRequest) Method() (string, error) {
86 | if c.err == nil {
87 | return c.request.Method, nil
88 | }
89 | return "", c.err
90 | }
91 |
92 | // ReadRequest fills the request object for the RPC method.
93 | func (c *CodecRequest) ReadRequest(args interface{}) error {
94 | if c.err == nil {
95 | if c.request.Params != nil {
96 | // JSON params is array value. RPC params is struct.
97 | // Unmarshal into array containing the request struct.
98 | params := [1]interface{}{args}
99 | c.err = json.Unmarshal(*c.request.Params, ¶ms)
100 | } else {
101 | c.err = errors.New("rpc: method request ill-formed: missing params field")
102 | }
103 | }
104 | return c.err
105 | }
106 |
107 | // WriteResponse encodes the response and writes it to the ResponseWriter.
108 | //
109 | // The err parameter is the error resulted from calling the RPC method,
110 | // or nil if there was no error.
111 | func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}, methodErr error) error {
112 | if c.err != nil {
113 | return c.err
114 | }
115 | res := &serverResponse{
116 | Result: reply,
117 | Error: &null,
118 | Id: c.request.Id,
119 | }
120 | if methodErr != nil {
121 | // Propagate error message as string.
122 | res.Error = methodErr.Error()
123 | // Result must be null if there was an error invoking the method.
124 | // http://json-rpc.org/wiki/specification#a1.2Response
125 | res.Result = &null
126 | }
127 | if c.request.Id == nil {
128 | // Id is null for notifications and they don't have a response.
129 | res.Id = &null
130 | } else {
131 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
132 | encoder := json.NewEncoder(w)
133 | encoder.Encode(res)
134 | }
135 | return nil
136 | }
137 |
--------------------------------------------------------------------------------
/protorpc/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package protorpc
7 |
8 | import (
9 | "encoding/json"
10 | "errors"
11 | "fmt"
12 | "io"
13 | "net/http"
14 | "strings"
15 |
16 | "github.com/gorilla/rpc"
17 | )
18 |
19 | var null = json.RawMessage([]byte("null"))
20 |
21 | // ----------------------------------------------------------------------------
22 | // Request and Response
23 | // ----------------------------------------------------------------------------
24 |
25 | // serverRequest represents a ProtoRPC request received by the server.
26 | type serverRequest struct {
27 | // A String containing the name of the method to be invoked.
28 | Method string `json:"method"`
29 | // An Array of objects to pass as arguments to the method.
30 | Params *json.RawMessage `json:"params"`
31 | // The request id. This can be of any type. It is used to match the
32 | // response with the request that it is replying to.
33 | Id *json.RawMessage `json:"id"`
34 | }
35 |
36 | // serverResponse represents a ProtoRPC response returned by the server.
37 | type serverResponse struct {
38 | // The Object that was returned by the invoked method. This must be null
39 | // in case there was an error invoking the method.
40 | Result interface{} `json:"result"`
41 | // An Error object if there was an error invoking the method. It must be
42 | // null if there was no error.
43 | Error interface{} `json:"error"`
44 | // This must be the same id as the request it is responding to.
45 | Id *json.RawMessage `json:"id"`
46 | }
47 |
48 | // ----------------------------------------------------------------------------
49 | // Codec
50 | // ----------------------------------------------------------------------------
51 |
52 | // NewCodec returns a new ProtoRPC Codec.
53 | func NewCodec() *Codec {
54 | return &Codec{}
55 | }
56 |
57 | // Codec creates a CodecRequest to process each request.
58 | type Codec struct {
59 | }
60 |
61 | // NewRequest returns a CodecRequest.
62 | func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
63 | return newCodecRequest(r)
64 | }
65 |
66 | // ----------------------------------------------------------------------------
67 | // CodecRequest
68 | // ----------------------------------------------------------------------------
69 |
70 | // newCodecRequest returns a new CodecRequest.
71 | func newCodecRequest(r *http.Request) rpc.CodecRequest {
72 | // Decode the request body and check if RPC method is valid.
73 | req := new(serverRequest)
74 | path := r.URL.Path
75 | index := strings.LastIndex(path, "/")
76 | if index < 0 {
77 | return &CodecRequest{request: req, err: fmt.Errorf("rpc: no method: %s", path)}
78 | }
79 | req.Method = path[index+1:]
80 | err := json.NewDecoder(r.Body).Decode(&req.Params)
81 | r.Body.Close()
82 | var errr error
83 | if err != io.EOF {
84 | errr = err
85 | }
86 | return &CodecRequest{request: req, err: errr}
87 | }
88 |
89 | // CodecRequest decodes and encodes a single request.
90 | type CodecRequest struct {
91 | request *serverRequest
92 | err error
93 | }
94 |
95 | // Method returns the RPC method for the current request.
96 | //
97 | // The method uses a dotted notation as in "Service.Method".
98 | func (c *CodecRequest) Method() (string, error) {
99 | if c.err == nil {
100 | return c.request.Method, nil
101 | }
102 | return "", c.err
103 | }
104 |
105 | // ReadRequest fills the request object for the RPC method.
106 | func (c *CodecRequest) ReadRequest(args interface{}) error {
107 | if c.err == nil {
108 | if c.request.Params != nil {
109 | c.err = json.Unmarshal(*c.request.Params, args)
110 | } else {
111 | c.err = errors.New("rpc: method request ill-formed: missing params field")
112 | }
113 | }
114 | return c.err
115 | }
116 |
117 | // WriteResponse encodes the response and writes it to the ResponseWriter.
118 | //
119 | // The err parameter is the error resulted from calling the RPC method,
120 | // or nil if there was no error.
121 | func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}, methodErr error) error {
122 | if c.err != nil {
123 | return c.err
124 | }
125 | res := &serverResponse{
126 | Result: reply,
127 | Error: &null,
128 | Id: c.request.Id,
129 | }
130 | if methodErr != nil {
131 | // Propagate error message as string.
132 | res.Error = methodErr.Error()
133 | // Result must be null if there was an error invoking the method.
134 | res.Result = &struct {
135 | ErrorMessage interface{} `json:"error_message"`
136 | }{res.Error}
137 | w.WriteHeader(500)
138 | }
139 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
140 | encoder := json.NewEncoder(w)
141 | encoder.Encode(res.Result)
142 | return nil
143 | }
144 |
--------------------------------------------------------------------------------
/v2/protorpc/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package protorpc
7 |
8 | import (
9 | "encoding/json"
10 | "errors"
11 | "fmt"
12 | "io"
13 | "net/http"
14 | "strings"
15 |
16 | "github.com/gorilla/rpc/v2"
17 | )
18 |
19 | var null = json.RawMessage([]byte("null"))
20 |
21 | // ----------------------------------------------------------------------------
22 | // Request and Response
23 | // ----------------------------------------------------------------------------
24 |
25 | // serverRequest represents a ProtoRPC request received by the server.
26 | type serverRequest struct {
27 | // A String containing the name of the method to be invoked.
28 | Method string `json:"method"`
29 | // An Array of objects to pass as arguments to the method.
30 | Params *json.RawMessage `json:"params"`
31 | // The request id. This can be of any type. It is used to match the
32 | // response with the request that it is replying to.
33 | Id *json.RawMessage `json:"id"`
34 | }
35 |
36 | // serverResponse represents a ProtoRPC response returned by the server.
37 | type serverResponse struct {
38 | // The Object that was returned by the invoked method. This must be null
39 | // in case there was an error invoking the method.
40 | Result interface{} `json:"result"`
41 | // An Error object if there was an error invoking the method. It must be
42 | // null if there was no error.
43 | Error interface{} `json:"error"`
44 | // This must be the same id as the request it is responding to.
45 | Id *json.RawMessage `json:"id"`
46 | }
47 |
48 | // ----------------------------------------------------------------------------
49 | // Codec
50 | // ----------------------------------------------------------------------------
51 |
52 | // NewCodec returns a new ProtoRPC Codec.
53 | func NewCodec() *Codec {
54 | return &Codec{}
55 | }
56 |
57 | // Codec creates a CodecRequest to process each request.
58 | type Codec struct {
59 | }
60 |
61 | // NewRequest returns a CodecRequest.
62 | func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
63 | return newCodecRequest(r)
64 | }
65 |
66 | // ----------------------------------------------------------------------------
67 | // CodecRequest
68 | // ----------------------------------------------------------------------------
69 |
70 | // newCodecRequest returns a new CodecRequest.
71 | func newCodecRequest(r *http.Request) rpc.CodecRequest {
72 | // Decode the request body and check if RPC method is valid.
73 | req := new(serverRequest)
74 | path := r.URL.Path
75 | index := strings.LastIndex(path, "/")
76 | if index < 0 {
77 | return &CodecRequest{request: req, err: fmt.Errorf("rpc: no method: %s", path)}
78 | }
79 | req.Method = path[index+1:]
80 | err := json.NewDecoder(r.Body).Decode(&req.Params)
81 | r.Body.Close()
82 | var errr error
83 | if err != io.EOF {
84 | errr = err
85 | }
86 | return &CodecRequest{request: req, err: errr}
87 | }
88 |
89 | // CodecRequest decodes and encodes a single request.
90 | type CodecRequest struct {
91 | request *serverRequest
92 | err error
93 | }
94 |
95 | // Method returns the RPC method for the current request.
96 | //
97 | // The method uses a dotted notation as in "Service.Method".
98 | func (c *CodecRequest) Method() (string, error) {
99 | if c.err == nil {
100 | return c.request.Method, nil
101 | }
102 | return "", c.err
103 | }
104 |
105 | // ReadRequest fills the request object for the RPC method.
106 | func (c *CodecRequest) ReadRequest(args interface{}) error {
107 | if c.err == nil {
108 | if c.request.Params != nil {
109 | c.err = json.Unmarshal(*c.request.Params, args)
110 | } else {
111 | c.err = errors.New("rpc: method request ill-formed: missing params field")
112 | }
113 | }
114 | return c.err
115 | }
116 |
117 | // WriteResponse encodes the response and writes it to the ResponseWriter.
118 | func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
119 | res := &serverResponse{
120 | Result: reply,
121 | Error: &null,
122 | Id: c.request.Id,
123 | }
124 | c.writeServerResponse(w, 200, res)
125 | }
126 |
127 | func (c *CodecRequest) WriteError(w http.ResponseWriter, status int, err error) {
128 | res := &serverResponse{
129 | Result: &struct {
130 | ErrorMessage interface{} `json:"error_message"`
131 | }{err.Error()},
132 | Id: c.request.Id,
133 | }
134 | c.writeServerResponse(w, status, res)
135 | }
136 |
137 | func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, status int, res *serverResponse) {
138 | b, err := json.Marshal(res.Result)
139 | if err != nil {
140 | rpc.WriteError(w, 400, err.Error())
141 | }
142 |
143 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
144 | w.WriteHeader(status)
145 | w.Write(b)
146 | }
147 |
--------------------------------------------------------------------------------
/v2/json2/testapp/jquery.jsonrpc.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery JSON-RPC Plugin
3 | *
4 | * @version: 0.3(2012-05-17)
5 | * @author hagino3000 (Takashi Nishibayashi)
6 | * @author alanjds (Alan Justino da Silva)
7 | *
8 | * A JSON-RPC 2.0 implementation for jQuery.
9 | * JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol.
10 | * Read more in the
11 | *
12 | * Requires json2.js if browser has not window.JSON.
13 | *
14 | * Usage:
15 | * $.jsonrpc(data [, callbacks [, debug]]);
16 | *
17 | * where data = {url: '/rpc/', method:'simplefunc', params:['posi', 'tional']}
18 | * or data = {url: '/rpc/', method:'complexfunc', params:{nam:'ed', par:'ams'}}
19 | * and callbacks = {success: successFunc, error: errorFunc}
20 | *
21 | * Setting no callback produces a JSON-RPC Notification.
22 | * 'data' accepts 'timeout' keyword too, who sets the $.ajax request timeout.
23 | * Setting 'debug' to true prints responses to Firebug's console.info
24 | *
25 | * Examples:
26 | * // A RPC call with named parameters
27 | * $.jsonrpc({
28 | * url : '/rpc',
29 | * method : 'createUser',
30 | * params : {name : 'John Smith', userId : '1000'}
31 | * }, {
32 | * success : function(result) {
33 | * //doSomething
34 | * },
35 | * error : function(error) {
36 | * //doSomething
37 | * }
38 | * });
39 | *
40 | * // Once set defaultUrl, url option is no need
41 | * $.jsonrpc.defaultUrl = '/rpc';
42 | *
43 | * // A Notification
44 | * $.jsonrpc({
45 | * method : 'notify',
46 | * params : {action : 'logout', userId : '1000'}
47 | * });
48 | *
49 | * // A Notification using console to debug and with timeout set
50 | * $.jsonrpc({
51 | * method : 'notify',
52 | * params : {action : 'logout', userId : '1000'},
53 | * debug : true,
54 | * timeout : 500,
55 | * });
56 | *
57 | * // Set DataFilter. It is useful for buggy API that returns sometimes not json but html (when 500, 403..).
58 | * $.jsonrpc({
59 | * method : 'getUser',
60 | * dataFilter : function(data, type) {
61 | * try {
62 | * return JSON.parse(data);
63 | * } catch(e) {
64 | * return {error : {message : 'Cannot parse response', data : data}};
65 | * }
66 | * }, function(result){ doSomething... }
67 | * }, {
68 | * success : handleSuccess
69 | * error : handleFailure
70 | * });
71 | *
72 | * This document is licensed as free software under the terms of the
73 | * MIT License: http://www.opensource.org/licenses/mit-license.php
74 | */
75 | (function($) {
76 |
77 | var rpcid = 1,
78 | emptyFn = function() {};
79 |
80 | $.jsonrpc = $.jsonrpc || function(data, callbacks, debug) {
81 | debug = debug || false;
82 |
83 | var postdata = {
84 | jsonrpc: '2.0',
85 | method: data.method || '',
86 | params: data.params || {}
87 | };
88 | if (callbacks) {
89 | postdata.id = data.id || rpcid++;
90 | } else {
91 | callbacks = emptyFn;
92 | }
93 |
94 | if (typeof(callbacks) === 'function') {
95 | callbacks = {
96 | success: callbacks,
97 | error: callbacks
98 | };
99 | }
100 |
101 | var dataFilter = data.dataFilter;
102 |
103 | var ajaxopts = {
104 | url: data.url || $.jsonrpc.defaultUrl,
105 | contentType: 'application/json',
106 | dataType: 'text',
107 | dataFilter: function(data, type) {
108 | if (dataFilter) {
109 | return dataFilter(data);
110 | } else {
111 | if (data != "") return JSON.parse(data);
112 | }
113 | },
114 | type: 'POST',
115 | processData: false,
116 | data: JSON.stringify(postdata),
117 | success: function(resp) {
118 | if (resp && !resp.error) {
119 | return callbacks.success && callbacks.success(resp.result);
120 | } else if (resp && resp.error) {
121 | return callbacks.error && callbacks.error(resp.error);
122 | } else {
123 | return callbacks.error && callbacks.error(resp);
124 | }
125 | },
126 | error: function(xhr, status, error) {
127 | if (error === 'timeout') {
128 | callbacks.error({
129 | status: status,
130 | code: 0,
131 | message: 'Request Timeout'
132 | });
133 | return;
134 | }
135 | // If response code is 404, 400, 500, server returns error object
136 | try {
137 | var res = JSON.parse(xhr.responseText);
138 | callbacks.error(res.error);
139 | } catch (e) {
140 | callbacks.error({
141 | status: status,
142 | code: 0,
143 | message: error
144 | });
145 | }
146 | }
147 | };
148 | if (data.timeout) {
149 | ajaxopts['timeout'] = data.timeout;
150 | }
151 |
152 | $.ajax(ajaxopts);
153 |
154 | return $;
155 | }
156 | $.jsonrpc.defaultUrl = $.jsonrpc.defaultUrl || '/jsonrpc/';
157 |
158 | })(jQuery);
159 |
--------------------------------------------------------------------------------
/v2/json/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json
7 |
8 | import (
9 | "encoding/json"
10 | "errors"
11 | "fmt"
12 | "net/http"
13 |
14 | "github.com/gorilla/rpc/v2"
15 | )
16 |
17 | var null = json.RawMessage([]byte("null"))
18 |
19 | // An Error is a wrapper for a JSON interface value. It can be used by either
20 | // a service's handler func to write more complex JSON data to an error field
21 | // of a server's response, or by a client to read it.
22 | type Error struct {
23 | Data interface{}
24 | }
25 |
26 | func (e *Error) Error() string {
27 | return fmt.Sprintf("%v", e.Data)
28 | }
29 |
30 | // ----------------------------------------------------------------------------
31 | // Request and Response
32 | // ----------------------------------------------------------------------------
33 |
34 | // serverRequest represents a JSON-RPC request received by the server.
35 | type serverRequest struct {
36 | // A String containing the name of the method to be invoked.
37 | Method string `json:"method"`
38 | // An Array of objects to pass as arguments to the method.
39 | Params *json.RawMessage `json:"params"`
40 | // The request id. This can be of any type. It is used to match the
41 | // response with the request that it is replying to.
42 | Id *json.RawMessage `json:"id"`
43 | }
44 |
45 | // serverResponse represents a JSON-RPC response returned by the server.
46 | type serverResponse struct {
47 | // The Object that was returned by the invoked method. This must be null
48 | // in case there was an error invoking the method.
49 | Result interface{} `json:"result"`
50 | // An Error object if there was an error invoking the method. It must be
51 | // null if there was no error.
52 | Error interface{} `json:"error"`
53 | // This must be the same id as the request it is responding to.
54 | Id *json.RawMessage `json:"id"`
55 | }
56 |
57 | // ----------------------------------------------------------------------------
58 | // Codec
59 | // ----------------------------------------------------------------------------
60 |
61 | // NewCodec returns a new JSON Codec.
62 | func NewCodec() *Codec {
63 | return &Codec{}
64 | }
65 |
66 | // Codec creates a CodecRequest to process each request.
67 | type Codec struct {
68 | }
69 |
70 | // NewRequest returns a CodecRequest.
71 | func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
72 | return newCodecRequest(r)
73 | }
74 |
75 | // ----------------------------------------------------------------------------
76 | // CodecRequest
77 | // ----------------------------------------------------------------------------
78 |
79 | // newCodecRequest returns a new CodecRequest.
80 | func newCodecRequest(r *http.Request) rpc.CodecRequest {
81 | // Decode the request body and check if RPC method is valid.
82 | req := new(serverRequest)
83 | err := json.NewDecoder(r.Body).Decode(req)
84 | r.Body.Close()
85 | return &CodecRequest{request: req, err: err}
86 | }
87 |
88 | // CodecRequest decodes and encodes a single request.
89 | type CodecRequest struct {
90 | request *serverRequest
91 | err error
92 | }
93 |
94 | // Method returns the RPC method for the current request.
95 | //
96 | // The method uses a dotted notation as in "Service.Method".
97 | func (c *CodecRequest) Method() (string, error) {
98 | if c.err == nil {
99 | return c.request.Method, nil
100 | }
101 | return "", c.err
102 | }
103 |
104 | // ReadRequest fills the request object for the RPC method.
105 | func (c *CodecRequest) ReadRequest(args interface{}) error {
106 | if c.err == nil {
107 | if c.request.Params != nil {
108 | // JSON params is array value. RPC params is struct.
109 | // Unmarshal into array containing the request struct.
110 | params := [1]interface{}{args}
111 | c.err = json.Unmarshal(*c.request.Params, ¶ms)
112 | } else {
113 | c.err = errors.New("rpc: method request ill-formed: missing params field")
114 | }
115 | }
116 | return c.err
117 | }
118 |
119 | // WriteResponse encodes the response and writes it to the ResponseWriter.
120 | func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
121 | if c.request.Id != nil {
122 | // Id is null for notifications and they don't have a response.
123 | res := &serverResponse{
124 | Result: reply,
125 | Error: &null,
126 | Id: c.request.Id,
127 | }
128 | c.writeServerResponse(w, 200, res)
129 | }
130 | }
131 |
132 | func (c *CodecRequest) WriteError(w http.ResponseWriter, _ int, err error) {
133 | res := &serverResponse{
134 | Result: &null,
135 | Id: c.request.Id,
136 | }
137 | if jsonErr, ok := err.(*Error); ok {
138 | res.Error = jsonErr.Data
139 | } else {
140 | res.Error = err.Error()
141 | }
142 | c.writeServerResponse(w, 400, res)
143 | }
144 |
145 | func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, status int, res *serverResponse) {
146 | b, err := json.Marshal(res)
147 | if err == nil {
148 | w.WriteHeader(status)
149 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
150 | w.Write(b)
151 | } else {
152 | // Not sure in which case will this happen. But seems harmless.
153 | rpc.WriteError(w, 400, err.Error())
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/v2/map.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package rpc
7 |
8 | import (
9 | "fmt"
10 | "net/http"
11 | "reflect"
12 | "strings"
13 | "sync"
14 | "unicode"
15 | "unicode/utf8"
16 | )
17 |
18 | var (
19 | // Precompute the reflect.Type of error and http.Request
20 | typeOfError = reflect.TypeOf((*error)(nil)).Elem()
21 | typeOfRequest = reflect.TypeOf((*http.Request)(nil)).Elem()
22 | )
23 |
24 | // ----------------------------------------------------------------------------
25 | // service
26 | // ----------------------------------------------------------------------------
27 |
28 | type service struct {
29 | name string // name of service
30 | rcvr reflect.Value // receiver of methods for the service
31 | rcvrType reflect.Type // type of the receiver
32 | methods map[string]*serviceMethod // registered methods
33 | }
34 |
35 | type serviceMethod struct {
36 | method reflect.Method // receiver method
37 | argsType reflect.Type // type of the request argument
38 | replyType reflect.Type // type of the response argument
39 | }
40 |
41 | // ----------------------------------------------------------------------------
42 | // serviceMap
43 | // ----------------------------------------------------------------------------
44 |
45 | // serviceMap is a registry for services.
46 | type serviceMap struct {
47 | mutex sync.Mutex
48 | services map[string]*service
49 | }
50 |
51 | // register adds a new service using reflection to extract its methods.
52 | func (m *serviceMap) register(rcvr interface{}, name string) error {
53 | // Setup service.
54 | s := &service{
55 | name: name,
56 | rcvr: reflect.ValueOf(rcvr),
57 | rcvrType: reflect.TypeOf(rcvr),
58 | methods: make(map[string]*serviceMethod),
59 | }
60 | if name == "" {
61 | s.name = reflect.Indirect(s.rcvr).Type().Name()
62 | if !isExported(s.name) {
63 | return fmt.Errorf("rpc: type %q is not exported", s.name)
64 | }
65 | }
66 | if s.name == "" {
67 | return fmt.Errorf("rpc: no service name for type %q",
68 | s.rcvrType.String())
69 | }
70 | // Setup methods.
71 | for i := 0; i < s.rcvrType.NumMethod(); i++ {
72 | method := s.rcvrType.Method(i)
73 | mtype := method.Type
74 | // Method must be exported.
75 | if method.PkgPath != "" {
76 | continue
77 | }
78 | // Method needs four ins: receiver, *http.Request, *args, *reply.
79 | if mtype.NumIn() != 4 {
80 | continue
81 | }
82 | // First argument must be a pointer and must be http.Request.
83 | reqType := mtype.In(1)
84 | if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest {
85 | continue
86 | }
87 | // Second argument must be a pointer and must be exported.
88 | args := mtype.In(2)
89 | if args.Kind() != reflect.Ptr || !isExportedOrBuiltin(args) {
90 | continue
91 | }
92 | // Third argument must be a pointer and must be exported.
93 | reply := mtype.In(3)
94 | if reply.Kind() != reflect.Ptr || !isExportedOrBuiltin(reply) {
95 | continue
96 | }
97 | // Method needs one out: error.
98 | if mtype.NumOut() != 1 {
99 | continue
100 | }
101 | if returnType := mtype.Out(0); returnType != typeOfError {
102 | continue
103 | }
104 | s.methods[method.Name] = &serviceMethod{
105 | method: method,
106 | argsType: args.Elem(),
107 | replyType: reply.Elem(),
108 | }
109 | }
110 | if len(s.methods) == 0 {
111 | return fmt.Errorf("rpc: %q has no exported methods of suitable type",
112 | s.name)
113 | }
114 | // Add to the map.
115 | m.mutex.Lock()
116 | defer m.mutex.Unlock()
117 | if m.services == nil {
118 | m.services = make(map[string]*service)
119 | } else if _, ok := m.services[s.name]; ok {
120 | return fmt.Errorf("rpc: service already defined: %q", s.name)
121 | }
122 | m.services[s.name] = s
123 | return nil
124 | }
125 |
126 | // get returns a registered service given a method name.
127 | //
128 | // The method name uses a dotted notation as in "Service.Method".
129 | func (m *serviceMap) get(method string) (*service, *serviceMethod, error) {
130 | parts := strings.Split(method, ".")
131 | if len(parts) != 2 {
132 | err := fmt.Errorf("rpc: service/method request ill-formed: %q", method)
133 | return nil, nil, err
134 | }
135 | m.mutex.Lock()
136 | service := m.services[parts[0]]
137 | m.mutex.Unlock()
138 | if service == nil {
139 | err := fmt.Errorf("rpc: can't find service %q", method)
140 | return nil, nil, err
141 | }
142 | serviceMethod := service.methods[parts[1]]
143 | if serviceMethod == nil {
144 | err := fmt.Errorf("rpc: can't find method %q", method)
145 | return nil, nil, err
146 | }
147 | return service, serviceMethod, nil
148 | }
149 |
150 | // isExported returns true of a string is an exported (upper case) name.
151 | func isExported(name string) bool {
152 | rune, _ := utf8.DecodeRuneInString(name)
153 | return unicode.IsUpper(rune)
154 | }
155 |
156 | // isExportedOrBuiltin returns true if a type is exported or a builtin.
157 | func isExportedOrBuiltin(t reflect.Type) bool {
158 | for t.Kind() == reflect.Ptr {
159 | t = t.Elem()
160 | }
161 | // PkgPath will be non-empty even for an exported type,
162 | // so we need to check the type name as well.
163 | return isExported(t.Name()) || t.PkgPath() == ""
164 | }
165 |
--------------------------------------------------------------------------------
/v2/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package rpc
7 |
8 | import (
9 | "fmt"
10 | "net/http"
11 | "reflect"
12 | "strings"
13 | )
14 |
15 | // ----------------------------------------------------------------------------
16 | // Codec
17 | // ----------------------------------------------------------------------------
18 |
19 | // Codec creates a CodecRequest to process each request.
20 | type Codec interface {
21 | NewRequest(*http.Request) CodecRequest
22 | }
23 |
24 | // CodecRequest decodes a request and encodes a response using a specific
25 | // serialization scheme.
26 | type CodecRequest interface {
27 | // Reads the request and returns the RPC method name.
28 | Method() (string, error)
29 | // Reads the request filling the RPC method args.
30 | ReadRequest(interface{}) error
31 | // Writes the response using the RPC method reply.
32 | WriteResponse(http.ResponseWriter, interface{})
33 | // Writes an error produced by the server.
34 | WriteError(w http.ResponseWriter, status int, err error)
35 | }
36 |
37 | // ----------------------------------------------------------------------------
38 | // Server
39 | // ----------------------------------------------------------------------------
40 |
41 | // NewServer returns a new RPC server.
42 | func NewServer() *Server {
43 | return &Server{
44 | codecs: make(map[string]Codec),
45 | services: new(serviceMap),
46 | }
47 | }
48 |
49 | // Server serves registered RPC services using registered codecs.
50 | type Server struct {
51 | codecs map[string]Codec
52 | services *serviceMap
53 | }
54 |
55 | // RegisterCodec adds a new codec to the server.
56 | //
57 | // Codecs are defined to process a given serialization scheme, e.g., JSON or
58 | // XML. A codec is chosen based on the "Content-Type" header from the request,
59 | // excluding the charset definition.
60 | func (s *Server) RegisterCodec(codec Codec, contentType string) {
61 | s.codecs[strings.ToLower(contentType)] = codec
62 | }
63 |
64 | // RegisterService adds a new service to the server.
65 | //
66 | // The name parameter is optional: if empty it will be inferred from
67 | // the receiver type name.
68 | //
69 | // Methods from the receiver will be extracted if these rules are satisfied:
70 | //
71 | // - The receiver is exported (begins with an upper case letter) or local
72 | // (defined in the package registering the service).
73 | // - The method name is exported.
74 | // - The method has three arguments: *http.Request, *args, *reply.
75 | // - All three arguments are pointers.
76 | // - The second and third arguments are exported or local.
77 | // - The method has return type error.
78 | //
79 | // All other methods are ignored.
80 | func (s *Server) RegisterService(receiver interface{}, name string) error {
81 | return s.services.register(receiver, name)
82 | }
83 |
84 | // HasMethod returns true if the given method is registered.
85 | //
86 | // The method uses a dotted notation as in "Service.Method".
87 | func (s *Server) HasMethod(method string) bool {
88 | if _, _, err := s.services.get(method); err == nil {
89 | return true
90 | }
91 | return false
92 | }
93 |
94 | // ServeHTTP
95 | func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
96 | if r.Method != "POST" {
97 | WriteError(w, 405, "rpc: POST method required, received "+r.Method)
98 | return
99 | }
100 | contentType := r.Header.Get("Content-Type")
101 | idx := strings.Index(contentType, ";")
102 | if idx != -1 {
103 | contentType = contentType[:idx]
104 | }
105 | var codec Codec
106 | if contentType == "" && len(s.codecs) == 1 {
107 | // If Content-Type is not set and only one codec has been registered,
108 | // then default to that codec.
109 | for _, c := range s.codecs {
110 | codec = c
111 | }
112 | } else if codec = s.codecs[strings.ToLower(contentType)]; codec == nil {
113 | WriteError(w, 415, "rpc: unrecognized Content-Type: "+contentType)
114 | return
115 | }
116 | // Create a new codec request.
117 | codecReq := codec.NewRequest(r)
118 | // Get service method to be called.
119 | method, errMethod := codecReq.Method()
120 | if errMethod != nil {
121 | codecReq.WriteError(w, 400, errMethod)
122 | return
123 | }
124 | serviceSpec, methodSpec, errGet := s.services.get(method)
125 | if errGet != nil {
126 | codecReq.WriteError(w, 400, errGet)
127 | return
128 | }
129 | // Decode the args.
130 | args := reflect.New(methodSpec.argsType)
131 | if errRead := codecReq.ReadRequest(args.Interface()); errRead != nil {
132 | codecReq.WriteError(w, 400, errRead)
133 | return
134 | }
135 | // Call the service method.
136 | reply := reflect.New(methodSpec.replyType)
137 | errValue := methodSpec.method.Func.Call([]reflect.Value{
138 | serviceSpec.rcvr,
139 | reflect.ValueOf(r),
140 | args,
141 | reply,
142 | })
143 | // Cast the result to error if needed.
144 | var errResult error
145 | errInter := errValue[0].Interface()
146 | if errInter != nil {
147 | errResult = errInter.(error)
148 | }
149 | // Prevents Internet Explorer from MIME-sniffing a response away
150 | // from the declared content-type
151 | w.Header().Set("x-content-type-options", "nosniff")
152 | // Encode the response.
153 | if errResult == nil {
154 | codecReq.WriteResponse(w, reply.Interface())
155 | } else {
156 | codecReq.WriteError(w, 400, errResult)
157 | }
158 | }
159 |
160 | func WriteError(w http.ResponseWriter, status int, msg string) {
161 | w.WriteHeader(status)
162 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
163 | fmt.Fprint(w, msg)
164 | }
165 |
--------------------------------------------------------------------------------
/map.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package rpc
7 |
8 | import (
9 | "fmt"
10 | "net/http"
11 | "reflect"
12 | "strings"
13 | "sync"
14 | "unicode"
15 | "unicode/utf8"
16 | )
17 |
18 | var (
19 | // Precompute the reflect.Type of error and http.Request
20 | typeOfError = reflect.TypeOf((*error)(nil)).Elem()
21 | typeOfRequest = reflect.TypeOf((*http.Request)(nil)).Elem()
22 | )
23 |
24 | // ----------------------------------------------------------------------------
25 | // service
26 | // ----------------------------------------------------------------------------
27 |
28 | type service struct {
29 | name string // name of service
30 | rcvr reflect.Value // receiver of methods for the service
31 | rcvrType reflect.Type // type of the receiver
32 | methods map[string]*serviceMethod // registered methods
33 | passReq bool
34 | }
35 |
36 | type serviceMethod struct {
37 | method reflect.Method // receiver method
38 | argsType reflect.Type // type of the request argument
39 | replyType reflect.Type // type of the response argument
40 | }
41 |
42 | // ----------------------------------------------------------------------------
43 | // serviceMap
44 | // ----------------------------------------------------------------------------
45 |
46 | // serviceMap is a registry for services.
47 | type serviceMap struct {
48 | mutex sync.Mutex
49 | services map[string]*service
50 | }
51 |
52 | // register adds a new service using reflection to extract its methods.
53 | func (m *serviceMap) register(rcvr interface{}, name string, passReq bool) error {
54 | // Setup service.
55 | s := &service{
56 | name: name,
57 | rcvr: reflect.ValueOf(rcvr),
58 | rcvrType: reflect.TypeOf(rcvr),
59 | methods: make(map[string]*serviceMethod),
60 | passReq: passReq,
61 | }
62 | if name == "" {
63 | s.name = reflect.Indirect(s.rcvr).Type().Name()
64 | if !isExported(s.name) {
65 | return fmt.Errorf("rpc: type %q is not exported", s.name)
66 | }
67 | }
68 | if s.name == "" {
69 | return fmt.Errorf("rpc: no service name for type %q",
70 | s.rcvrType.String())
71 | }
72 | // Setup methods.
73 | for i := 0; i < s.rcvrType.NumMethod(); i++ {
74 | method := s.rcvrType.Method(i)
75 | mtype := method.Type
76 |
77 | // offset the parameter indexes by one if the
78 | // service methods accept an HTTP request pointer
79 | var paramOffset int
80 | if passReq {
81 | paramOffset = 1
82 | } else {
83 | paramOffset = 0
84 | }
85 |
86 | // Method must be exported.
87 | if method.PkgPath != "" {
88 | continue
89 | }
90 | // Method needs four ins: receiver, *http.Request, *args, *reply.
91 | if mtype.NumIn() != 3+paramOffset {
92 | continue
93 | }
94 |
95 | // If the service methods accept an HTTP request pointer
96 | if passReq {
97 | // First argument must be a pointer and must be http.Request.
98 | reqType := mtype.In(1)
99 | if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest {
100 | continue
101 | }
102 | }
103 | // Next argument must be a pointer and must be exported.
104 | args := mtype.In(1 + paramOffset)
105 | if args.Kind() != reflect.Ptr || !isExportedOrBuiltin(args) {
106 | continue
107 | }
108 | // Next argument must be a pointer and must be exported.
109 | reply := mtype.In(2 + paramOffset)
110 | if reply.Kind() != reflect.Ptr || !isExportedOrBuiltin(reply) {
111 | continue
112 | }
113 | // Method needs one out: error.
114 | if mtype.NumOut() != 1 {
115 | continue
116 | }
117 | if returnType := mtype.Out(0); returnType != typeOfError {
118 | continue
119 | }
120 | s.methods[method.Name] = &serviceMethod{
121 | method: method,
122 | argsType: args.Elem(),
123 | replyType: reply.Elem(),
124 | }
125 | }
126 | if len(s.methods) == 0 {
127 | return fmt.Errorf("rpc: %q has no exported methods of suitable type",
128 | s.name)
129 | }
130 | // Add to the map.
131 | m.mutex.Lock()
132 | defer m.mutex.Unlock()
133 | if m.services == nil {
134 | m.services = make(map[string]*service)
135 | } else if _, ok := m.services[s.name]; ok {
136 | return fmt.Errorf("rpc: service already defined: %q", s.name)
137 | }
138 | m.services[s.name] = s
139 | return nil
140 | }
141 |
142 | // get returns a registered service given a method name.
143 | //
144 | // The method name uses a dotted notation as in "Service.Method".
145 | func (m *serviceMap) get(method string) (*service, *serviceMethod, error) {
146 | parts := strings.Split(method, ".")
147 | if len(parts) != 2 {
148 | err := fmt.Errorf("rpc: service/method request ill-formed: %q", method)
149 | return nil, nil, err
150 | }
151 | m.mutex.Lock()
152 | service := m.services[parts[0]]
153 | m.mutex.Unlock()
154 | if service == nil {
155 | err := fmt.Errorf("rpc: can't find service %q", method)
156 | return nil, nil, err
157 | }
158 | serviceMethod := service.methods[parts[1]]
159 | if serviceMethod == nil {
160 | err := fmt.Errorf("rpc: can't find method %q", method)
161 | return nil, nil, err
162 | }
163 | return service, serviceMethod, nil
164 | }
165 |
166 | // isExported returns true of a string is an exported (upper case) name.
167 | func isExported(name string) bool {
168 | rune, _ := utf8.DecodeRuneInString(name)
169 | return unicode.IsUpper(rune)
170 | }
171 |
172 | // isExportedOrBuiltin returns true if a type is exported or a builtin.
173 | func isExportedOrBuiltin(t reflect.Type) bool {
174 | for t.Kind() == reflect.Ptr {
175 | t = t.Elem()
176 | }
177 | // PkgPath will be non-empty even for an exported type,
178 | // so we need to check the type name as well.
179 | return isExported(t.Name()) || t.PkgPath() == ""
180 | }
181 |
--------------------------------------------------------------------------------
/v2/json2/json_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json2
7 |
8 | import (
9 | "bytes"
10 | "encoding/json"
11 | "errors"
12 | "net/http"
13 | "testing"
14 |
15 | "github.com/gorilla/rpc/v2"
16 | )
17 |
18 | // ResponseRecorder is an implementation of http.ResponseWriter that
19 | // records its mutations for later inspection in tests.
20 | type ResponseRecorder struct {
21 | Code int // the HTTP response code from WriteHeader
22 | HeaderMap http.Header // the HTTP response headers
23 | Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
24 | Flushed bool
25 | }
26 |
27 | // NewRecorder returns an initialized ResponseRecorder.
28 | func NewRecorder() *ResponseRecorder {
29 | return &ResponseRecorder{
30 | HeaderMap: make(http.Header),
31 | Body: new(bytes.Buffer),
32 | }
33 | }
34 |
35 | // DefaultRemoteAddr is the default remote address to return in RemoteAddr if
36 | // an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
37 | const DefaultRemoteAddr = "1.2.3.4"
38 |
39 | // Header returns the response headers.
40 | func (rw *ResponseRecorder) Header() http.Header {
41 | return rw.HeaderMap
42 | }
43 |
44 | // Write always succeeds and writes to rw.Body, if not nil.
45 | func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
46 | if rw.Body != nil {
47 | rw.Body.Write(buf)
48 | }
49 | if rw.Code == 0 {
50 | rw.Code = http.StatusOK
51 | }
52 | return len(buf), nil
53 | }
54 |
55 | // WriteHeader sets rw.Code.
56 | func (rw *ResponseRecorder) WriteHeader(code int) {
57 | rw.Code = code
58 | }
59 |
60 | // Flush sets rw.Flushed to true.
61 | func (rw *ResponseRecorder) Flush() {
62 | rw.Flushed = true
63 | }
64 |
65 | // ----------------------------------------------------------------------------
66 |
67 | var ErrResponseError = errors.New("response error")
68 |
69 | type Service1Request struct {
70 | A int
71 | B int
72 | }
73 |
74 | type Service1NoParamsRequest struct {
75 | V string `json:"jsonrpc"`
76 | M string `json:"method"`
77 | ID uint64 `json:"id"`
78 | }
79 |
80 | type Service1ParamsArrayRequest struct {
81 | V string `json:"jsonrpc"`
82 | P []struct {
83 | T string
84 | } `json:"params"`
85 | M string `json:"method"`
86 | ID uint64 `json:"id"`
87 | }
88 |
89 | type Service1Response struct {
90 | Result int
91 | }
92 |
93 | type Service1 struct {
94 | }
95 |
96 | const Service1DefaultResponse = 9999
97 |
98 | func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
99 | if req.A == 0 && req.B == 0 {
100 | // Sentinel value for test with no params.
101 | res.Result = Service1DefaultResponse
102 | } else {
103 | res.Result = req.A * req.B
104 | }
105 | return nil
106 | }
107 |
108 | func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
109 | return ErrResponseError
110 | }
111 |
112 | func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) error {
113 | if !s.HasMethod(method) {
114 | t.Fatal("Expected to be registered:", method)
115 | }
116 |
117 | buf, _ := EncodeClientRequest(method, req)
118 | body := bytes.NewBuffer(buf)
119 | r, _ := http.NewRequest("POST", "http://localhost:8080/", body)
120 | r.Header.Set("Content-Type", "application/json")
121 |
122 | w := NewRecorder()
123 | s.ServeHTTP(w, r)
124 |
125 | return DecodeClientResponse(w.Body, res)
126 | }
127 |
128 | func executeRaw(t *testing.T, s *rpc.Server, req interface{}, res interface{}) error {
129 | j, _ := json.Marshal(req)
130 | r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(j))
131 | r.Header.Set("Content-Type", "application/json")
132 |
133 | w := NewRecorder()
134 | s.ServeHTTP(w, r)
135 |
136 | return DecodeClientResponse(w.Body, res)
137 | }
138 |
139 | func TestService(t *testing.T) {
140 | s := rpc.NewServer()
141 | s.RegisterCodec(NewCodec(), "application/json")
142 | s.RegisterService(new(Service1), "")
143 |
144 | var res Service1Response
145 | if err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
146 | t.Error("Expected err to be nil, but got:", err)
147 | }
148 | if res.Result != 8 {
149 | t.Errorf("Wrong response: %v.", res.Result)
150 | }
151 |
152 | if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil {
153 | t.Errorf("Expected to get %q, but got nil", ErrResponseError)
154 | } else if err.Error() != ErrResponseError.Error() {
155 | t.Errorf("Expected to get %q, but got %q", ErrResponseError, err)
156 | }
157 |
158 | // No parameters.
159 | res = Service1Response{}
160 | if err := executeRaw(t, s, &Service1NoParamsRequest{"2.0", "Service1.Multiply", 1}, &res); err != nil {
161 | t.Error(err)
162 | }
163 | if res.Result != Service1DefaultResponse {
164 | t.Errorf("Wrong response: got %v, want %v", res.Result, Service1DefaultResponse)
165 | }
166 |
167 | // Parameters as by-position.
168 | res = Service1Response{}
169 | req := Service1ParamsArrayRequest{
170 | V: "2.0",
171 | P: []struct {
172 | T string
173 | }{{
174 | T: "test",
175 | }},
176 | M: "Service1.Multiply",
177 | ID: 1,
178 | }
179 | if err := executeRaw(t, s, &req, &res); err != nil {
180 | t.Error(err)
181 | }
182 | if res.Result != Service1DefaultResponse {
183 | t.Errorf("Wrong response: got %v, want %v", res.Result, Service1DefaultResponse)
184 | }
185 | }
186 |
187 | func TestDecodeNullResult(t *testing.T) {
188 | data := `{"jsonrpc": "2.0", "id": 12345, "result": null}`
189 | reader := bytes.NewReader([]byte(data))
190 | var result interface{}
191 |
192 | err := DecodeClientResponse(reader, &result)
193 |
194 | if err != ErrNullResult {
195 | t.Error("Expected err no be ErrNullResult, but got:", err)
196 | }
197 |
198 | if result != nil {
199 | t.Error("Expected result to be nil, but got:", result)
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/server_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package rpc
7 |
8 | import (
9 | "net/http"
10 | "strconv"
11 | "testing"
12 | )
13 |
14 | type Service1Request struct {
15 | A int
16 | B int
17 | }
18 |
19 | type Service1Response struct {
20 | Result int
21 | }
22 |
23 | type Service1 struct {
24 | }
25 |
26 | func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
27 | res.Result = req.A * req.B
28 | return nil
29 | }
30 |
31 | func (t *Service1) Add(req *Service1Request, res *Service1Response) error {
32 | res.Result = req.A + req.B
33 | return nil
34 | }
35 |
36 | type Service2 struct {
37 | }
38 |
39 | func TestRegisterService(t *testing.T) {
40 | var err error
41 | s := NewServer()
42 | service1 := new(Service1)
43 | service2 := new(Service2)
44 |
45 | // Inferred name.
46 | err = s.RegisterService(service1, "")
47 | if err != nil || !s.HasMethod("Service1.Multiply") {
48 | t.Errorf("Expected to be registered: Service1.Multiply")
49 | }
50 | // Provided name.
51 | err = s.RegisterService(service1, "Foo")
52 | if err != nil || !s.HasMethod("Foo.Multiply") {
53 | t.Errorf("Expected to be registered: Foo.Multiply")
54 | }
55 | // No methods.
56 | err = s.RegisterService(service2, "")
57 | if err == nil {
58 | t.Errorf("Expected error on service2")
59 | }
60 | }
61 |
62 | func TestRegisterTCPService(t *testing.T) {
63 | var err error
64 | s := NewServer()
65 | service1 := new(Service1)
66 | service2 := new(Service2)
67 |
68 | // Inferred name.
69 | err = s.RegisterTCPService(service1, "")
70 | if err != nil || !s.HasMethod("Service1.Add") {
71 | t.Errorf("Expected to be registered: Service1.Add")
72 | }
73 | // Provided name.
74 | err = s.RegisterTCPService(service1, "Foo")
75 | if err != nil || !s.HasMethod("Foo.Add") {
76 | t.Errorf("Expected to be registered: Foo.Add")
77 | }
78 | // No methods.
79 | err = s.RegisterTCPService(service2, "")
80 | if err == nil {
81 | t.Errorf("Expected error on service2")
82 | }
83 | }
84 |
85 | // MockCodec decodes to Service1.Multiply.
86 | type MockCodec struct {
87 | A, B int
88 | }
89 |
90 | func (c MockCodec) NewRequest(*http.Request) CodecRequest {
91 | return MockCodecRequest{c.A, c.B}
92 | }
93 |
94 | type MockCodecRequest struct {
95 | A, B int
96 | }
97 |
98 | func (r MockCodecRequest) Method() (string, error) {
99 | return "Service1.Multiply", nil
100 | }
101 |
102 | func (r MockCodecRequest) ReadRequest(args interface{}) error {
103 | req := args.(*Service1Request)
104 | req.A, req.B = r.A, r.B
105 | return nil
106 | }
107 |
108 | func (r MockCodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}, methodErr error) error {
109 | if methodErr != nil {
110 | w.Write([]byte(methodErr.Error()))
111 | } else {
112 | res := reply.(*Service1Response)
113 | w.Write([]byte(strconv.Itoa(res.Result)))
114 | }
115 | return nil
116 | }
117 |
118 | type MockResponseWriter struct {
119 | header http.Header
120 | Status int
121 | Body string
122 | }
123 |
124 | func NewMockResponseWriter() *MockResponseWriter {
125 | header := make(http.Header)
126 | return &MockResponseWriter{header: header}
127 | }
128 |
129 | func (w *MockResponseWriter) Header() http.Header {
130 | return w.header
131 | }
132 |
133 | func (w *MockResponseWriter) Write(p []byte) (int, error) {
134 | w.Body = string(p)
135 | if w.Status == 0 {
136 | w.Status = 200
137 | }
138 | return len(p), nil
139 | }
140 |
141 | func (w *MockResponseWriter) WriteHeader(status int) {
142 | w.Status = status
143 | }
144 |
145 | func TestServeHTTP(t *testing.T) {
146 | const (
147 | A = 2
148 | B = 3
149 | )
150 | expected := A * B
151 |
152 | s := NewServer()
153 | s.RegisterService(new(Service1), "")
154 | s.RegisterCodec(MockCodec{A, B}, "mock")
155 |
156 | r, err := http.NewRequest("POST", "", nil)
157 | if err != nil {
158 | t.Fatal(err)
159 | }
160 | r.Header.Set("Content-Type", "mock; dummy")
161 | w := NewMockResponseWriter()
162 | s.ServeHTTP(w, r)
163 | if w.Status != 200 {
164 | t.Errorf("Status was %d, should be 200.", w.Status)
165 | }
166 | if w.Body != strconv.Itoa(expected) {
167 | t.Errorf("Response body was %s, should be %s.", w.Body, strconv.Itoa(expected))
168 | }
169 |
170 | // Test wrong Content-Type
171 | r.Header.Set("Content-Type", "invalid")
172 | w = NewMockResponseWriter()
173 | s.ServeHTTP(w, r)
174 | if w.Status != 415 {
175 | t.Errorf("Status was %d, should be 415.", w.Status)
176 | }
177 | if w.Body != "rpc: unrecognized Content-Type: invalid" {
178 | t.Errorf("Wrong response body.")
179 | }
180 |
181 | // Test omitted Content-Type; codec should default to the sole registered one.
182 | r.Header.Del("Content-Type")
183 | w = NewMockResponseWriter()
184 | s.ServeHTTP(w, r)
185 | if w.Status != 200 {
186 | t.Errorf("Status was %d, should be 200.", w.Status)
187 | }
188 | if w.Body != strconv.Itoa(expected) {
189 | t.Errorf("Response body was %s, should be %s.", w.Body, strconv.Itoa(expected))
190 | }
191 | }
192 |
193 | func TestInterception(t *testing.T) {
194 | const (
195 | A = 2
196 | B = 3
197 | )
198 | expected := A * B
199 |
200 | r2, err := http.NewRequest("POST", "mocked/request", nil)
201 | if err != nil {
202 | t.Fatal(err)
203 | }
204 |
205 | s := NewServer()
206 | s.RegisterService(new(Service1), "")
207 | s.RegisterCodec(MockCodec{A, B}, "mock")
208 | s.RegisterInterceptFunc(func(i *RequestInfo) *http.Request {
209 | return r2
210 | })
211 | s.RegisterAfterFunc(func(i *RequestInfo) {
212 | if i.Request != r2 {
213 | t.Errorf("Request was %v, should be %v.", i.Request, r2)
214 | }
215 | })
216 |
217 | r, err := http.NewRequest("POST", "", nil)
218 | if err != nil {
219 | t.Fatal(err)
220 | }
221 | r.Header.Set("Content-Type", "mock; dummy")
222 | w := NewMockResponseWriter()
223 | s.ServeHTTP(w, r)
224 | if w.Status != 200 {
225 | t.Errorf("Status was %d, should be 200.", w.Status)
226 | }
227 | if w.Body != strconv.Itoa(expected) {
228 | t.Errorf("Response body was %s, should be %s.", w.Body, strconv.Itoa(expected))
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/v2/json2/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package json2
7 |
8 | import (
9 | "encoding/json"
10 | "net/http"
11 |
12 | "github.com/gorilla/rpc/v2"
13 | )
14 |
15 | var null = json.RawMessage([]byte("null"))
16 | var Version = "2.0"
17 |
18 | // ----------------------------------------------------------------------------
19 | // Request and Response
20 | // ----------------------------------------------------------------------------
21 |
22 | // serverRequest represents a JSON-RPC request received by the server.
23 | type serverRequest struct {
24 | // JSON-RPC protocol.
25 | Version string `json:"jsonrpc"`
26 |
27 | // A String containing the name of the method to be invoked.
28 | Method string `json:"method"`
29 |
30 | // A Structured value to pass as arguments to the method.
31 | Params *json.RawMessage `json:"params"`
32 |
33 | // The request id. MUST be a string, number or null.
34 | // Our implementation will not do type checking for id.
35 | // It will be copied as it is.
36 | Id *json.RawMessage `json:"id"`
37 | }
38 |
39 | // serverResponse represents a JSON-RPC response returned by the server.
40 | type serverResponse struct {
41 | // JSON-RPC protocol.
42 | Version string `json:"jsonrpc"`
43 |
44 | // The Object that was returned by the invoked method. This must be null
45 | // in case there was an error invoking the method.
46 | // As per spec the member will be omitted if there was an error.
47 | Result interface{} `json:"result,omitempty"`
48 |
49 | // An Error object if there was an error invoking the method. It must be
50 | // null if there was no error.
51 | // As per spec the member will be omitted if there was no error.
52 | Error *Error `json:"error,omitempty"`
53 |
54 | // This must be the same id as the request it is responding to.
55 | Id *json.RawMessage `json:"id"`
56 | }
57 |
58 | // ----------------------------------------------------------------------------
59 | // Codec
60 | // ----------------------------------------------------------------------------
61 |
62 | // NewcustomCodec returns a new JSON Codec based on passed encoder selector.
63 | func NewCustomCodec(encSel rpc.EncoderSelector) *Codec {
64 | return &Codec{encSel: encSel}
65 | }
66 |
67 | // NewCodec returns a new JSON Codec.
68 | func NewCodec() *Codec {
69 | return NewCustomCodec(rpc.DefaultEncoderSelector)
70 | }
71 |
72 | // Codec creates a CodecRequest to process each request.
73 | type Codec struct {
74 | encSel rpc.EncoderSelector
75 | }
76 |
77 | // NewRequest returns a CodecRequest.
78 | func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
79 | return newCodecRequest(r, c.encSel.Select(r))
80 | }
81 |
82 | // ----------------------------------------------------------------------------
83 | // CodecRequest
84 | // ----------------------------------------------------------------------------
85 |
86 | // newCodecRequest returns a new CodecRequest.
87 | func newCodecRequest(r *http.Request, encoder rpc.Encoder) rpc.CodecRequest {
88 | // Decode the request body and check if RPC method is valid.
89 | req := new(serverRequest)
90 | err := json.NewDecoder(r.Body).Decode(req)
91 | if err != nil {
92 | err = &Error{
93 | Code: E_PARSE,
94 | Message: err.Error(),
95 | Data: req,
96 | }
97 | }
98 | if req.Version != Version {
99 | err = &Error{
100 | Code: E_INVALID_REQ,
101 | Message: "jsonrpc must be " + Version,
102 | Data: req,
103 | }
104 | }
105 | r.Body.Close()
106 | return &CodecRequest{request: req, err: err, encoder: encoder}
107 | }
108 |
109 | // CodecRequest decodes and encodes a single request.
110 | type CodecRequest struct {
111 | request *serverRequest
112 | err error
113 | encoder rpc.Encoder
114 | }
115 |
116 | // Method returns the RPC method for the current request.
117 | //
118 | // The method uses a dotted notation as in "Service.Method".
119 | func (c *CodecRequest) Method() (string, error) {
120 | if c.err == nil {
121 | return c.request.Method, nil
122 | }
123 | return "", c.err
124 | }
125 |
126 | // ReadRequest fills the request object for the RPC method.
127 | //
128 | // ReadRequest parses request parameters in two supported forms in
129 | // accordance with http://www.jsonrpc.org/specification#parameter_structures
130 | //
131 | // by-position: params MUST be an Array, containing the
132 | // values in the Server expected order.
133 | //
134 | // by-name: params MUST be an Object, with member names
135 | // that match the Server expected parameter names. The
136 | // absence of expected names MAY result in an error being
137 | // generated. The names MUST match exactly, including
138 | // case, to the method's expected parameters.
139 | func (c *CodecRequest) ReadRequest(args interface{}) error {
140 | if c.err == nil && c.request.Params != nil {
141 | // Note: if c.request.Params is nil it's not an error, it's an optional member.
142 | // JSON params structured object. Unmarshal to the args object.
143 | if err := json.Unmarshal(*c.request.Params, args); err != nil {
144 | // Clearly JSON params is not a structured object,
145 | // fallback and attempt an unmarshal with JSON params as
146 | // array value and RPC params is struct. Unmarshal into
147 | // array containing the request struct.
148 | params := [1]interface{}{args}
149 | if err = json.Unmarshal(*c.request.Params, ¶ms); err != nil {
150 | c.err = &Error{
151 | Code: E_INVALID_REQ,
152 | Message: err.Error(),
153 | Data: c.request.Params,
154 | }
155 | }
156 | }
157 | }
158 | return c.err
159 | }
160 |
161 | // WriteResponse encodes the response and writes it to the ResponseWriter.
162 | func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
163 | res := &serverResponse{
164 | Version: Version,
165 | Result: reply,
166 | Id: c.request.Id,
167 | }
168 | c.writeServerResponse(w, res)
169 | }
170 |
171 | func (c *CodecRequest) WriteError(w http.ResponseWriter, status int, err error) {
172 | jsonErr, ok := err.(*Error)
173 | if !ok {
174 | jsonErr = &Error{
175 | Code: E_SERVER,
176 | Message: err.Error(),
177 | }
178 | }
179 | res := &serverResponse{
180 | Version: Version,
181 | Error: jsonErr,
182 | Id: c.request.Id,
183 | }
184 | c.writeServerResponse(w, res)
185 | }
186 |
187 | func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, res *serverResponse) {
188 | // Id is null for notifications and they don't have a response.
189 | if c.request.Id != nil {
190 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
191 | encoder := json.NewEncoder(c.encoder.Encode(w))
192 | err := encoder.Encode(res)
193 |
194 | // Not sure in which case will this happen. But seems harmless.
195 | if err != nil {
196 | rpc.WriteError(w, 400, err.Error())
197 | }
198 | }
199 | }
200 |
201 | type EmptyResponse struct {
202 | }
203 |
--------------------------------------------------------------------------------
/server.go:
--------------------------------------------------------------------------------
1 | // Copyright 2009 The Go Authors. All rights reserved.
2 | // Copyright 2012 The Gorilla Authors. All rights reserved.
3 | // Use of this source code is governed by a BSD-style
4 | // license that can be found in the LICENSE file.
5 |
6 | package rpc
7 |
8 | import (
9 | "fmt"
10 | "net/http"
11 | "reflect"
12 | "strings"
13 | )
14 |
15 | // ----------------------------------------------------------------------------
16 | // Codec
17 | // ----------------------------------------------------------------------------
18 |
19 | // Codec creates a CodecRequest to process each request.
20 | type Codec interface {
21 | NewRequest(*http.Request) CodecRequest
22 | }
23 |
24 | // CodecRequest decodes a request and encodes a response using a specific
25 | // serialization scheme.
26 | type CodecRequest interface {
27 | // Reads request and returns the RPC method name.
28 | Method() (string, error)
29 | // Reads request filling the RPC method args.
30 | ReadRequest(interface{}) error
31 | // Writes response using the RPC method reply. The error parameter is
32 | // the error returned by the method call, if any.
33 | WriteResponse(http.ResponseWriter, interface{}, error) error
34 | }
35 |
36 | // ----------------------------------------------------------------------------
37 | // Server
38 | // ----------------------------------------------------------------------------
39 |
40 | // NewServer returns a new RPC server.
41 | func NewServer() *Server {
42 | return &Server{
43 | codecs: make(map[string]Codec),
44 | services: new(serviceMap),
45 | }
46 | }
47 |
48 | // RequestInfo contains all the information we pass to before/after functions
49 | type RequestInfo struct {
50 | Method string
51 | Error error
52 | Request *http.Request
53 | StatusCode int
54 | }
55 |
56 | // Server serves registered RPC services using registered codecs.
57 | type Server struct {
58 | codecs map[string]Codec
59 | services *serviceMap
60 | interceptFunc func(i *RequestInfo) *http.Request
61 | beforeFunc func(i *RequestInfo)
62 | afterFunc func(i *RequestInfo)
63 | }
64 |
65 | // RegisterCodec adds a new codec to the server.
66 | //
67 | // Codecs are defined to process a given serialization scheme, e.g., JSON or
68 | // XML. A codec is chosen based on the "Content-Type" header from the request,
69 | // excluding the charset definition.
70 | func (s *Server) RegisterCodec(codec Codec, contentType string) {
71 | s.codecs[strings.ToLower(contentType)] = codec
72 | }
73 |
74 | // RegisterService adds a new service to the server.
75 | //
76 | // The name parameter is optional: if empty it will be inferred from
77 | // the receiver type name.
78 | //
79 | // Methods from the receiver will be extracted if these rules are satisfied:
80 | //
81 | // - The receiver is exported (begins with an upper case letter) or local
82 | // (defined in the package registering the service).
83 | // - The method name is exported.
84 | // - The method has three arguments: *http.Request, *args, *reply.
85 | // - All three arguments are pointers.
86 | // - The second and third arguments are exported or local.
87 | // - The method has return type error.
88 | //
89 | // All other methods are ignored.
90 | func (s *Server) RegisterService(receiver interface{}, name string) error {
91 | return s.services.register(receiver, name, true)
92 | }
93 |
94 | // RegisterTCPService adds a new TCP service to the server.
95 | // No HTTP request struct will be passed to the service methods.
96 | //
97 | // The name parameter is optional: if empty it will be inferred from
98 | // the receiver type name.
99 | //
100 | // Methods from the receiver will be extracted if these rules are satisfied:
101 | //
102 | // - The receiver is exported (begins with an upper case letter) or local
103 | // (defined in the package registering the service).
104 | // - The method name is exported.
105 | // - The method has two arguments: *args, *reply.
106 | // - Both arguments are pointers.
107 | // - Both arguments are exported or local.
108 | // - The method has return type error.
109 | //
110 | // All other methods are ignored.
111 | func (s *Server) RegisterTCPService(receiver interface{}, name string) error {
112 | return s.services.register(receiver, name, false)
113 | }
114 |
115 | // HasMethod returns true if the given method is registered.
116 | //
117 | // The method uses a dotted notation as in "Service.Method".
118 | func (s *Server) HasMethod(method string) bool {
119 | if _, _, err := s.services.get(method); err == nil {
120 | return true
121 | }
122 | return false
123 | }
124 |
125 | // RegisterInterceptFunc registers the specified function as the function
126 | // that will be called before every request. The function is allowed to intercept
127 | // the request e.g. add values to the context.
128 | //
129 | // Note: Only one function can be registered, subsequent calls to this
130 | // method will overwrite all the previous functions.
131 | func (s *Server) RegisterInterceptFunc(f func(i *RequestInfo) *http.Request) {
132 | s.interceptFunc = f
133 | }
134 |
135 | // RegisterBeforeFunc registers the specified function as the function
136 | // that will be called before every request.
137 | //
138 | // Note: Only one function can be registered, subsequent calls to this
139 | // method will overwrite all the previous functions.
140 | func (s *Server) RegisterBeforeFunc(f func(i *RequestInfo)) {
141 | s.beforeFunc = f
142 | }
143 |
144 | // RegisterAfterFunc registers the specified function as the function
145 | // that will be called after every request
146 | //
147 | // Note: Only one function can be registered, subsequent calls to this
148 | // method will overwrite all the previous functions.
149 | func (s *Server) RegisterAfterFunc(f func(i *RequestInfo)) {
150 | s.afterFunc = f
151 | }
152 |
153 | // ServeHTTP
154 | func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
155 | if r.Method != "POST" {
156 | s.writeError(w, 405, "rpc: POST method required, received "+r.Method)
157 | return
158 | }
159 | contentType := r.Header.Get("Content-Type")
160 | idx := strings.Index(contentType, ";")
161 | if idx != -1 {
162 | contentType = contentType[:idx]
163 | }
164 | var codec Codec
165 | if contentType == "" && len(s.codecs) == 1 {
166 | // If Content-Type is not set and only one codec has been registered,
167 | // then default to that codec.
168 | for _, c := range s.codecs {
169 | codec = c
170 | }
171 | } else if codec = s.codecs[strings.ToLower(contentType)]; codec == nil {
172 | s.writeError(w, 415, "rpc: unrecognized Content-Type: "+contentType)
173 | return
174 | }
175 | // Create a new codec request.
176 | codecReq := codec.NewRequest(r)
177 | // Get service method to be called.
178 | method, errMethod := codecReq.Method()
179 | if errMethod != nil {
180 | s.writeError(w, 400, errMethod.Error())
181 | return
182 | }
183 | serviceSpec, methodSpec, errGet := s.services.get(method)
184 | if errGet != nil {
185 | s.writeError(w, 400, errGet.Error())
186 | return
187 | }
188 | // Decode the args.
189 | args := reflect.New(methodSpec.argsType)
190 | if errRead := codecReq.ReadRequest(args.Interface()); errRead != nil {
191 | s.writeError(w, 400, errRead.Error())
192 | return
193 | }
194 |
195 | // Call the registered Intercept Function
196 | if s.interceptFunc != nil {
197 | req := s.interceptFunc(&RequestInfo{
198 | Request: r,
199 | Method: method,
200 | })
201 | if req != nil {
202 | r = req
203 | }
204 | }
205 | // Call the registered Before Function
206 | if s.beforeFunc != nil {
207 | s.beforeFunc(&RequestInfo{
208 | Request: r,
209 | Method: method,
210 | })
211 | }
212 |
213 | // Call the service method.
214 | reply := reflect.New(methodSpec.replyType)
215 |
216 | // omit the HTTP request if the service method doesn't accept it
217 | var errValue []reflect.Value
218 | if serviceSpec.passReq {
219 | errValue = methodSpec.method.Func.Call([]reflect.Value{
220 | serviceSpec.rcvr,
221 | reflect.ValueOf(r),
222 | args,
223 | reply,
224 | })
225 | } else {
226 | errValue = methodSpec.method.Func.Call([]reflect.Value{
227 | serviceSpec.rcvr,
228 | args,
229 | reply,
230 | })
231 | }
232 |
233 | // Cast the result to error if needed.
234 | var errResult error
235 | errInter := errValue[0].Interface()
236 | if errInter != nil {
237 | errResult = errInter.(error)
238 | }
239 |
240 | // Prevents Internet Explorer from MIME-sniffing a response away
241 | // from the declared content-type
242 | w.Header().Set("x-content-type-options", "nosniff")
243 | // Encode the response.
244 | if errWrite := codecReq.WriteResponse(w, reply.Interface(), errResult); errWrite != nil {
245 | s.writeError(w, 400, errWrite.Error())
246 | } else {
247 | // Call the registered After Function
248 | if s.afterFunc != nil {
249 | s.afterFunc(&RequestInfo{
250 | Request: r,
251 | Method: method,
252 | Error: errResult,
253 | StatusCode: 200,
254 | })
255 | }
256 | }
257 | }
258 |
259 | func (s *Server) writeError(w http.ResponseWriter, status int, msg string) {
260 | w.WriteHeader(status)
261 | w.Header().Set("Content-Type", "text/plain; charset=utf-8")
262 | fmt.Fprint(w, msg)
263 | if s.afterFunc != nil {
264 | s.afterFunc(&RequestInfo{
265 | Error: fmt.Errorf(msg),
266 | StatusCode: status,
267 | })
268 | }
269 | }
270 |
--------------------------------------------------------------------------------