mirror of
https://github.com/pgsty/minio.git
synced 2026-07-20 20:50:22 +03:00
Add and remove dependencies
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
rpc
|
||||
===
|
||||
|
||||
gorilla/rpc is a foundation for RPC over HTTP services, providing access to the exported methods of an object through HTTP requests.
|
||||
|
||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/rpc
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// gzipWriter writes and closes the gzip writer.
|
||||
type gzipWriter struct {
|
||||
w *gzip.Writer
|
||||
}
|
||||
|
||||
func (gw *gzipWriter) Write(p []byte) (n int, err error) {
|
||||
defer gw.w.Close()
|
||||
return gw.w.Write(p)
|
||||
}
|
||||
|
||||
// gzipEncoder implements the gzip compressed http encoder.
|
||||
type gzipEncoder struct {
|
||||
}
|
||||
|
||||
func (enc *gzipEncoder) Encode(w http.ResponseWriter) io.Writer {
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
return &gzipWriter{gzip.NewWriter(w)}
|
||||
}
|
||||
|
||||
// flateWriter writes and closes the flate writer.
|
||||
type flateWriter struct {
|
||||
w *flate.Writer
|
||||
}
|
||||
|
||||
func (fw *flateWriter) Write(p []byte) (n int, err error) {
|
||||
defer fw.w.Close()
|
||||
return fw.w.Write(p)
|
||||
}
|
||||
|
||||
// flateEncoder implements the flate compressed http encoder.
|
||||
type flateEncoder struct {
|
||||
}
|
||||
|
||||
func (enc *flateEncoder) Encode(w http.ResponseWriter) io.Writer {
|
||||
fw, err := flate.NewWriter(w, flate.DefaultCompression)
|
||||
if err != nil {
|
||||
return w
|
||||
}
|
||||
w.Header().Set("Content-Encoding", "deflate")
|
||||
return &flateWriter{fw}
|
||||
}
|
||||
|
||||
// CompressionSelector generates the compressed http encoder.
|
||||
type CompressionSelector struct {
|
||||
}
|
||||
|
||||
// acceptedEnc returns the first compression type in "Accept-Encoding" header
|
||||
// field of the request.
|
||||
func acceptedEnc(req *http.Request) string {
|
||||
encHeader := req.Header.Get("Accept-Encoding")
|
||||
if encHeader == "" {
|
||||
return ""
|
||||
}
|
||||
encTypes := strings.FieldsFunc(encHeader, func(r rune) bool {
|
||||
return unicode.IsSpace(r) || r == ','
|
||||
})
|
||||
for _, enc := range encTypes {
|
||||
if enc == "gzip" || enc == "deflate" {
|
||||
return enc
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Select method selects the correct compression encoder based on http HEADER.
|
||||
func (_ *CompressionSelector) Select(r *http.Request) Encoder {
|
||||
switch acceptedEnc(r) {
|
||||
case "gzip":
|
||||
return &gzipEncoder{}
|
||||
case "flate":
|
||||
return &flateEncoder{}
|
||||
}
|
||||
return DefaultEncoder
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package gorilla/rpc is a foundation for RPC over HTTP services, providing
|
||||
access to the exported methods of an object through HTTP requests.
|
||||
|
||||
This package derives from the standard net/rpc package but uses a single HTTP
|
||||
request per call instead of persistent connections. Other differences
|
||||
compared to net/rpc:
|
||||
|
||||
- Multiple codecs can be registered in the same server.
|
||||
- A codec is chosen based on the "Content-Type" header from the request.
|
||||
- Service methods also receive http.Request as parameter.
|
||||
- This package can be used on Google App Engine.
|
||||
|
||||
Let's setup a server and register a codec and service:
|
||||
|
||||
import (
|
||||
"http"
|
||||
"github.com/gorilla/rpc/v2"
|
||||
"github.com/gorilla/rpc/v2/json"
|
||||
)
|
||||
|
||||
func init() {
|
||||
s := rpc.NewServer()
|
||||
s.RegisterCodec(json.NewCodec(), "application/json")
|
||||
s.RegisterService(new(HelloService), "")
|
||||
http.Handle("/rpc", s)
|
||||
}
|
||||
|
||||
This server handles requests to the "/rpc" path using a JSON codec.
|
||||
A codec is tied to a content type. In the example above, the JSON codec is
|
||||
registered to serve requests with "application/json" as the value for the
|
||||
"Content-Type" header. If the header includes a charset definition, it is
|
||||
ignored; only the media-type part is taken into account.
|
||||
|
||||
A service can be registered using a name. If the name is empty, like in the
|
||||
example above, it will be inferred from the service type.
|
||||
|
||||
That's all about the server setup. Now let's define a simple service:
|
||||
|
||||
type HelloArgs struct {
|
||||
Who string
|
||||
}
|
||||
|
||||
type HelloReply struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
type HelloService struct {}
|
||||
|
||||
func (h *HelloService) Say(r *http.Request, args *HelloArgs, reply *HelloReply) error {
|
||||
reply.Message = "Hello, " + args.Who + "!"
|
||||
return nil
|
||||
}
|
||||
|
||||
The example above defines a service with a method "HelloService.Say" and
|
||||
the arguments and reply related to that method.
|
||||
|
||||
The service must be exported (begin with an upper case letter) or local
|
||||
(defined in the package registering the service).
|
||||
|
||||
When a service is registered, the server inspects the service methods
|
||||
and make available the ones that follow these rules:
|
||||
|
||||
- The method name is exported.
|
||||
- The method has three arguments: *http.Request, *args, *reply.
|
||||
- All three arguments are pointers.
|
||||
- The second and third arguments are exported or local.
|
||||
- The method has return type error.
|
||||
|
||||
All other methods are ignored.
|
||||
|
||||
Gorilla has packages with common RPC codecs. Check out their documentation:
|
||||
|
||||
JSON: http://gorilla-web.appspot.com/pkg/rpc/json
|
||||
*/
|
||||
package rpc
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Encoder interface contains the encoder for http response.
|
||||
// Eg. gzip, flate compressions.
|
||||
type Encoder interface {
|
||||
Encode(w http.ResponseWriter) io.Writer
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
}
|
||||
|
||||
func (_ *encoder) Encode(w http.ResponseWriter) io.Writer {
|
||||
return w
|
||||
}
|
||||
|
||||
var DefaultEncoder = &encoder{}
|
||||
|
||||
// EncoderSelector interface provides a way to select encoder using the http
|
||||
// request. Typically people can use this to check HEADER of the request and
|
||||
// figure out client capabilities.
|
||||
// Eg. "Accept-Encoding" tells about supported compressions.
|
||||
type EncoderSelector interface {
|
||||
Select(r *http.Request) Encoder
|
||||
}
|
||||
|
||||
type encoderSelector struct {
|
||||
}
|
||||
|
||||
func (_ *encoderSelector) Select(_ *http.Request) Encoder {
|
||||
return DefaultEncoder
|
||||
}
|
||||
|
||||
var DefaultEncoderSelector = &encoderSelector{}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012-2013 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request and Response
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// clientRequest represents a JSON-RPC request sent by a client.
|
||||
type clientRequest struct {
|
||||
// A String containing the name of the method to be invoked.
|
||||
Method string `json:"method"`
|
||||
// Object to pass as request parameter to the method.
|
||||
Params [1]interface{} `json:"params"`
|
||||
// The request id. This can be of any type. It is used to match the
|
||||
// response with the request that it is replying to.
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
// clientResponse represents a JSON-RPC response returned to a client.
|
||||
type clientResponse struct {
|
||||
Result *json.RawMessage `json:"result"`
|
||||
Error interface{} `json:"error"`
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
// EncodeClientRequest encodes parameters for a JSON-RPC client request.
|
||||
func EncodeClientRequest(method string, args interface{}) ([]byte, error) {
|
||||
c := &clientRequest{
|
||||
Method: method,
|
||||
Params: [1]interface{}{args},
|
||||
Id: uint64(rand.Int63()),
|
||||
}
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// DecodeClientResponse decodes the response body of a client request into
|
||||
// the interface reply.
|
||||
func DecodeClientResponse(r io.Reader, reply interface{}) error {
|
||||
var c clientResponse
|
||||
if err := json.NewDecoder(r).Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.Error != nil {
|
||||
return &Error{Data: c.Error}
|
||||
}
|
||||
return json.Unmarshal(*c.Result, reply)
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package gorilla/rpc/json provides a codec for JSON-RPC over HTTP services.
|
||||
|
||||
To register the codec in a RPC server:
|
||||
|
||||
import (
|
||||
"http"
|
||||
"github.com/gorilla/rpc/v2"
|
||||
"github.com/gorilla/rpc/v2/json"
|
||||
)
|
||||
|
||||
func init() {
|
||||
s := rpc.NewServer()
|
||||
s.RegisterCodec(json.NewCodec(), "application/json")
|
||||
// [...]
|
||||
http.Handle("/rpc", s)
|
||||
}
|
||||
|
||||
A codec is tied to a content type. In the example above, the server will use
|
||||
the JSON codec for requests with "application/json" as the value for the
|
||||
"Content-Type" header.
|
||||
|
||||
This package follows the JSON-RPC 1.0 specification:
|
||||
|
||||
http://json-rpc.org/wiki/specification
|
||||
|
||||
Request format is:
|
||||
|
||||
method:
|
||||
The name of the method to be invoked, as a string in dotted notation
|
||||
as in "Service.Method".
|
||||
params:
|
||||
An array with a single object to pass as argument to the method.
|
||||
id:
|
||||
The request id, a uint. It is used to match the response with the
|
||||
request that it is replying to.
|
||||
|
||||
Response format is:
|
||||
|
||||
result:
|
||||
The Object that was returned by the invoked method,
|
||||
or null in case there was an error invoking the method.
|
||||
error:
|
||||
An Error object if there was an error invoking the method,
|
||||
or null if there was no error.
|
||||
id:
|
||||
The same id as the request it is responding to.
|
||||
|
||||
Check the gorilla/rpc documentation for more details:
|
||||
|
||||
http://gorilla-web.appspot.com/pkg/rpc
|
||||
*/
|
||||
package json
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/rpc/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrResponseError = errors.New("response error")
|
||||
ErrResponseJsonError = &Error{Data: map[string]interface{}{
|
||||
"stackstrace": map[string]interface{}{"0": "foo()"},
|
||||
"error": "a message",
|
||||
}}
|
||||
)
|
||||
|
||||
type Service1Request struct {
|
||||
A int
|
||||
B int
|
||||
}
|
||||
|
||||
type Service1Response struct {
|
||||
Result int
|
||||
}
|
||||
|
||||
type Service1 struct {
|
||||
}
|
||||
|
||||
func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
res.Result = req.A * req.B
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
return ErrResponseError
|
||||
}
|
||||
|
||||
func (t *Service1) ResponseJsonError(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
return ErrResponseJsonError
|
||||
}
|
||||
|
||||
func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) error {
|
||||
if !s.HasMethod(method) {
|
||||
t.Fatal("Expected to be registered:", method)
|
||||
}
|
||||
|
||||
buf, _ := EncodeClientRequest(method, req)
|
||||
body := bytes.NewBuffer(buf)
|
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", body)
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
|
||||
return DecodeClientResponse(w.Body, res)
|
||||
}
|
||||
|
||||
func executeRaw(t *testing.T, s *rpc.Server, req json.RawMessage) (int, *bytes.Buffer) {
|
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(req))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
|
||||
return w.Code, w.Body
|
||||
}
|
||||
|
||||
func field(name string, blob json.RawMessage) (v interface{}, ok bool) {
|
||||
var obj map[string]interface{}
|
||||
if err := json.Unmarshal(blob, &obj); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
v, ok = obj[name]
|
||||
return
|
||||
}
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
s := rpc.NewServer()
|
||||
s.RegisterCodec(NewCodec(), "application/json")
|
||||
s.RegisterService(new(Service1), "")
|
||||
|
||||
var res Service1Response
|
||||
if err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
|
||||
t.Error("Expected err to be nil, but got", err)
|
||||
}
|
||||
if res.Result != 8 {
|
||||
t.Error("Expected res.Result to be 8, but got", res.Result)
|
||||
}
|
||||
if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil {
|
||||
t.Errorf("Expected to get %q, but got nil", ErrResponseError)
|
||||
} else if err.Error() != ErrResponseError.Error() {
|
||||
t.Errorf("Expected to get %q, but got %q", ErrResponseError, err)
|
||||
}
|
||||
if code, res := executeRaw(t, s, json.RawMessage(`{"method":"Service1.Multiply","params":null,"id":5}`)); code != 400 {
|
||||
t.Error("Expected response code to be 400, but got", code)
|
||||
} else if v, ok := field("result", res.Bytes()); !ok || v != nil {
|
||||
t.Errorf("Expected ok to be true and v to be nil, but got %v and %v", ok, v)
|
||||
}
|
||||
if err := execute(t, s, "Service1.ResponseJsonError", &Service1Request{4, 2}, &res); err == nil {
|
||||
t.Errorf("Expected to get %q, but got nil", ErrResponseError)
|
||||
} else if jsonErr, ok := err.(*Error); !ok {
|
||||
t.Error("Expected err to be of a *json.Error type")
|
||||
} else if !reflect.DeepEqual(jsonErr.Data, ErrResponseJsonError.Data) {
|
||||
t.Errorf("Expected jsonErr to be %q, but got %q", ErrResponseJsonError, jsonErr)
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/rpc/v2"
|
||||
)
|
||||
|
||||
var null = json.RawMessage([]byte("null"))
|
||||
|
||||
// An Error is a wrapper for a JSON interface value. It can be used by either
|
||||
// a service's handler func to write more complex JSON data to an error field
|
||||
// of a server's response, or by a client to read it.
|
||||
type Error struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%v", e.Data)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request and Response
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// serverRequest represents a JSON-RPC request received by the server.
|
||||
type serverRequest struct {
|
||||
// A String containing the name of the method to be invoked.
|
||||
Method string `json:"method"`
|
||||
// An Array of objects to pass as arguments to the method.
|
||||
Params *json.RawMessage `json:"params"`
|
||||
// The request id. This can be of any type. It is used to match the
|
||||
// response with the request that it is replying to.
|
||||
Id *json.RawMessage `json:"id"`
|
||||
}
|
||||
|
||||
// serverResponse represents a JSON-RPC response returned by the server.
|
||||
type serverResponse struct {
|
||||
// The Object that was returned by the invoked method. This must be null
|
||||
// in case there was an error invoking the method.
|
||||
Result interface{} `json:"result"`
|
||||
// An Error object if there was an error invoking the method. It must be
|
||||
// null if there was no error.
|
||||
Error interface{} `json:"error"`
|
||||
// This must be the same id as the request it is responding to.
|
||||
Id *json.RawMessage `json:"id"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Codec
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// NewCodec returns a new JSON Codec.
|
||||
func NewCodec() *Codec {
|
||||
return &Codec{}
|
||||
}
|
||||
|
||||
// Codec creates a CodecRequest to process each request.
|
||||
type Codec struct {
|
||||
}
|
||||
|
||||
// NewRequest returns a CodecRequest.
|
||||
func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
|
||||
return newCodecRequest(r)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CodecRequest
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// newCodecRequest returns a new CodecRequest.
|
||||
func newCodecRequest(r *http.Request) rpc.CodecRequest {
|
||||
// Decode the request body and check if RPC method is valid.
|
||||
req := new(serverRequest)
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
r.Body.Close()
|
||||
return &CodecRequest{request: req, err: err}
|
||||
}
|
||||
|
||||
// CodecRequest decodes and encodes a single request.
|
||||
type CodecRequest struct {
|
||||
request *serverRequest
|
||||
err error
|
||||
}
|
||||
|
||||
// Method returns the RPC method for the current request.
|
||||
//
|
||||
// The method uses a dotted notation as in "Service.Method".
|
||||
func (c *CodecRequest) Method() (string, error) {
|
||||
if c.err == nil {
|
||||
return c.request.Method, nil
|
||||
}
|
||||
return "", c.err
|
||||
}
|
||||
|
||||
// ReadRequest fills the request object for the RPC method.
|
||||
func (c *CodecRequest) ReadRequest(args interface{}) error {
|
||||
if c.err == nil {
|
||||
if c.request.Params != nil {
|
||||
// JSON params is array value. RPC params is struct.
|
||||
// Unmarshal into array containing the request struct.
|
||||
params := [1]interface{}{args}
|
||||
c.err = json.Unmarshal(*c.request.Params, ¶ms)
|
||||
} else {
|
||||
c.err = errors.New("rpc: method request ill-formed: missing params field")
|
||||
}
|
||||
}
|
||||
return c.err
|
||||
}
|
||||
|
||||
// WriteResponse encodes the response and writes it to the ResponseWriter.
|
||||
func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
|
||||
if c.request.Id != nil {
|
||||
// Id is null for notifications and they don't have a response.
|
||||
res := &serverResponse{
|
||||
Result: reply,
|
||||
Error: &null,
|
||||
Id: c.request.Id,
|
||||
}
|
||||
c.writeServerResponse(w, 200, res)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CodecRequest) WriteError(w http.ResponseWriter, _ int, err error) {
|
||||
res := &serverResponse{
|
||||
Result: &null,
|
||||
Id: c.request.Id,
|
||||
}
|
||||
if jsonErr, ok := err.(*Error); ok {
|
||||
res.Error = jsonErr.Data
|
||||
} else {
|
||||
res.Error = err.Error()
|
||||
}
|
||||
c.writeServerResponse(w, 400, res)
|
||||
}
|
||||
|
||||
func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, status int, res *serverResponse) {
|
||||
b, err := json.Marshal(res)
|
||||
if err == nil {
|
||||
w.WriteHeader(status)
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Write(b)
|
||||
} else {
|
||||
// Not sure in which case will this happen. But seems harmless.
|
||||
rpc.WriteError(w, 400, err.Error())
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request and Response
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// clientRequest represents a JSON-RPC request sent by a client.
|
||||
type clientRequest struct {
|
||||
// JSON-RPC protocol.
|
||||
Version string `json:"jsonrpc"`
|
||||
|
||||
// A String containing the name of the method to be invoked.
|
||||
Method string `json:"method"`
|
||||
|
||||
// Object to pass as request parameter to the method.
|
||||
Params interface{} `json:"params"`
|
||||
|
||||
// The request id. This can be of any type. It is used to match the
|
||||
// response with the request that it is replying to.
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
// clientResponse represents a JSON-RPC response returned to a client.
|
||||
type clientResponse struct {
|
||||
Version string `json:"jsonrpc"`
|
||||
Result *json.RawMessage `json:"result"`
|
||||
Error *json.RawMessage `json:"error"`
|
||||
}
|
||||
|
||||
// EncodeClientRequest encodes parameters for a JSON-RPC client request.
|
||||
func EncodeClientRequest(method string, args interface{}) ([]byte, error) {
|
||||
c := &clientRequest{
|
||||
Version: "2.0",
|
||||
Method: method,
|
||||
Params: args,
|
||||
Id: uint64(rand.Int63()),
|
||||
}
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// DecodeClientResponse decodes the response body of a client request into
|
||||
// the interface reply.
|
||||
func DecodeClientResponse(r io.Reader, reply interface{}) error {
|
||||
var c clientResponse
|
||||
if err := json.NewDecoder(r).Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.Error != nil {
|
||||
jsonErr := &Error{}
|
||||
if err := json.Unmarshal(*c.Error, jsonErr); err != nil {
|
||||
return &Error{
|
||||
Code: E_SERVER,
|
||||
Message: string(*c.Error),
|
||||
}
|
||||
}
|
||||
return jsonErr
|
||||
}
|
||||
|
||||
if c.Result == nil {
|
||||
return ErrNullResult
|
||||
}
|
||||
|
||||
return json.Unmarshal(*c.Result, reply)
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
E_PARSE ErrorCode = -32700
|
||||
E_INVALID_REQ ErrorCode = -32600
|
||||
E_NO_METHOD ErrorCode = -32601
|
||||
E_BAD_PARAMS ErrorCode = -32602
|
||||
E_INTERNAL ErrorCode = -32603
|
||||
E_SERVER ErrorCode = -32000
|
||||
)
|
||||
|
||||
var ErrNullResult = errors.New("result is null")
|
||||
|
||||
type Error struct {
|
||||
// A Number that indicates the error type that occurred.
|
||||
Code ErrorCode `json:"code"` /* required */
|
||||
|
||||
// A String providing a short description of the error.
|
||||
// The message SHOULD be limited to a concise single sentence.
|
||||
Message string `json:"message"` /* required */
|
||||
|
||||
// A Primitive or Structured value that contains additional information about the error.
|
||||
Data interface{} `json:"data"` /* optional */
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/rpc/v2"
|
||||
)
|
||||
|
||||
// ResponseRecorder is an implementation of http.ResponseWriter that
|
||||
// records its mutations for later inspection in tests.
|
||||
type ResponseRecorder struct {
|
||||
Code int // the HTTP response code from WriteHeader
|
||||
HeaderMap http.Header // the HTTP response headers
|
||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||
Flushed bool
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewRecorder() *ResponseRecorder {
|
||||
return &ResponseRecorder{
|
||||
HeaderMap: make(http.Header),
|
||||
Body: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
||||
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
|
||||
const DefaultRemoteAddr = "1.2.3.4"
|
||||
|
||||
// Header returns the response headers.
|
||||
func (rw *ResponseRecorder) Header() http.Header {
|
||||
return rw.HeaderMap
|
||||
}
|
||||
|
||||
// Write always succeeds and writes to rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||
if rw.Body != nil {
|
||||
rw.Body.Write(buf)
|
||||
}
|
||||
if rw.Code == 0 {
|
||||
rw.Code = http.StatusOK
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// WriteHeader sets rw.Code.
|
||||
func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||
rw.Code = code
|
||||
}
|
||||
|
||||
// Flush sets rw.Flushed to true.
|
||||
func (rw *ResponseRecorder) Flush() {
|
||||
rw.Flushed = true
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var ErrResponseError = errors.New("response error")
|
||||
|
||||
type Service1Request struct {
|
||||
A int
|
||||
B int
|
||||
}
|
||||
|
||||
type Service1BadRequest struct {
|
||||
V string `json:"jsonrpc"`
|
||||
M string `json:"method"`
|
||||
ID uint64 `json:"id"`
|
||||
}
|
||||
|
||||
type Service1Response struct {
|
||||
Result int
|
||||
}
|
||||
|
||||
type Service1 struct {
|
||||
}
|
||||
|
||||
func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
res.Result = req.A * req.B
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
return ErrResponseError
|
||||
}
|
||||
|
||||
func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) error {
|
||||
if !s.HasMethod(method) {
|
||||
t.Fatal("Expected to be registered:", method)
|
||||
}
|
||||
|
||||
buf, _ := EncodeClientRequest(method, req)
|
||||
body := bytes.NewBuffer(buf)
|
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", body)
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
|
||||
return DecodeClientResponse(w.Body, res)
|
||||
}
|
||||
|
||||
func executeRaw(t *testing.T, s *rpc.Server, req interface{}, res interface{}) error {
|
||||
j, _ := json.Marshal(req)
|
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(j))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
|
||||
return DecodeClientResponse(w.Body, res)
|
||||
}
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
s := rpc.NewServer()
|
||||
s.RegisterCodec(NewCodec(), "application/json")
|
||||
s.RegisterService(new(Service1), "")
|
||||
|
||||
var res Service1Response
|
||||
if err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
|
||||
t.Error("Expected err to be nil, but got:", err)
|
||||
}
|
||||
if res.Result != 8 {
|
||||
t.Errorf("Wrong response: %v.", res.Result)
|
||||
}
|
||||
|
||||
if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil {
|
||||
t.Errorf("Expected to get %q, but got nil", ErrResponseError)
|
||||
} else if err.Error() != ErrResponseError.Error() {
|
||||
t.Errorf("Expected to get %q, but got %q", ErrResponseError, err)
|
||||
}
|
||||
|
||||
if err := executeRaw(t, s, &Service1BadRequest{"2.0", "Service1.Multiply", 1}, &res); err == nil {
|
||||
t.Errorf("Expected error but error in nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeNullResult(t *testing.T) {
|
||||
data := `{"jsonrpc": "2.0", "id": 12345, "result": null}`
|
||||
reader := bytes.NewReader([]byte(data))
|
||||
var result interface{}
|
||||
|
||||
err := DecodeClientResponse(reader, &result)
|
||||
|
||||
if err != ErrNullResult {
|
||||
t.Error("Expected err no be ErrNullResult, but got:", err)
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
t.Error("Expected result to be nil, but got:", result)
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/rpc/v2"
|
||||
)
|
||||
|
||||
var null = json.RawMessage([]byte("null"))
|
||||
var Version = "2.0"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request and Response
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// serverRequest represents a JSON-RPC request received by the server.
|
||||
type serverRequest struct {
|
||||
// JSON-RPC protocol.
|
||||
Version string `json:"jsonrpc"`
|
||||
|
||||
// A String containing the name of the method to be invoked.
|
||||
Method string `json:"method"`
|
||||
|
||||
// A Structured value to pass as arguments to the method.
|
||||
Params *json.RawMessage `json:"params"`
|
||||
|
||||
// The request id. MUST be a string, number or null.
|
||||
// Our implementation will not do type checking for id.
|
||||
// It will be copied as it is.
|
||||
Id *json.RawMessage `json:"id"`
|
||||
}
|
||||
|
||||
// serverResponse represents a JSON-RPC response returned by the server.
|
||||
type serverResponse struct {
|
||||
// JSON-RPC protocol.
|
||||
Version string `json:"jsonrpc"`
|
||||
|
||||
// The Object that was returned by the invoked method. This must be null
|
||||
// in case there was an error invoking the method.
|
||||
// As per spec the member will be omitted if there was an error.
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
|
||||
// An Error object if there was an error invoking the method. It must be
|
||||
// null if there was no error.
|
||||
// As per spec the member will be omitted if there was no error.
|
||||
Error *Error `json:"error,omitempty"`
|
||||
|
||||
// This must be the same id as the request it is responding to.
|
||||
Id *json.RawMessage `json:"id"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Codec
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// NewcustomCodec returns a new JSON Codec based on passed encoder selector.
|
||||
func NewCustomCodec(encSel rpc.EncoderSelector) *Codec {
|
||||
return &Codec{encSel: encSel}
|
||||
}
|
||||
|
||||
// NewCodec returns a new JSON Codec.
|
||||
func NewCodec() *Codec {
|
||||
return NewCustomCodec(rpc.DefaultEncoderSelector)
|
||||
}
|
||||
|
||||
// Codec creates a CodecRequest to process each request.
|
||||
type Codec struct {
|
||||
encSel rpc.EncoderSelector
|
||||
}
|
||||
|
||||
// NewRequest returns a CodecRequest.
|
||||
func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
|
||||
return newCodecRequest(r, c.encSel.Select(r))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CodecRequest
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// newCodecRequest returns a new CodecRequest.
|
||||
func newCodecRequest(r *http.Request, encoder rpc.Encoder) rpc.CodecRequest {
|
||||
// Decode the request body and check if RPC method is valid.
|
||||
req := new(serverRequest)
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
if err != nil {
|
||||
err = &Error{
|
||||
Code: E_PARSE,
|
||||
Message: err.Error(),
|
||||
Data: req,
|
||||
}
|
||||
}
|
||||
if req.Version != Version {
|
||||
err = &Error{
|
||||
Code: E_INVALID_REQ,
|
||||
Message: "jsonrpc must be " + Version,
|
||||
Data: req,
|
||||
}
|
||||
}
|
||||
r.Body.Close()
|
||||
return &CodecRequest{request: req, err: err, encoder: encoder}
|
||||
}
|
||||
|
||||
// CodecRequest decodes and encodes a single request.
|
||||
type CodecRequest struct {
|
||||
request *serverRequest
|
||||
err error
|
||||
encoder rpc.Encoder
|
||||
}
|
||||
|
||||
// Method returns the RPC method for the current request.
|
||||
//
|
||||
// The method uses a dotted notation as in "Service.Method".
|
||||
func (c *CodecRequest) Method() (string, error) {
|
||||
if c.err == nil {
|
||||
return c.request.Method, nil
|
||||
}
|
||||
return "", c.err
|
||||
}
|
||||
|
||||
// ReadRe<quest fills the request object for the RPC method.
|
||||
func (c *CodecRequest) ReadRequest(args interface{}) error {
|
||||
if c.err == nil {
|
||||
if c.request.Params != nil {
|
||||
// JSON params structured object. Unmarshal to the args object.
|
||||
err := json.Unmarshal(*c.request.Params, args)
|
||||
if err != nil {
|
||||
c.err = &Error{
|
||||
Code: E_INVALID_REQ,
|
||||
Message: err.Error(),
|
||||
Data: c.request.Params,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.err = &Error{
|
||||
Code: E_INVALID_REQ,
|
||||
Message: "rpc: method request ill-formed: missing params field",
|
||||
}
|
||||
}
|
||||
}
|
||||
return c.err
|
||||
}
|
||||
|
||||
// WriteResponse encodes the response and writes it to the ResponseWriter.
|
||||
func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
|
||||
res := &serverResponse{
|
||||
Version: Version,
|
||||
Result: reply,
|
||||
Id: c.request.Id,
|
||||
}
|
||||
c.writeServerResponse(w, res)
|
||||
}
|
||||
|
||||
func (c *CodecRequest) WriteError(w http.ResponseWriter, status int, err error) {
|
||||
jsonErr, ok := err.(*Error)
|
||||
if !ok {
|
||||
jsonErr = &Error{
|
||||
Code: E_SERVER,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
res := &serverResponse{
|
||||
Version: Version,
|
||||
Error: jsonErr,
|
||||
Id: c.request.Id,
|
||||
}
|
||||
c.writeServerResponse(w, res)
|
||||
}
|
||||
|
||||
func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, res *serverResponse) {
|
||||
// Id is null for notifications and they don't have a response.
|
||||
if c.request.Id != nil {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
encoder := json.NewEncoder(c.encoder.Encode(w))
|
||||
err := encoder.Encode(res)
|
||||
|
||||
// Not sure in which case will this happen. But seems harmless.
|
||||
if err != nil {
|
||||
rpc.WriteError(w, 400, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type EmptyResponse struct {
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
Counter (TestApp)
|
||||
=================
|
||||
|
||||
1) Build
|
||||
|
||||
$ go build counter.go
|
||||
$ ./counter
|
||||
|
||||
|
||||
2) Navigate to localhost:65534 on the browser to access the app.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2013 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/rpc/v2"
|
||||
"github.com/gorilla/rpc/v2/json2"
|
||||
)
|
||||
|
||||
type Counter struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
type IncrReq struct {
|
||||
Delta int
|
||||
}
|
||||
|
||||
// Notification.
|
||||
func (c *Counter) Incr(r *http.Request, req *IncrReq, res *json2.EmptyResponse) error {
|
||||
log.Printf("<- Incr %+v", *req)
|
||||
c.Count += req.Delta
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetReq struct {
|
||||
}
|
||||
|
||||
func (c *Counter) Get(r *http.Request, req *GetReq, res *Counter) error {
|
||||
log.Printf("<- Get %+v", *req)
|
||||
*res = *c
|
||||
log.Printf("-> %v", *res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
address := flag.String("address", ":65534", "")
|
||||
s := rpc.NewServer()
|
||||
s.RegisterCodec(json2.NewCustomCodec(&rpc.CompressionSelector{}), "application/json")
|
||||
s.RegisterService(new(Counter), "")
|
||||
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("./"))))
|
||||
http.Handle("/jsonrpc/", s)
|
||||
log.Fatal(http.ListenAndServe(*address, nil))
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
function log(m, label) {
|
||||
msg = $("<li><span>" + m + "</span></li>");
|
||||
msg.find("span").addClass(label);
|
||||
out = $("#output");
|
||||
out.append(msg);
|
||||
out.animate({"scrollTop": out[0].scrollHeight}, "fast");
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#incr").click(function() {
|
||||
req = {
|
||||
method : "Counter.Incr",
|
||||
params : {delta: 1},
|
||||
};
|
||||
log("<- " + JSON.stringify(req), "secondary label");
|
||||
$.jsonrpc(req);
|
||||
});
|
||||
$("#get").click(function() {
|
||||
req = {
|
||||
method : "Counter.Get",
|
||||
params : {},
|
||||
};
|
||||
log("<- " + JSON.stringify(req), "label");
|
||||
$.jsonrpc(req, {
|
||||
success : function(result) {
|
||||
$("#get").addClass("success");
|
||||
setTimeout(function() {
|
||||
$("#get").removeClass("success");
|
||||
}, 2000);
|
||||
log("-> " + JSON.stringify(result), "success label");
|
||||
},
|
||||
error : function(error) {
|
||||
$("#get").addClass("alert");
|
||||
setTimeout(function() {
|
||||
$("#get").removeClass("alert");
|
||||
}, 2000);
|
||||
log("-> " + JSON.stringify(error), "alert label");
|
||||
},
|
||||
});
|
||||
});
|
||||
$("#nan").click(function() {
|
||||
req = {
|
||||
method : "Counter.Nan",
|
||||
params : {},
|
||||
};
|
||||
log("<- " + JSON.stringify(req), "label");
|
||||
$.jsonrpc(req, {
|
||||
success : function(result) {
|
||||
$("#nan").addClass("success");
|
||||
setTimeout(function() {
|
||||
$("#nan").removeClass("success");
|
||||
}, 2000);
|
||||
log("-> " + JSON.stringify(result), "success label");
|
||||
},
|
||||
error : function(error) {
|
||||
$("#nan").addClass("alert");
|
||||
setTimeout(function() {
|
||||
$("#nan").removeClass("alert");
|
||||
}, 2000);
|
||||
log("-> " + JSON.stringify(error), "alert label");
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="//code.jquery.com/jquery-1.9.1.js"></script>
|
||||
<link href="//cdn.jsdelivr.net/foundation/4.1.2/css/foundation.min.css" rel='stylesheet' type='text/css'>
|
||||
<link href="//fonts.googleapis.com/css?family=Inconsolata" rel='stylesheet' type='text/css'>
|
||||
<script src="jquery.jsonrpc.js"></script>
|
||||
<script src="counter.js"></script>
|
||||
<title>Counter</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inconsolata', sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="padding:20px">
|
||||
<div class="row">
|
||||
<div class="large-1 columns"><a id="incr" href="#" class="small secondary button">Incr</a></div>
|
||||
<div class="large-1 columns"><a id="get" href="#" class="small button">Get </a></div>
|
||||
<div class="large-1 columns"><a id="nan" href="#" class="small button">Nan </a></div>
|
||||
<div class="large-9 columns"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="large-12 columns panel">
|
||||
<ul id="output" style="height:600px;overflow:scroll;list-style-type:none;">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* jQuery JSON-RPC Plugin
|
||||
*
|
||||
* @version: 0.3(2012-05-17)
|
||||
* @author hagino3000 <http://twitter.com/hagino3000> (Takashi Nishibayashi)
|
||||
* @author alanjds <http://twitter.com/alanjds> (Alan Justino da Silva)
|
||||
*
|
||||
* A JSON-RPC 2.0 implementation for jQuery.
|
||||
* JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol.
|
||||
* Read more in the <http://groups.google.com/group/json-rpc/web/json-rpc-2-0>
|
||||
*
|
||||
* Requires json2.js<http://www.json.org/json2.js> if browser has not window.JSON.
|
||||
*
|
||||
* Usage:
|
||||
* $.jsonrpc(data [, callbacks [, debug]]);
|
||||
*
|
||||
* where data = {url: '/rpc/', method:'simplefunc', params:['posi', 'tional']}
|
||||
* or data = {url: '/rpc/', method:'complexfunc', params:{nam:'ed', par:'ams'}}
|
||||
* and callbacks = {success: successFunc, error: errorFunc}
|
||||
*
|
||||
* Setting no callback produces a JSON-RPC Notification.
|
||||
* 'data' accepts 'timeout' keyword too, who sets the $.ajax request timeout.
|
||||
* Setting 'debug' to true prints responses to Firebug's console.info
|
||||
*
|
||||
* Examples:
|
||||
* // A RPC call with named parameters
|
||||
* $.jsonrpc({
|
||||
* url : '/rpc',
|
||||
* method : 'createUser',
|
||||
* params : {name : 'John Smith', userId : '1000'}
|
||||
* }, {
|
||||
* success : function(result) {
|
||||
* //doSomething
|
||||
* },
|
||||
* error : function(error) {
|
||||
* //doSomething
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // Once set defaultUrl, url option is no need
|
||||
* $.jsonrpc.defaultUrl = '/rpc';
|
||||
*
|
||||
* // A Notification
|
||||
* $.jsonrpc({
|
||||
* method : 'notify',
|
||||
* params : {action : 'logout', userId : '1000'}
|
||||
* });
|
||||
*
|
||||
* // A Notification using console to debug and with timeout set
|
||||
* $.jsonrpc({
|
||||
* method : 'notify',
|
||||
* params : {action : 'logout', userId : '1000'},
|
||||
* debug : true,
|
||||
* timeout : 500,
|
||||
* });
|
||||
*
|
||||
* // Set DataFilter. It is useful for buggy API that returns sometimes not json but html (when 500, 403..).
|
||||
* $.jsonrpc({
|
||||
* method : 'getUser',
|
||||
* dataFilter : function(data, type) {
|
||||
* try {
|
||||
* return JSON.parse(data);
|
||||
* } catch(e) {
|
||||
* return {error : {message : 'Cannot parse response', data : data}};
|
||||
* }
|
||||
* }, function(result){ doSomething... }
|
||||
* }, {
|
||||
* success : handleSuccess
|
||||
* error : handleFailure
|
||||
* });
|
||||
*
|
||||
* This document is licensed as free software under the terms of the
|
||||
* MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
var rpcid = 1,
|
||||
emptyFn = function() {};
|
||||
|
||||
$.jsonrpc = $.jsonrpc || function(data, callbacks, debug) {
|
||||
debug = debug || false;
|
||||
|
||||
var postdata = {
|
||||
jsonrpc: '2.0',
|
||||
method: data.method || '',
|
||||
params: data.params || {}
|
||||
};
|
||||
if (callbacks) {
|
||||
postdata.id = data.id || rpcid++;
|
||||
} else {
|
||||
callbacks = emptyFn;
|
||||
}
|
||||
|
||||
if (typeof(callbacks) === 'function') {
|
||||
callbacks = {
|
||||
success: callbacks,
|
||||
error: callbacks
|
||||
};
|
||||
}
|
||||
|
||||
var dataFilter = data.dataFilter;
|
||||
|
||||
var ajaxopts = {
|
||||
url: data.url || $.jsonrpc.defaultUrl,
|
||||
contentType: 'application/json',
|
||||
dataType: 'text',
|
||||
dataFilter: function(data, type) {
|
||||
if (dataFilter) {
|
||||
return dataFilter(data);
|
||||
} else {
|
||||
if (data != "") return JSON.parse(data);
|
||||
}
|
||||
},
|
||||
type: 'POST',
|
||||
processData: false,
|
||||
data: JSON.stringify(postdata),
|
||||
success: function(resp) {
|
||||
if (resp && !resp.error) {
|
||||
return callbacks.success && callbacks.success(resp.result);
|
||||
} else if (resp && resp.error) {
|
||||
return callbacks.error && callbacks.error(resp.error);
|
||||
} else {
|
||||
return callbacks.error && callbacks.error(resp);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
if (error === 'timeout') {
|
||||
callbacks.error({
|
||||
status: status,
|
||||
code: 0,
|
||||
message: 'Request Timeout'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// If response code is 404, 400, 500, server returns error object
|
||||
try {
|
||||
var res = JSON.parse(xhr.responseText);
|
||||
callbacks.error(res.error);
|
||||
} catch (e) {
|
||||
callbacks.error({
|
||||
status: status,
|
||||
code: 0,
|
||||
message: error
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
if (data.timeout) {
|
||||
ajaxopts['timeout'] = data.timeout;
|
||||
}
|
||||
|
||||
$.ajax(ajaxopts);
|
||||
|
||||
return $;
|
||||
}
|
||||
$.jsonrpc.defaultUrl = $.jsonrpc.defaultUrl || '/jsonrpc/';
|
||||
|
||||
})(jQuery);
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
// Precompute the reflect.Type of error and http.Request
|
||||
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||
typeOfRequest = reflect.TypeOf((*http.Request)(nil)).Elem()
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// service
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type service struct {
|
||||
name string // name of service
|
||||
rcvr reflect.Value // receiver of methods for the service
|
||||
rcvrType reflect.Type // type of the receiver
|
||||
methods map[string]*serviceMethod // registered methods
|
||||
}
|
||||
|
||||
type serviceMethod struct {
|
||||
method reflect.Method // receiver method
|
||||
argsType reflect.Type // type of the request argument
|
||||
replyType reflect.Type // type of the response argument
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// serviceMap
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// serviceMap is a registry for services.
|
||||
type serviceMap struct {
|
||||
mutex sync.Mutex
|
||||
services map[string]*service
|
||||
}
|
||||
|
||||
// register adds a new service using reflection to extract its methods.
|
||||
func (m *serviceMap) register(rcvr interface{}, name string) error {
|
||||
// Setup service.
|
||||
s := &service{
|
||||
name: name,
|
||||
rcvr: reflect.ValueOf(rcvr),
|
||||
rcvrType: reflect.TypeOf(rcvr),
|
||||
methods: make(map[string]*serviceMethod),
|
||||
}
|
||||
if name == "" {
|
||||
s.name = reflect.Indirect(s.rcvr).Type().Name()
|
||||
if !isExported(s.name) {
|
||||
return fmt.Errorf("rpc: type %q is not exported", s.name)
|
||||
}
|
||||
}
|
||||
if s.name == "" {
|
||||
return fmt.Errorf("rpc: no service name for type %q",
|
||||
s.rcvrType.String())
|
||||
}
|
||||
// Setup methods.
|
||||
for i := 0; i < s.rcvrType.NumMethod(); i++ {
|
||||
method := s.rcvrType.Method(i)
|
||||
mtype := method.Type
|
||||
// Method must be exported.
|
||||
if method.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
// Method needs four ins: receiver, *http.Request, *args, *reply.
|
||||
if mtype.NumIn() != 4 {
|
||||
continue
|
||||
}
|
||||
// First argument must be a pointer and must be http.Request.
|
||||
reqType := mtype.In(1)
|
||||
if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest {
|
||||
continue
|
||||
}
|
||||
// Second argument must be a pointer and must be exported.
|
||||
args := mtype.In(2)
|
||||
if args.Kind() != reflect.Ptr || !isExportedOrBuiltin(args) {
|
||||
continue
|
||||
}
|
||||
// Third argument must be a pointer and must be exported.
|
||||
reply := mtype.In(3)
|
||||
if reply.Kind() != reflect.Ptr || !isExportedOrBuiltin(reply) {
|
||||
continue
|
||||
}
|
||||
// Method needs one out: error.
|
||||
if mtype.NumOut() != 1 {
|
||||
continue
|
||||
}
|
||||
if returnType := mtype.Out(0); returnType != typeOfError {
|
||||
continue
|
||||
}
|
||||
s.methods[method.Name] = &serviceMethod{
|
||||
method: method,
|
||||
argsType: args.Elem(),
|
||||
replyType: reply.Elem(),
|
||||
}
|
||||
}
|
||||
if len(s.methods) == 0 {
|
||||
return fmt.Errorf("rpc: %q has no exported methods of suitable type",
|
||||
s.name)
|
||||
}
|
||||
// Add to the map.
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
if m.services == nil {
|
||||
m.services = make(map[string]*service)
|
||||
} else if _, ok := m.services[s.name]; ok {
|
||||
return fmt.Errorf("rpc: service already defined: %q", s.name)
|
||||
}
|
||||
m.services[s.name] = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// get returns a registered service given a method name.
|
||||
//
|
||||
// The method name uses a dotted notation as in "Service.Method".
|
||||
func (m *serviceMap) get(method string) (*service, *serviceMethod, error) {
|
||||
parts := strings.Split(method, ".")
|
||||
if len(parts) != 2 {
|
||||
err := fmt.Errorf("rpc: service/method request ill-formed: %q", method)
|
||||
return nil, nil, err
|
||||
}
|
||||
m.mutex.Lock()
|
||||
service := m.services[parts[0]]
|
||||
m.mutex.Unlock()
|
||||
if service == nil {
|
||||
err := fmt.Errorf("rpc: can't find service %q", method)
|
||||
return nil, nil, err
|
||||
}
|
||||
serviceMethod := service.methods[parts[1]]
|
||||
if serviceMethod == nil {
|
||||
err := fmt.Errorf("rpc: can't find method %q", method)
|
||||
return nil, nil, err
|
||||
}
|
||||
return service, serviceMethod, nil
|
||||
}
|
||||
|
||||
// isExported returns true of a string is an exported (upper case) name.
|
||||
func isExported(name string) bool {
|
||||
rune, _ := utf8.DecodeRuneInString(name)
|
||||
return unicode.IsUpper(rune)
|
||||
}
|
||||
|
||||
// isExportedOrBuiltin returns true if a type is exported or a builtin.
|
||||
func isExportedOrBuiltin(t reflect.Type) bool {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
// PkgPath will be non-empty even for an exported type,
|
||||
// so we need to check the type name as well.
|
||||
return isExported(t.Name()) || t.PkgPath() == ""
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package gorilla/rpc/protorpc provides a codec for ProtoRPC over HTTP services.
|
||||
|
||||
To register the codec in a RPC server:
|
||||
|
||||
import (
|
||||
"http"
|
||||
"github.com/gorilla/rpc/v2"
|
||||
"github.com/gorilla/rpc/v2/protorpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
s := rpc.NewServer()
|
||||
s.RegisterCodec(protorpc.NewCodec(), "application/json")
|
||||
// [...]
|
||||
http.Handle("/rpc", s)
|
||||
}
|
||||
|
||||
A codec is tied to a content type. In the example above, the server
|
||||
will use the ProtoRPC codec for requests with "application/json" as
|
||||
the value for the "Content-Type" header.
|
||||
|
||||
This package implement ProtoRPC, based on the JSON-RPC transport, it
|
||||
differs in that it uses HTTP as its envelope.
|
||||
|
||||
Example:
|
||||
POST /Service.Method
|
||||
Request:
|
||||
{
|
||||
"requestField1": "value1",
|
||||
"requestField2": "value2",
|
||||
}
|
||||
Response:
|
||||
{
|
||||
"responseField1": "value1",
|
||||
"responseField2": "value2",
|
||||
}
|
||||
|
||||
Check the gorilla/rpc documentation for more details:
|
||||
|
||||
http://gorilla-web.appspot.com/pkg/rpc
|
||||
*/
|
||||
package protorpc
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package protorpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/rpc/v2"
|
||||
)
|
||||
|
||||
var ErrResponseError = errors.New("response error")
|
||||
|
||||
type Service1Request struct {
|
||||
A int
|
||||
B int
|
||||
}
|
||||
|
||||
type Service1BadRequest struct {
|
||||
}
|
||||
|
||||
type Service1Response struct {
|
||||
Result int
|
||||
ErrorMessage string `json:"error_message"`
|
||||
}
|
||||
|
||||
type Service1 struct {
|
||||
}
|
||||
|
||||
func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
res.Result = req.A * req.B
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
return ErrResponseError
|
||||
}
|
||||
|
||||
func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) (int, error) {
|
||||
if !s.HasMethod(method) {
|
||||
t.Fatal("Expected to be registered:", method)
|
||||
}
|
||||
|
||||
buf, _ := json.Marshal(req)
|
||||
body := bytes.NewBuffer(buf)
|
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/"+method, body)
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
|
||||
err := json.NewDecoder(w.Body).Decode(res)
|
||||
return w.Code, err
|
||||
}
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
s := rpc.NewServer()
|
||||
s.RegisterCodec(NewCodec(), "application/json")
|
||||
s.RegisterService(new(Service1), "")
|
||||
|
||||
var res Service1Response
|
||||
if _, err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
|
||||
t.Error("Expected err to be nil, but got:", err)
|
||||
}
|
||||
if res.Result != 8 {
|
||||
t.Error("Expected res.Result to be 8, but got:", res.Result)
|
||||
}
|
||||
if res.ErrorMessage != "" {
|
||||
t.Error("Expected error_message to be empty, but got:", res.ErrorMessage)
|
||||
}
|
||||
if code, err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err != nil || code != 400 {
|
||||
t.Errorf("Expected code to be 400 and error to be nil, but got %v (%v)", code, err)
|
||||
}
|
||||
if res.ErrorMessage == "" {
|
||||
t.Errorf("Expected error_message to be %q, but got %q", ErrResponseError, res.ErrorMessage)
|
||||
}
|
||||
if code, _ := execute(t, s, "Service1.Multiply", nil, &res); code != 400 {
|
||||
t.Error("Expected http response code 400, but got", code)
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package protorpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/rpc/v2"
|
||||
)
|
||||
|
||||
var null = json.RawMessage([]byte("null"))
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request and Response
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// serverRequest represents a ProtoRPC request received by the server.
|
||||
type serverRequest struct {
|
||||
// A String containing the name of the method to be invoked.
|
||||
Method string `json:"method"`
|
||||
// An Array of objects to pass as arguments to the method.
|
||||
Params *json.RawMessage `json:"params"`
|
||||
// The request id. This can be of any type. It is used to match the
|
||||
// response with the request that it is replying to.
|
||||
Id *json.RawMessage `json:"id"`
|
||||
}
|
||||
|
||||
// serverResponse represents a ProtoRPC response returned by the server.
|
||||
type serverResponse struct {
|
||||
// The Object that was returned by the invoked method. This must be null
|
||||
// in case there was an error invoking the method.
|
||||
Result interface{} `json:"result"`
|
||||
// An Error object if there was an error invoking the method. It must be
|
||||
// null if there was no error.
|
||||
Error interface{} `json:"error"`
|
||||
// This must be the same id as the request it is responding to.
|
||||
Id *json.RawMessage `json:"id"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Codec
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// NewCodec returns a new ProtoRPC Codec.
|
||||
func NewCodec() *Codec {
|
||||
return &Codec{}
|
||||
}
|
||||
|
||||
// Codec creates a CodecRequest to process each request.
|
||||
type Codec struct {
|
||||
}
|
||||
|
||||
// NewRequest returns a CodecRequest.
|
||||
func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
|
||||
return newCodecRequest(r)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CodecRequest
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// newCodecRequest returns a new CodecRequest.
|
||||
func newCodecRequest(r *http.Request) rpc.CodecRequest {
|
||||
// Decode the request body and check if RPC method is valid.
|
||||
req := new(serverRequest)
|
||||
path := r.URL.Path
|
||||
index := strings.LastIndex(path, "/")
|
||||
if index < 0 {
|
||||
return &CodecRequest{request: req, err: fmt.Errorf("rpc: no method: %s", path)}
|
||||
}
|
||||
req.Method = path[index+1:]
|
||||
err := json.NewDecoder(r.Body).Decode(&req.Params)
|
||||
r.Body.Close()
|
||||
var errr error
|
||||
if err != io.EOF {
|
||||
errr = err
|
||||
}
|
||||
return &CodecRequest{request: req, err: errr}
|
||||
}
|
||||
|
||||
// CodecRequest decodes and encodes a single request.
|
||||
type CodecRequest struct {
|
||||
request *serverRequest
|
||||
err error
|
||||
}
|
||||
|
||||
// Method returns the RPC method for the current request.
|
||||
//
|
||||
// The method uses a dotted notation as in "Service.Method".
|
||||
func (c *CodecRequest) Method() (string, error) {
|
||||
if c.err == nil {
|
||||
return c.request.Method, nil
|
||||
}
|
||||
return "", c.err
|
||||
}
|
||||
|
||||
// ReadRequest fills the request object for the RPC method.
|
||||
func (c *CodecRequest) ReadRequest(args interface{}) error {
|
||||
if c.err == nil {
|
||||
if c.request.Params != nil {
|
||||
c.err = json.Unmarshal(*c.request.Params, args)
|
||||
} else {
|
||||
c.err = errors.New("rpc: method request ill-formed: missing params field")
|
||||
}
|
||||
}
|
||||
return c.err
|
||||
}
|
||||
|
||||
// WriteResponse encodes the response and writes it to the ResponseWriter.
|
||||
func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
|
||||
res := &serverResponse{
|
||||
Result: reply,
|
||||
Error: &null,
|
||||
Id: c.request.Id,
|
||||
}
|
||||
c.writeServerResponse(w, 200, res)
|
||||
}
|
||||
|
||||
func (c *CodecRequest) WriteError(w http.ResponseWriter, status int, err error) {
|
||||
res := &serverResponse{
|
||||
Result: &struct {
|
||||
ErrorMessage interface{} `json:"error_message"`
|
||||
}{err.Error()},
|
||||
Id: c.request.Id,
|
||||
}
|
||||
c.writeServerResponse(w, status, res)
|
||||
}
|
||||
|
||||
func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, status int, res *serverResponse) {
|
||||
b, err := json.Marshal(res.Result)
|
||||
if err == nil {
|
||||
w.WriteHeader(status)
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.Write(b)
|
||||
} else {
|
||||
// Not sure in which case will this happen. But seems harmless.
|
||||
rpc.WriteError(w, 400, err.Error())
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Codec
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Codec creates a CodecRequest to process each request.
|
||||
type Codec interface {
|
||||
NewRequest(*http.Request) CodecRequest
|
||||
}
|
||||
|
||||
// CodecRequest decodes a request and encodes a response using a specific
|
||||
// serialization scheme.
|
||||
type CodecRequest interface {
|
||||
// Reads the request and returns the RPC method name.
|
||||
Method() (string, error)
|
||||
// Reads the request filling the RPC method args.
|
||||
ReadRequest(interface{}) error
|
||||
// Writes the response using the RPC method reply.
|
||||
WriteResponse(http.ResponseWriter, interface{})
|
||||
// Writes an error produced by the server.
|
||||
WriteError(w http.ResponseWriter, status int, err error)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Server
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// NewServer returns a new RPC server.
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
codecs: make(map[string]Codec),
|
||||
services: new(serviceMap),
|
||||
}
|
||||
}
|
||||
|
||||
// Server serves registered RPC services using registered codecs.
|
||||
type Server struct {
|
||||
codecs map[string]Codec
|
||||
services *serviceMap
|
||||
}
|
||||
|
||||
// RegisterCodec adds a new codec to the server.
|
||||
//
|
||||
// Codecs are defined to process a given serialization scheme, e.g., JSON or
|
||||
// XML. A codec is chosen based on the "Content-Type" header from the request,
|
||||
// excluding the charset definition.
|
||||
func (s *Server) RegisterCodec(codec Codec, contentType string) {
|
||||
s.codecs[strings.ToLower(contentType)] = codec
|
||||
}
|
||||
|
||||
// RegisterService adds a new service to the server.
|
||||
//
|
||||
// The name parameter is optional: if empty it will be inferred from
|
||||
// the receiver type name.
|
||||
//
|
||||
// Methods from the receiver will be extracted if these rules are satisfied:
|
||||
//
|
||||
// - The receiver is exported (begins with an upper case letter) or local
|
||||
// (defined in the package registering the service).
|
||||
// - The method name is exported.
|
||||
// - The method has three arguments: *http.Request, *args, *reply.
|
||||
// - All three arguments are pointers.
|
||||
// - The second and third arguments are exported or local.
|
||||
// - The method has return type error.
|
||||
//
|
||||
// All other methods are ignored.
|
||||
func (s *Server) RegisterService(receiver interface{}, name string) error {
|
||||
return s.services.register(receiver, name)
|
||||
}
|
||||
|
||||
// HasMethod returns true if the given method is registered.
|
||||
//
|
||||
// The method uses a dotted notation as in "Service.Method".
|
||||
func (s *Server) HasMethod(method string) bool {
|
||||
if _, _, err := s.services.get(method); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ServeHTTP
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
WriteError(w, 405, "rpc: POST method required, received "+r.Method)
|
||||
return
|
||||
}
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
idx := strings.Index(contentType, ";")
|
||||
if idx != -1 {
|
||||
contentType = contentType[:idx]
|
||||
}
|
||||
codec := s.codecs[strings.ToLower(contentType)]
|
||||
if codec == nil {
|
||||
WriteError(w, 415, "rpc: unrecognized Content-Type: "+contentType)
|
||||
return
|
||||
}
|
||||
// Create a new codec request.
|
||||
codecReq := codec.NewRequest(r)
|
||||
// Get service method to be called.
|
||||
method, errMethod := codecReq.Method()
|
||||
if errMethod != nil {
|
||||
codecReq.WriteError(w, 400, errMethod)
|
||||
return
|
||||
}
|
||||
serviceSpec, methodSpec, errGet := s.services.get(method)
|
||||
if errGet != nil {
|
||||
codecReq.WriteError(w, 400, errGet)
|
||||
return
|
||||
}
|
||||
// Decode the args.
|
||||
args := reflect.New(methodSpec.argsType)
|
||||
if errRead := codecReq.ReadRequest(args.Interface()); errRead != nil {
|
||||
codecReq.WriteError(w, 400, errRead)
|
||||
return
|
||||
}
|
||||
// Call the service method.
|
||||
reply := reflect.New(methodSpec.replyType)
|
||||
errValue := methodSpec.method.Func.Call([]reflect.Value{
|
||||
serviceSpec.rcvr,
|
||||
reflect.ValueOf(r),
|
||||
args,
|
||||
reply,
|
||||
})
|
||||
// Cast the result to error if needed.
|
||||
var errResult error
|
||||
errInter := errValue[0].Interface()
|
||||
if errInter != nil {
|
||||
errResult = errInter.(error)
|
||||
}
|
||||
// Prevents Internet Explorer from MIME-sniffing a response away
|
||||
// from the declared content-type
|
||||
w.Header().Set("x-content-type-options", "nosniff")
|
||||
// Encode the response.
|
||||
if errResult == nil {
|
||||
codecReq.WriteResponse(w, reply.Interface())
|
||||
} else {
|
||||
codecReq.WriteError(w, 400, errResult)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteError(w http.ResponseWriter, status int, msg string) {
|
||||
w.WriteHeader(status)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprint(w, msg)
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Service1Request struct {
|
||||
A int
|
||||
B int
|
||||
}
|
||||
|
||||
type Service1Response struct {
|
||||
Result int
|
||||
}
|
||||
|
||||
type Service1 struct {
|
||||
}
|
||||
|
||||
func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
res.Result = req.A * req.B
|
||||
return nil
|
||||
}
|
||||
|
||||
type Service2 struct {
|
||||
}
|
||||
|
||||
func TestRegisterService(t *testing.T) {
|
||||
var err error
|
||||
s := NewServer()
|
||||
service1 := new(Service1)
|
||||
service2 := new(Service2)
|
||||
|
||||
// Inferred name.
|
||||
err = s.RegisterService(service1, "")
|
||||
if err != nil || !s.HasMethod("Service1.Multiply") {
|
||||
t.Errorf("Expected to be registered: Service1.Multiply")
|
||||
}
|
||||
// Provided name.
|
||||
err = s.RegisterService(service1, "Foo")
|
||||
if err != nil || !s.HasMethod("Foo.Multiply") {
|
||||
t.Errorf("Expected to be registered: Foo.Multiply")
|
||||
}
|
||||
// No methods.
|
||||
err = s.RegisterService(service2, "")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error on service2")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user