├── .gitignore ├── v2 ├── json2 │ ├── testapp │ │ ├── README.md │ │ ├── index.html │ │ ├── counter.go │ │ ├── counter.js │ │ └── jquery.jsonrpc.js │ ├── error.go │ ├── client.go │ ├── json_test.go │ └── server.go ├── README.md ├── encoder_selector.go ├── protorpc │ ├── doc.go │ ├── protorpc_test.go │ └── server.go ├── LICENSE ├── json │ ├── doc.go │ ├── client.go │ ├── json_test.go │ └── server.go ├── compression_selector.go ├── doc.go ├── server_test.go ├── map.go └── server.go ├── README.md ├── .travis.yml ├── protorpc ├── doc.go ├── protorpc_test.go └── server.go ├── LICENSE ├── json ├── doc.go ├── client.go ├── json_test.go └── server.go ├── doc.go ├── map.go ├── server_test.go └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /v2/json2/testapp/README.md: -------------------------------------------------------------------------------- 1 | Counter (TestApp) 2 | ================= 3 | 4 | 1) Build 5 | 6 | $ go build counter.go 7 | $ ./counter 8 | 9 | 10 | 2) Navigate to localhost:65534 on the browser to access the app. 11 | -------------------------------------------------------------------------------- /v2/README.md: -------------------------------------------------------------------------------- 1 | rpc 2 | === 3 | 4 | gorilla/rpc is a foundation for RPC over HTTP services, providing access to the exported methods of an object through HTTP requests. 5 | 6 | Read the full documentation here: http://www.gorillatoolkit.org/pkg/rpc 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rpc 2 | === 3 | [![Build Status](https://travis-ci.org/gorilla/rpc.png?branch=master)](https://travis-ci.org/gorilla/rpc) 4 | 5 | gorilla/rpc is a foundation for RPC over HTTP services, providing access to the exported methods of an object through HTTP requests. 6 | 7 | Read the full documentation here: http://www.gorillatoolkit.org/pkg/rpc 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - go: 1.3 7 | - go: 1.4 8 | - go: 1.5 9 | - go: 1.6 10 | - go: 1.7 11 | - go: tip 12 | allow_failures: 13 | - go: tip 14 | 15 | script: 16 | - go get -t -v ./... 17 | - diff -u <(echo -n) <(gofmt -d .) 18 | - go vet $(go list ./... | grep -v /vendor/) 19 | - go test -v -race ./... 20 | -------------------------------------------------------------------------------- /v2/json2/testapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Counter 10 | 15 | 16 | 17 | 18 |
19 |
Incr
20 |
Get 
21 |
Nan 
22 |
23 |
24 |
25 |
26 | 28 |
29 |
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 | --------------------------------------------------------------------------------