Brad Peabody
Brad Peabody

Reputation: 11417

dynamically determine function parameters in Go

Is there a way to programmatically determine the parameters (sequence and types thereof) to a function in Go?

I would like to translate an HTTP request into a method/func call dynamically, to avoid having to write duplicative marshaling code (yes I know I'll need to be very careful about what methods I expose and the possible security risks involved). This would only seem to be possible if I can inspect the parameters a function expects, parse the appropriate values from the request and then dynamically call the function (looks like the last step would use reflect.Value.Call - at least that part seems straight forward).

EDIT: The advantage of being able to do this is that you can create a method that is suitable for use directly in Go as well as remotely. For example, if you have a func like this:

func UpdatePerson(p *Person) error { ... }

That obviously is usable in Go; and I want to be able to expose that externally via HTTP as well.

Using json-rpc comes close but looks like it would (in some cases) require some more crufty structures to be used for the i/o on it.

EDIT 2: Basically I have a whole shipload of CRUD to write and I want to just expose parts of the DAO without having to write all this crazy-ass marshaling code to make another layer that deals with the HTTP stuff. (For some security sensitive things I'll need to write things more carefully, but many of my DAO functions should literally just be callable "directly from the browser" - and also from Go.) json-rpc meets the first requirement, but not necessarily the second.

Upvotes: 1

Views: 2211

Answers (2)

James Henstridge
James Henstridge

Reputation: 43949

You can determine a function's parameters using the reflect package. For example:

// argument is interface{} so we can accept functions of any signature
func printArguments(f interface{}) {
    t := reflect.TypeOf(f)
    fmt.Println("Function arguments:")
    for i := 0; i < t.NumIn(); i++ {
        fmt.Printf(" %d. %v\n", i, t.In(i))
    }
    fmt.Println("Function return values:")
    for i := 0; i < t.NumOut(); i++ {
        fmt.Printf(" %d. %v\n", i, t.Out(i))
    }
}

The In and Out functions return reflect.Type values representing the argument and return value types respectively. You can use this type information to construct an argument vector of Value objects to call the function with.

You can play around with this example here: http://play.golang.org/p/qLThrI_Cw9

Upvotes: 2

dave
dave

Reputation: 64725

If I understand what you are trying to do, you are looking for json-rpc. A very simple example of how to use it is below:

package main

import (
  "github.com/gorilla/rpc"
  "github.com/gorilla/rpc/json"
  "net/http"
)

type MyArgs struct {
    Msg string
}

type MyResponse struct {
    Msg string
}

type MyService struct{}

func (h *MyService) Test(r *http.Request, args *MyArgs, response *MyResponse) error {
    response.Msg = "Received: " + args.Msg
    return nil
}

func main() {
    s := rpc.NewServer()
    s.RegisterCodec(json.NewCodec(), "application/json")
    s.RegisterService(new(MyService), "")
    http.Handle("/rpc", s)
    http.ListenAndServe(":10000", nil)
}

Test with curl:

curl -X POST -H "Content-Type: application/json" \
-d '{"method":"MyService.Test","params":[{"Msg":"Test"}], "id":"1"}' \
http://localhost:10000/rpc

Response:

{"result":{"Msg":"Received: Test"},"error":null,"id":"1"}

EDIT:

There seems to be a little confusion - you don't need Go on both ends - you can call the method from the browser with jQuery like so:

$.ajax({
  url: '/rpc',
  type:"POST",
  data: JSON.stringify({"method":"MyService.Test","params":[{"Msg":"Test"}], "id":"1"}),
  contentType:"application/json; charset=utf-8",
  dataType:"json",
  success: function(data){
      console.log(data);
  }
});

Upvotes: 2

Related Questions