1

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.

3
  • 1
    Nope - the function needs to be referenced somewhere otherwise it may be dropped during the build. To that end, I don't think there's a way to dynamically discover a (potentially dropped) function just by querying the runtime. There is, however, runtime.FuncForPC .. but that is statically checked and requires you to pass a function to it. Theres also ways to extract a function's details via the stacktrace .. but again, that won't be of much help to you if you plan on dynamic resolution. Commented Nov 5, 2014 at 2:25
  • Gotcha. What if I have a reference to the func itself (so it won't get dropping during the link phase) but am not calling it, e.g.: var MyFuncs = map[string]interface{} { "func1": func1 } Is there something in reflect that let's me look at it in more detail at runtime? Commented Nov 5, 2014 at 2:29
  • 1
    Not that I am aware of ... no. The reflect package deals with types .. not packages. You may be able to investigate reflect.MakeFunc to dynamically dispatch your calls though (I haven't used it myself.. seems promising though). Commented Nov 5, 2014 at 2:34

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

Comments

2

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);
  }
});

6 Comments

I see what you mean - yes this is definitely an option I hadn't considered.
Ah yes - this is also something I didn't consider.
why does the response look like this {"Msg":"Received: Test"}. Since "Msg" and "Test" were both params, and the params are concatenated onto the existing string "Received: " + args.Msg, I would expect the "Msg" and "Test" to both appear after Received in the response, something more like {Received: Msg : Test }
@Leahcim - because we did response.Msg = "Received: " + args.Msg, which becomes the string "Received: test"
@dave so "Msg" is the key and "Test" is the value, and you access that value by doing response.Msg? I don't see how "Msg": ends up before Received in the result?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.