JSON RPC request in Golang with Gorilla/rpc
سؤال
I'm trying to use the Gorilla/rpc
package to set up a RPC to receive a request and reply with a response (obviously).
First I'm trying with the example provided with Gorilla/rpc
Here's my code:
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
}
func main() {
r := mux.NewRouter()
jsonRPC := rpc.NewServer()
jsonCodec := json.NewCodec()
jsonRPC.RegisterCodec(jsonCodec, "application/json")
jsonRPC.RegisterCodec(jsonCodec, "application/json; charset=UTF-8") // For firefox 11 and other browsers which append the charset=UTF-8
jsonRPC.RegisterService(new(HelloService), "")
r.Handle("/api", jsonRPC)
http.ListenAndServe(":"+port, nil)
}
I have a couple of issues:
I'm not sure how I would set a
Access-Control-Allow-Origin
header like I normally would on ahttp.ResponseWriter
(with a regular webserver) for cross domain requests, as this doesn't take ahttp.ResponseWriter
as an argument.What would I actually send to access the
HelloService.Say
method? I've tried{ method: "HelloService.Say", params:[{Who: "Me"}]}
but I get405 (Method Not Allowed)
(not sure if this is because I can't make x-domain requests though?)
Any insight greatly appreciated.
المحلول
EDITED: to fix bad use of 'type synonyms'
For number 1:
Gorilla/rpc/json
's CodecRequest.WriteResponse
(which implements Gorilla/rpc
's CodecRequest
) is the one place where the code touches an http.ResponseWriter
.
This means that we have to have our own implementation of CodecRequest
which sets the CORS header.
Every CodecRequest
used by the server is actually generated by a Codec
; Codec
s are factories for making CodecRequests
, to put it another way.
This means that we have to create a Codec
to generate our CodecRequest
s that will set CORS headers.
The great thing about Go is that it's really easy to compose this extra behavior!
Try this:
package cors_codec
import (
"Gorilla/rpc"
"net/http"
"strings"
)
//interface: ain't nobody dope like me I feel so fresh and clean
func CodecWithCors([]string corsDomains, unpimped rpc.Codec) rpc.Codec {
return corsCodecRequest{corsDomains, unpimped}
}
type corsCodecRequest struct {
corsDomains []string
underlyingCodecRequest rpc.CodecRequest
}
//override exactly one method of the underlying anonymous field and delegate to it.
func (ccr corsCodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}, methodErr error) error {
w.Header().add("Access-Control-Allow-Origin", strings.join(ccr.corsDomains, " "))
return ccr.underlyingCodecRequest.WriteResponse(w, reply, error)
}
type corsCodec struct {
corsDomains []string
underlyingCodec rpc.Codec
}
//override exactly one method of the underlying anonymous field and delegate to it.
func (cc corsCodec) NewRequest(req *http.Request) rpc.CodecRequest {
return corsCodecRequest{cc.corsDomains, cc.underlyingCodec.NewRequest(req)}
}
That was a fun exercise!
نصائح أخرى
I know this is a pretty old question but there is actually an easier way to achieve this.
You can do this thanks to rs/cors
and justinas/alice
packages in few lines.
func main() {
chain := alice.New(cors.Default().Handler)
server := rpc.NewServer()
server.RegisterCodec(json2.NewCodec(), "application/json")
server.RegisterService(new(HelloService), "")
http.Handle("/rpc", chain.Then(server))
http.ListenAndServe(":8081", nil)
}